@controleonline/ui-common 1.2.80 → 1.2.81
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/AGENTS.md +8 -0
- package/package.json +9 -3
- package/src/api/fetch.js +7 -7
- package/src/react/components/AddImportModal.js +6 -2
- package/src/react/components/BackgroundRuntimeBridge.js +201 -215
- package/src/react/components/ConfirmModal.js +44 -9
- package/src/react/components/ConfirmModal.styles.js +55 -41
- package/src/react/components/CopyDeviceConfigModal.js +193 -0
- package/src/react/components/CopyDeviceConfigModal.styles.js +123 -0
- package/src/react/components/DefaultProvider.native.js +22 -0
- package/src/react/components/DefaultProvider.web.js +25 -1
- package/src/react/components/EntityLogBranch.js +482 -0
- package/src/react/components/EntityLogCards.js +369 -0
- package/src/react/components/EntityLogContent.js +113 -794
- package/src/react/components/LinkedOrderProductsTab.js +3 -3
- package/src/react/components/PrintService.js +130 -409
- package/src/react/components/RemoteCheckoutService.js +2 -2
- package/src/react/components/RuntimeBottomNavigationBar.js +141 -146
- package/src/react/components/RuntimeFooterMarqueeText.js +137 -0
- package/src/react/components/RuntimeInfoFooter.js +12 -15
- package/src/react/components/SystemErrorToast.js +49 -29
- package/src/react/components/SystemErrorToast.styles.js +43 -38
- package/src/react/components/UserAvatar.js +120 -28
- package/src/react/config/deviceConfigBootstrap.js +330 -304
- package/src/react/hooks/usePrintSpoolEffects.js +161 -0
- package/src/react/pages/DeviceDetailPage.js +6 -2882
- package/src/react/pages/Devices/detail/DeviceDetailAlertsCommandsSection.js +233 -0
- package/src/react/pages/Devices/detail/DeviceDetailHeader.js +138 -0
- package/src/react/pages/Devices/detail/DeviceDetailMovementSections.js +167 -0
- package/src/react/pages/Devices/detail/DeviceDetailOrdersPrintSection.js +216 -0
- package/src/react/pages/Devices/detail/DeviceDetailPaymentSection.js +40 -0
- package/src/react/pages/Devices/detail/DeviceDetailPdvConfigSection.js +390 -0
- package/src/react/pages/Devices/detail/DeviceDetailRenderers.js +135 -0
- package/src/react/pages/Devices/detail/DeviceDetailScreen.js +493 -0
- package/src/react/pages/Devices/detail/OptionButtonChip.js +65 -0
- package/src/react/pages/Devices/detail/deviceDetailConstants.js +36 -0
- package/src/react/pages/Devices/detail/deviceDetailHelpers.js +70 -0
- package/src/react/pages/Devices/detail/useDeviceDetailActions.js +483 -0
- package/src/react/pages/Devices/detail/useDeviceDetailCopyConfig.js +87 -0
- package/src/react/pages/Devices/detail/useDeviceDetailLoaders.js +327 -0
- package/src/react/pages/Devices/detail/useDeviceDetailSaves.js +429 -0
- package/src/react/pages/Devices/detail/useDeviceDetailStateA.js +484 -0
- package/src/react/pages/Devices/detail/useDeviceDetailStateB.js +71 -0
- package/src/react/pages/Devices/deviceTypes/index.js +2 -0
- package/src/react/pages/Devices/deviceTypes/pdv.js +11 -0
- package/src/react/pages/Imports.js +3 -2
- package/src/react/pages/IntegrationConfigFields.js +126 -0
- package/src/react/pages/IntegrationConfigPage.js +122 -591
- package/src/react/pages/IntegrationConfigPage.utils.js +95 -0
- package/src/react/print/usePrintButtonController.js +9 -1
- package/src/react/services/Getnet/Checkout.js +81 -0
- package/src/react/services/Getnet/Getnet.js +77 -0
- package/src/react/services/paymentGatewayExecution.js +20 -0
- package/src/react/utils/bottomNavigationToolbar.js +161 -0
- package/src/react/utils/commercialDocumentOrders.js +18 -6
- package/src/react/utils/copyDeviceConfigs.js +198 -0
- package/src/react/utils/fileUrl.js +43 -0
- package/src/react/utils/importStatus.js +19 -10
- package/src/react/utils/logSettings.js +7 -7
- package/src/react/utils/maintenanceSettings.js +16 -0
- package/src/react/utils/paymentDevices.js +6 -0
- package/src/react/utils/printManagedPrinter.js +45 -0
- package/src/react/utils/printRouting.js +63 -0
- package/src/react/utils/printSpoolUtils.js +119 -0
- package/src/react/utils/printerDevices.js +368 -368
- package/src/react/utils/runtimeFooter.js +10 -19
- package/src/react/utils/shopFranchises.js +186 -65
- package/src/react/utils/systemErrorMessage.js +71 -67
- package/src/store/device_config/customActions.js +119 -30
- package/src/tests/browser/manager/bottom-navigation-role-toolbar.spec.js +353 -0
- package/src/tests/browser/manager/device-detail-alias-save.spec.js +345 -0
- package/src/tests/browser/manager/device-detail-copy-config.spec.js +365 -0
- package/src/tests/browser/manager/runtime-footer-marquee.spec.js +262 -0
- package/src/tests/browser/manager/theme-confirm-toast.spec.js +236 -0
- package/src/tests/react/components/RuntimeFooterMarqueeText.test.js +171 -0
- package/src/tests/react/components/UserAvatar.test.js +6 -2
- package/src/tests/react/components/themeAwareModals.test.mjs +60 -0
- package/src/tests/react/config/deviceConfigBootstrap.test.js +104 -66
- package/src/tests/react/pages/deviceDetailDelete.test.js +78 -0
- package/src/tests/react/pages/receitaFederalIntegrationContract.test.mjs +48 -0
- package/src/tests/react/utils/bottomNavigationToolbar.test.js +222 -0
- package/src/tests/react/utils/copyDeviceConfigs.test.mjs +127 -0
- package/src/tests/react/utils/fileUrl.test.js +38 -0
- package/src/tests/react/utils/importStatus.test.js +26 -9
- package/src/tests/react/utils/passwordPolicy.test.js +27 -0
- package/src/tests/react/utils/printRouting.test.mjs +86 -0
- package/src/tests/react/utils/printSpoolUtils.test.mjs +52 -0
- package/src/tests/react/utils/runtimeFooter.test.js +7 -0
- package/src/tests/react/utils/shopFranchises.test.js +121 -115
- package/src/tests/react/utils/systemErrorMessage.test.js +32 -59
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy device_config.configs from a source physical device to a destination
|
|
3
|
+
* physical device without changing destination identity (id/hash/hardware).
|
|
4
|
+
*
|
|
5
|
+
* Copyable keys = free-form keys present in the source `configs` JSON
|
|
6
|
+
* (discovered at runtime). Entity fields (id, device, people, type) are never
|
|
7
|
+
* copied.
|
|
8
|
+
*
|
|
9
|
+
* Known keys observed in the codebase (non-exhaustive; actual copy uses
|
|
10
|
+
* whatever keys exist on the source row):
|
|
11
|
+
* - pos-operation-mode, pos-gateway, printer-enabled, payment-type-ids
|
|
12
|
+
* - pos-product-showcase-id, pos-delivery-enabled, pos-auto-print-enabled
|
|
13
|
+
* - pos-cash-management-mode, check-order-type, check-order-management-mode
|
|
14
|
+
* - android-kiosk-enabled, android-launcher-enabled
|
|
15
|
+
* - order-payment-device, order-payment-devices, order-payment-device-change-allowed
|
|
16
|
+
* - display-id, printer, display-auto-print-product, display-allow-printer-change
|
|
17
|
+
* - display-size, display-side-break, print-mode
|
|
18
|
+
* - print-network-port, print-network-columns, print-network-code-page
|
|
19
|
+
* - camera-network-*, notification-sound-*, pos-order-visibility
|
|
20
|
+
* - device-runtime-debug-info-enabled, device-runtime-footer-text
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const normalizeDeviceId = value => String(value || '').trim();
|
|
24
|
+
|
|
25
|
+
const normalizeEntityId = value =>
|
|
26
|
+
String(value?.id || value || '')
|
|
27
|
+
.replace(/\D/g, '')
|
|
28
|
+
.trim();
|
|
29
|
+
|
|
30
|
+
const parseConfigsObject = configs => {
|
|
31
|
+
if (!configs) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
if (typeof configs === 'string') {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(configs);
|
|
37
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
38
|
+
? parsed
|
|
39
|
+
: {};
|
|
40
|
+
} catch {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (typeof configs === 'object' && !Array.isArray(configs)) {
|
|
45
|
+
return configs;
|
|
46
|
+
}
|
|
47
|
+
return {};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const listCopyableConfigKeys = configs =>
|
|
51
|
+
Object.keys(parseConfigsObject(configs) || {}).sort();
|
|
52
|
+
|
|
53
|
+
export const getDeviceConfigDeviceString = deviceConfig =>
|
|
54
|
+
normalizeDeviceId(
|
|
55
|
+
deviceConfig?.device?.device ||
|
|
56
|
+
deviceConfig?.device?.id ||
|
|
57
|
+
deviceConfig?.device ||
|
|
58
|
+
'',
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
export const getDeviceConfigType = deviceConfig =>
|
|
62
|
+
String(deviceConfig?.type || deviceConfig?.device?.type || '')
|
|
63
|
+
.trim()
|
|
64
|
+
.toUpperCase();
|
|
65
|
+
|
|
66
|
+
export const getDeviceConfigLabel = deviceConfig => {
|
|
67
|
+
const alias = String(
|
|
68
|
+
deviceConfig?.device?.alias ||
|
|
69
|
+
deviceConfig?.alias ||
|
|
70
|
+
getDeviceConfigDeviceString(deviceConfig) ||
|
|
71
|
+
'',
|
|
72
|
+
).trim();
|
|
73
|
+
const type = getDeviceConfigType(deviceConfig);
|
|
74
|
+
if (alias && type) {
|
|
75
|
+
return `${alias} (${type})`;
|
|
76
|
+
}
|
|
77
|
+
return alias || type || 'Device';
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const filterByCompany = (deviceConfigs, companyId) => {
|
|
81
|
+
const normalizedCompanyId = normalizeEntityId(companyId);
|
|
82
|
+
return (Array.isArray(deviceConfigs) ? deviceConfigs : []).filter(
|
|
83
|
+
deviceConfig =>
|
|
84
|
+
!normalizedCompanyId ||
|
|
85
|
+
normalizeEntityId(deviceConfig?.people?.id || deviceConfig?.people) ===
|
|
86
|
+
normalizedCompanyId,
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Group company device_config rows by physical device string, excluding the
|
|
92
|
+
* destination device. Used to build the "Copiar de…" picker.
|
|
93
|
+
*/
|
|
94
|
+
export const buildSourceDeviceOptions = ({
|
|
95
|
+
companyDeviceConfigs = [],
|
|
96
|
+
companyId,
|
|
97
|
+
destinationDeviceString,
|
|
98
|
+
} = {}) => {
|
|
99
|
+
const dest = normalizeDeviceId(destinationDeviceString);
|
|
100
|
+
const scoped = filterByCompany(companyDeviceConfigs, companyId);
|
|
101
|
+
const byDevice = new Map();
|
|
102
|
+
|
|
103
|
+
scoped.forEach(row => {
|
|
104
|
+
const deviceString = getDeviceConfigDeviceString(row);
|
|
105
|
+
if (!deviceString || (dest && deviceString === dest)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!byDevice.has(deviceString)) {
|
|
109
|
+
byDevice.set(deviceString, {
|
|
110
|
+
deviceString,
|
|
111
|
+
alias: String(row?.device?.alias || deviceString).trim(),
|
|
112
|
+
configs: [],
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
byDevice.get(deviceString).configs.push(row);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return Array.from(byDevice.values()).sort((a, b) =>
|
|
119
|
+
String(a.alias).localeCompare(String(b.alias), 'pt-BR'),
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Build POST payloads for addDeviceConfigs: one per type present on source.
|
|
125
|
+
*/
|
|
126
|
+
export const buildCopyPayloads = ({
|
|
127
|
+
sourceConfigs = [],
|
|
128
|
+
destinationDeviceString,
|
|
129
|
+
peopleIri,
|
|
130
|
+
} = {}) => {
|
|
131
|
+
const dest = normalizeDeviceId(destinationDeviceString);
|
|
132
|
+
const people = String(peopleIri || '').trim();
|
|
133
|
+
if (!dest || !people) {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const byType = new Map();
|
|
138
|
+
(Array.isArray(sourceConfigs) ? sourceConfigs : []).forEach(row => {
|
|
139
|
+
const type = getDeviceConfigType(row);
|
|
140
|
+
if (!type) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const configs = parseConfigsObject(row?.configs);
|
|
144
|
+
byType.set(type, {
|
|
145
|
+
device: dest,
|
|
146
|
+
people,
|
|
147
|
+
type,
|
|
148
|
+
configs: JSON.stringify(configs || {}),
|
|
149
|
+
keys: listCopyableConfigKeys(configs),
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return Array.from(byType.values());
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Execute copy: for each payload, call addDeviceConfigs.
|
|
158
|
+
*/
|
|
159
|
+
export const executeCopyDeviceConfigs = async ({
|
|
160
|
+
addDeviceConfigs,
|
|
161
|
+
sourceConfigs = [],
|
|
162
|
+
destinationDeviceString,
|
|
163
|
+
peopleIri,
|
|
164
|
+
} = {}) => {
|
|
165
|
+
if (typeof addDeviceConfigs !== 'function') {
|
|
166
|
+
throw new Error('addDeviceConfigs is required');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const payloads = buildCopyPayloads({
|
|
170
|
+
sourceConfigs,
|
|
171
|
+
destinationDeviceString,
|
|
172
|
+
peopleIri,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (payloads.length === 0) {
|
|
176
|
+
return {types: [], keys: [], results: []};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const results = [];
|
|
180
|
+
const allKeys = new Set();
|
|
181
|
+
|
|
182
|
+
for (const payload of payloads) {
|
|
183
|
+
(payload.keys || []).forEach(key => allKeys.add(key));
|
|
184
|
+
const result = await addDeviceConfigs({
|
|
185
|
+
device: payload.device,
|
|
186
|
+
people: payload.people,
|
|
187
|
+
type: payload.type,
|
|
188
|
+
configs: payload.configs,
|
|
189
|
+
});
|
|
190
|
+
results.push({type: payload.type, keys: payload.keys, result});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
types: payloads.map(p => p.type),
|
|
195
|
+
keys: Array.from(allKeys).sort(),
|
|
196
|
+
results,
|
|
197
|
+
};
|
|
198
|
+
};
|
|
@@ -81,6 +81,44 @@ const resolveDownloadHost = ({company = null, appDomain = ''} = {}) => {
|
|
|
81
81
|
return resolveCompanyDomain(company, fallbackDomain);
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Environment / platform hosts (staging, dev, localhost) are not tenant domains.
|
|
86
|
+
* Injecting them into /{host}/files/{id}/download produces URLs like
|
|
87
|
+
* https://s.controleonline.com/staging.controleonline.com/files/... → 403.
|
|
88
|
+
* Keep app-domain header only; do not path-inject environment hosts.
|
|
89
|
+
* app-community#432
|
|
90
|
+
*/
|
|
91
|
+
const isEnvironmentLikeHost = host => {
|
|
92
|
+
const normalized = normalizeText(host).toLowerCase();
|
|
93
|
+
if (!normalized) return false;
|
|
94
|
+
if (
|
|
95
|
+
normalized === 'localhost' ||
|
|
96
|
+
normalized.startsWith('localhost:') ||
|
|
97
|
+
normalized === '127.0.0.1' ||
|
|
98
|
+
normalized.startsWith('127.0.0.1:')
|
|
99
|
+
) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
// staging.*, dev.*, *.staging.*, etc.
|
|
103
|
+
if (
|
|
104
|
+
/^(staging|dev|local|test|qa)([.-]|$)/i.test(normalized) ||
|
|
105
|
+
/\.(staging|dev|local|test)\./i.test(normalized) ||
|
|
106
|
+
normalized.endsWith('.local')
|
|
107
|
+
) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
// Explicit known platform hosts used as ENV DOMAIN, not company domains
|
|
111
|
+
if (
|
|
112
|
+
normalized === 'staging.controleonline.com' ||
|
|
113
|
+
normalized === 'dev.controleonline.com' ||
|
|
114
|
+
normalized === 'manager.controleonline.com' ||
|
|
115
|
+
normalized === 'pos.controleonline.com'
|
|
116
|
+
) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
};
|
|
121
|
+
|
|
84
122
|
const buildBackendDownloadUrl = (fileId, options = {}) => {
|
|
85
123
|
const normalizedId = String(fileId || '').trim();
|
|
86
124
|
if (!normalizedId) {
|
|
@@ -100,6 +138,11 @@ const buildTenantDownloadUrl = (url, host) => {
|
|
|
100
138
|
return absoluteUrl;
|
|
101
139
|
}
|
|
102
140
|
|
|
141
|
+
// Do not inject environment hosts into the path (app-community#432).
|
|
142
|
+
if (isEnvironmentLikeHost(normalizedHost)) {
|
|
143
|
+
return absoluteUrl;
|
|
144
|
+
}
|
|
145
|
+
|
|
103
146
|
if (/^https?:\/\//i.test(absoluteUrl)) {
|
|
104
147
|
const match = stripDomainQueryParams(absoluteUrl).match(
|
|
105
148
|
/^(https?:\/\/[^/?#]+)\/(?:[^/]+\/)?files\/([^/?#]+)\/download([^#]*)(#.*)?$/i,
|
|
@@ -73,18 +73,26 @@ function getImportErrorDetail(item) {
|
|
|
73
73
|
return candidates.find(candidate => String(candidate || '').trim()) || ''
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function resolveImportLabels(translate) {
|
|
77
|
-
return {
|
|
76
|
+
function resolveImportLabels(translate) {
|
|
77
|
+
return {
|
|
78
78
|
refreshLabel: translate?.('imports', 'button', 'refresh'),
|
|
79
79
|
processingLabel: translate?.('imports', 'status', 'processing'),
|
|
80
80
|
errorLabel: translate?.('imports', 'status', 'error'),
|
|
81
81
|
doneLabel: translate?.('imports', 'status', 'done'),
|
|
82
82
|
noStatusLabel: translate?.('imports', 'status', 'no_status'),
|
|
83
83
|
processingHelpLabel: translate?.('imports', 'message', 'processing_after_upload'),
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function resolveImportTranslator(translator = globalThis?.t) {
|
|
88
|
+
if (typeof translator?.t !== 'function') {
|
|
89
|
+
return undefined
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return translator.t.bind(translator)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolveImportStatusLabel(statusMeta, labels = {}) {
|
|
88
96
|
if (statusMeta?.labelKey === 'processing') return labels.processingLabel
|
|
89
97
|
if (statusMeta?.labelKey === 'error') return labels.errorLabel
|
|
90
98
|
if (statusMeta?.labelKey === 'done') return labels.doneLabel
|
|
@@ -94,7 +102,8 @@ function resolveImportStatusLabel(statusMeta, labels = {}) {
|
|
|
94
102
|
module.exports = {
|
|
95
103
|
getImportErrorDetail,
|
|
96
104
|
getImportStatusMeta,
|
|
97
|
-
normalizeImportStatus,
|
|
98
|
-
resolveImportLabels,
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
normalizeImportStatus,
|
|
106
|
+
resolveImportLabels,
|
|
107
|
+
resolveImportTranslator,
|
|
108
|
+
resolveImportStatusLabel,
|
|
109
|
+
}
|
|
@@ -7,25 +7,25 @@ export const LOG_POLICY_ITEMS = [
|
|
|
7
7
|
key: 'backend_error',
|
|
8
8
|
title: 'Erros do backend',
|
|
9
9
|
description:
|
|
10
|
-
'Registra
|
|
10
|
+
'Registra exceções e falhas HTTP do backend. É o grupo principal para erros 500.',
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
key: 'entity',
|
|
14
|
-
title: '
|
|
14
|
+
title: 'Histórico de entidades',
|
|
15
15
|
description:
|
|
16
|
-
'
|
|
16
|
+
'Mantém os diffs de criação, alteração e remoção das entidades do sistema.',
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
key: 'generic',
|
|
20
|
-
title: 'Logs
|
|
20
|
+
title: 'Logs genéricos',
|
|
21
21
|
description:
|
|
22
|
-
'Agrupa logs
|
|
22
|
+
'Agrupa logs técnicos de integração, workers e eventos que não são timeline de entidade.',
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
key: 'operation_patterns',
|
|
26
|
-
title: '
|
|
26
|
+
title: 'Padrões operacionais',
|
|
27
27
|
description:
|
|
28
|
-
'
|
|
28
|
+
'Mantém registros técnicos ligados a padrões e diagnósticos operacionais do sistema.',
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
key: 'frontend_debug',
|
|
@@ -9,6 +9,22 @@ export const MAINTENANCE_ROUTINE_ITEMS = [
|
|
|
9
9
|
defaultEnabled: true,
|
|
10
10
|
defaultCronExpression: '* * * * *',
|
|
11
11
|
},
|
|
12
|
+
{
|
|
13
|
+
key: 'cleanup_ephemeral_integrations',
|
|
14
|
+
title: 'Limpeza de integracoes efemeras',
|
|
15
|
+
description:
|
|
16
|
+
'Remove Websocket e PushNotification abertos ha mais de 24 horas.',
|
|
17
|
+
defaultEnabled: false,
|
|
18
|
+
defaultCronExpression: '0 * * * *',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
key: 'open_overdue_opportunities',
|
|
22
|
+
title: 'Oportunidades vencidas para aberto',
|
|
23
|
+
description:
|
|
24
|
+
'Move oportunidades de CRM de pendente para aberto quando a data de retorno ja passou.',
|
|
25
|
+
defaultEnabled: true,
|
|
26
|
+
defaultCronExpression: '* * * * *',
|
|
27
|
+
},
|
|
12
28
|
];
|
|
13
29
|
|
|
14
30
|
export const normalizeMaintenanceRoutines = value => {
|
|
@@ -2,9 +2,11 @@ import {Platform} from 'react-native';
|
|
|
2
2
|
|
|
3
3
|
export const PAYMENT_GATEWAY_CIELO = 'cielo';
|
|
4
4
|
export const PAYMENT_GATEWAY_INFINITE_PAY = 'infinite-pay';
|
|
5
|
+
export const PAYMENT_GATEWAY_GETNET = 'getnet';
|
|
5
6
|
export const PAYMENT_GATEWAYS = [
|
|
6
7
|
PAYMENT_GATEWAY_CIELO,
|
|
7
8
|
PAYMENT_GATEWAY_INFINITE_PAY,
|
|
9
|
+
PAYMENT_GATEWAY_GETNET,
|
|
8
10
|
];
|
|
9
11
|
export const POS_GATEWAY_CONFIG_KEY = 'pos-gateway';
|
|
10
12
|
export const PDV_PRINTER_ENABLED_CONFIG_KEY = 'printer-enabled';
|
|
@@ -152,6 +154,9 @@ export const normalizePaymentGateway = value => {
|
|
|
152
154
|
if (compactGateway === PAYMENT_GATEWAY_CIELO) {
|
|
153
155
|
return PAYMENT_GATEWAY_CIELO;
|
|
154
156
|
}
|
|
157
|
+
if (compactGateway === 'getnet' || compactGateway === PAYMENT_GATEWAY_GETNET) {
|
|
158
|
+
return PAYMENT_GATEWAY_GETNET;
|
|
159
|
+
}
|
|
155
160
|
|
|
156
161
|
return compactGateway;
|
|
157
162
|
};
|
|
@@ -309,6 +314,7 @@ export const getPaymentDeviceLabel = deviceConfig =>
|
|
|
309
314
|
export const getPaymentGatewayLabel = gateway => {
|
|
310
315
|
if (gateway === PAYMENT_GATEWAY_CIELO) return 'Cielo';
|
|
311
316
|
if (gateway === PAYMENT_GATEWAY_INFINITE_PAY) return 'Infinite Pay';
|
|
317
|
+
if (gateway === PAYMENT_GATEWAY_GETNET) return 'Getnet';
|
|
312
318
|
return 'Gateway nao configurado';
|
|
313
319
|
};
|
|
314
320
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_NETWORK_PRINTER_PORT,
|
|
3
|
+
DEFAULT_NETWORK_PRINTER_CODE_PAGE,
|
|
4
|
+
getPrinterHost,
|
|
5
|
+
NETWORK_PRINTER_CODE_PAGE_CONFIG_KEY,
|
|
6
|
+
NETWORK_PRINTER_PORT_CONFIG_KEY,
|
|
7
|
+
normalizePrinterPort,
|
|
8
|
+
} from '@controleonline/ui-common/src/react/utils/printerDevices';
|
|
9
|
+
import {
|
|
10
|
+
decodeNetworkPrinterPayload,
|
|
11
|
+
printOnNetworkPrinter,
|
|
12
|
+
} from '@controleonline/ui-common/src/react/services/NetworkPrinterService';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Print spool content on a managed network printer when available.
|
|
16
|
+
* @returns {Promise<boolean>} true if managed printer handled the job
|
|
17
|
+
*/
|
|
18
|
+
export const printOnManagedNetworkPrinter = async ({
|
|
19
|
+
managedPrinter,
|
|
20
|
+
content,
|
|
21
|
+
}) => {
|
|
22
|
+
if (!managedPrinter) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const printerHost = getPrinterHost(managedPrinter);
|
|
27
|
+
const printerPort = normalizePrinterPort(
|
|
28
|
+
managedPrinter?.configs?.[NETWORK_PRINTER_PORT_CONFIG_KEY] ||
|
|
29
|
+
DEFAULT_NETWORK_PRINTER_PORT,
|
|
30
|
+
);
|
|
31
|
+
const printerCodePage =
|
|
32
|
+
managedPrinter?.configs?.[NETWORK_PRINTER_CODE_PAGE_CONFIG_KEY] ||
|
|
33
|
+
DEFAULT_NETWORK_PRINTER_CODE_PAGE;
|
|
34
|
+
|
|
35
|
+
await printOnNetworkPrinter({
|
|
36
|
+
host: printerHost,
|
|
37
|
+
port: printerPort,
|
|
38
|
+
codePage: printerCodePage,
|
|
39
|
+
payload: decodeNetworkPrinterPayload(content, {
|
|
40
|
+
codePage: printerCodePage,
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return true;
|
|
45
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export const COMPANY_DEVICE_CONFIGS_CACHE_TTL_MS = 30 * 1000;
|
|
2
|
+
|
|
3
|
+
export const PRINT_ROUTE_MISSING_DESTINATION =
|
|
4
|
+
'Não foi possível rotear a impressão: nenhum device de impressão configurado.';
|
|
5
|
+
|
|
6
|
+
const normalizeDeviceId = value => String(value || '').trim();
|
|
7
|
+
const normalizeDeviceType = value => String(value || '').trim().toUpperCase();
|
|
8
|
+
|
|
9
|
+
export const PDV_RUNTIME_TYPE = 'PDV';
|
|
10
|
+
export const DISPLAY_RUNTIME_TYPE = 'DISPLAY';
|
|
11
|
+
|
|
12
|
+
export const resolvePrinterDeviceId = printer =>
|
|
13
|
+
normalizeDeviceId(printer?.device || printer?.deviceId);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Device ids whose open spools this runtime should poll/print.
|
|
17
|
+
* Managed network printers expose `device` (not only `deviceId`).
|
|
18
|
+
*/
|
|
19
|
+
export const resolveSpoolDeviceIdsForRuntime = ({
|
|
20
|
+
runtimeDeviceId = '',
|
|
21
|
+
runtimeDeviceType = '',
|
|
22
|
+
isWebRuntime = false,
|
|
23
|
+
managedPrinters = [],
|
|
24
|
+
includeLocalDevice = true,
|
|
25
|
+
} = {}) => {
|
|
26
|
+
const normalizedRuntimeId = normalizeDeviceId(runtimeDeviceId);
|
|
27
|
+
if (!normalizedRuntimeId || isWebRuntime) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const managedIds = (Array.isArray(managedPrinters) ? managedPrinters : [])
|
|
32
|
+
.map(resolvePrinterDeviceId)
|
|
33
|
+
.filter(Boolean);
|
|
34
|
+
|
|
35
|
+
const type = normalizeDeviceType(runtimeDeviceType);
|
|
36
|
+
|
|
37
|
+
if (type === PDV_RUNTIME_TYPE) {
|
|
38
|
+
return Array.from(
|
|
39
|
+
new Set(
|
|
40
|
+
[
|
|
41
|
+
...(includeLocalDevice ? [normalizedRuntimeId] : []),
|
|
42
|
+
...managedIds,
|
|
43
|
+
].filter(Boolean),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (type === DISPLAY_RUNTIME_TYPE) {
|
|
49
|
+
return Array.from(new Set(managedIds));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return [];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const resolvePrintRoutingError = ({
|
|
56
|
+
targetDeviceId = '',
|
|
57
|
+
selectedPrinter = null,
|
|
58
|
+
} = {}) => {
|
|
59
|
+
const resolvedTarget = normalizeDeviceId(
|
|
60
|
+
targetDeviceId || selectedPrinter?.device || selectedPrinter?.deviceId,
|
|
61
|
+
);
|
|
62
|
+
return resolvedTarget ? '' : PRINT_ROUTE_MISSING_DESTINATION;
|
|
63
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for print spool routing / queue handling.
|
|
3
|
+
* Kept small so PrintService stays under the 500-line absolute limit.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const SOCKET_PRINT_POLL_INTERVAL_DISCONNECTED = 10000;
|
|
7
|
+
export const SOCKET_PRINT_POLL_DELAY_CONNECTED = 60000;
|
|
8
|
+
export const SPOOL_ACK_RETRY_DELAY_MS = 10000;
|
|
9
|
+
|
|
10
|
+
export const normalizeDeviceType = value =>
|
|
11
|
+
String(value || '').trim().toUpperCase();
|
|
12
|
+
|
|
13
|
+
export const extractCollectionMembers = data => {
|
|
14
|
+
if (Array.isArray(data)) {
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (Array.isArray(data?.member)) {
|
|
19
|
+
return data.member;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (Array.isArray(data?.['hydra:member'])) {
|
|
23
|
+
return data['hydra:member'];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return [];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const resolveSpoolId = value => {
|
|
30
|
+
if (!value) return null;
|
|
31
|
+
if (typeof value === 'number') return value;
|
|
32
|
+
if (typeof value === 'string') {
|
|
33
|
+
const numericValue = value.replace(/\D/g, '');
|
|
34
|
+
return numericValue ? Number(numericValue) : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
resolveSpoolId(value?.id) ||
|
|
39
|
+
resolveSpoolId(value?.spoolId) ||
|
|
40
|
+
resolveSpoolId(value?.spool) ||
|
|
41
|
+
resolveSpoolId(value?.['@id'])
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Merge open-spool collections from multiple devices, dedupe by id, sort ascending.
|
|
47
|
+
*/
|
|
48
|
+
export const mergeOpenSpoolCollections = spoolCollections => {
|
|
49
|
+
const mergedSpools = [];
|
|
50
|
+
const spoolIds = new Set();
|
|
51
|
+
|
|
52
|
+
(Array.isArray(spoolCollections) ? spoolCollections : [])
|
|
53
|
+
.flat()
|
|
54
|
+
.forEach(item => {
|
|
55
|
+
const spoolId = resolveSpoolId(item);
|
|
56
|
+
const spoolKey = spoolId ? String(spoolId) : JSON.stringify(item);
|
|
57
|
+
|
|
58
|
+
if (!spoolIds.has(spoolKey)) {
|
|
59
|
+
spoolIds.add(spoolKey);
|
|
60
|
+
mergedSpools.push(item);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
mergedSpools.sort((left, right) => {
|
|
65
|
+
const leftId = resolveSpoolId(left) || 0;
|
|
66
|
+
const rightId = resolveSpoolId(right) || 0;
|
|
67
|
+
return leftId - rightId;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return mergedSpools;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Fetch open spools for a list of device ids (parallel).
|
|
75
|
+
* Returns merged, deduped, sorted list. Caller handles store updates / errors.
|
|
76
|
+
*/
|
|
77
|
+
export const fetchOpenSpoolsForDevices = async (api, deviceIds) => {
|
|
78
|
+
const ids = Array.from(
|
|
79
|
+
new Set((Array.isArray(deviceIds) ? deviceIds : []).filter(Boolean)),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (ids.length === 0) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const spoolCollections = await Promise.all(
|
|
87
|
+
ids.map(deviceId =>
|
|
88
|
+
api
|
|
89
|
+
.fetch('spools', {
|
|
90
|
+
params: {
|
|
91
|
+
'device.device': deviceId,
|
|
92
|
+
'status.realStatus': 'open',
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
.then(extractCollectionMembers)
|
|
96
|
+
.catch(() => []),
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return mergeOpenSpoolCollections(spoolCollections);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const resolveRequestedTargetDeviceId = (printJob, fallbackDeviceId) => {
|
|
104
|
+
const raw =
|
|
105
|
+
printJob?.targetDeviceId ||
|
|
106
|
+
printJob?.deviceId ||
|
|
107
|
+
printJob?.device?.device ||
|
|
108
|
+
printJob?.device ||
|
|
109
|
+
fallbackDeviceId;
|
|
110
|
+
return String(raw || '').trim();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const resolveRequestedTargetDeviceTypeValue = printJob =>
|
|
114
|
+
normalizeDeviceType(
|
|
115
|
+
printJob?.targetDeviceType ||
|
|
116
|
+
printJob?.deviceType ||
|
|
117
|
+
printJob?.device?.type ||
|
|
118
|
+
'',
|
|
119
|
+
);
|