@controleonline/ui-customers 1.0.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/.github/agents/developer.agent.md +30 -0
- package/.github/agents/devops.agent.md +30 -0
- package/.github/agents/qa.agent.md +30 -0
- package/.github/agents/security.agent.md +30 -0
- package/.scrutinizer.yml +20 -0
- package/AGENTS.md +50 -0
- package/FUNDING.yml +1 -0
- package/README.md +23 -0
- package/docs/technical/Client-Details-Franquia-Filial.md +18 -0
- package/docs/technical/Client-Details-People-Name-Case.md +103 -0
- package/docs/technical/Cliente-Vendedor-Vinculo-e-Permissoes.md +152 -0
- package/docs/wiki.md +1 -0
- package/package.json +32 -0
- package/scripts/validate-wiki-pointer.cjs +13 -0
- package/src/react/components/tabs/AddressesTab.js +494 -0
- package/src/react/components/tabs/CategoriesTab.js +196 -0
- package/src/react/components/tabs/ClassificationChips.js +494 -0
- package/src/react/components/tabs/ContactTab.js +621 -0
- package/src/react/components/tabs/ContactTab.styles.js +119 -0
- package/src/react/components/tabs/ContractsTab.js +13 -0
- package/src/react/components/tabs/DocumentsTab.js +463 -0
- package/src/react/components/tabs/DocumentsTab.styles.js +133 -0
- package/src/react/components/tabs/EmployeeContactRow.js +83 -0
- package/src/react/components/tabs/EmployeesTab.js +454 -0
- package/src/react/components/tabs/EmployeesTab.styles.js +172 -0
- package/src/react/components/tabs/FiscalTab.js +6 -0
- package/src/react/components/tabs/FranchiseCommissionSection.js +307 -0
- package/src/react/components/tabs/FranchiseLinksManageModal.js +215 -0
- package/src/react/components/tabs/FranchiseLinksTab.js +282 -0
- package/src/react/components/tabs/GeneralTab.js +466 -0
- package/src/react/components/tabs/GeneralTab.styles.js +101 -0
- package/src/react/components/tabs/MediaTab.js +356 -0
- package/src/react/components/tabs/PeopleCategoriesPanel.js +601 -0
- package/src/react/components/tabs/ProductsTab.js +236 -0
- package/src/react/components/tabs/ProductsTab.styles.js +143 -0
- package/src/react/components/tabs/SalesmanCommissionBlock.js +120 -0
- package/src/react/components/tabs/SalesmanManageModal.js +208 -0
- package/src/react/components/tabs/SalesmanTab.js +436 -0
- package/src/react/components/tabs/SalesmanTab.styles.js +4 -0
- package/src/react/components/tabs/UserApiKeyModal.js +90 -0
- package/src/react/components/tabs/UserFormModal.js +301 -0
- package/src/react/components/tabs/UsersTab.js +426 -0
- package/src/react/components/tabs/UsersTab.styles.js +283 -0
- package/src/react/components/tabs/UsersTab.styles.test.js +42 -0
- package/src/react/components/tabs/contactMutationMessage.js +18 -0
- package/src/react/components/tabs/employeeContacts.js +214 -0
- package/src/react/components/tabs/employeeUnlink.js +68 -0
- package/src/react/components/tabs/employeesTabHelpers.js +227 -0
- package/src/react/components/tabs/franchiseCommissionHelpers.js +134 -0
- package/src/react/components/tabs/franchiseLinksTab.helpers.js +247 -0
- package/src/react/components/tabs/generalTabHelpers.js +120 -0
- package/src/react/components/tabs/salesmanTab.helpers.js +114 -0
- package/src/react/components/tabs/salesmanTabHelpers.js +173 -0
- package/src/react/components/tabs/salesmanTabMedia.js +55 -0
- package/src/react/components/tabs/salesmanTabSession.js +39 -0
- package/src/react/components/tabs/useEmployeeUnlink.js +58 -0
- package/src/react/components/tabs/useFranchiseLinksManage.js +215 -0
- package/src/react/components/tabs/useSalesmanManage.js +222 -0
- package/src/react/components/tabs/usersTabHelpers.js +221 -0
- package/src/react/domain/productSupplierFlow.js +37 -0
- package/src/react/index.js +4 -0
- package/src/react/pages/ClientDetailsSkeleton.js +84 -0
- package/src/react/pages/clientDetailsHelpers.js +17 -0
- package/src/react/pages/clients.js +27 -0
- package/src/react/pages/details.js +529 -0
- package/src/react/pages/details.page.styles.js +105 -0
- package/src/react/pages/details.styles.js +15 -0
- package/src/react/pages/employees.js +69 -0
- package/src/react/pages/franchisees.js +30 -0
- package/src/react/pages/prospects.js +21 -0
- package/src/react/pages/provider.js +21 -0
- package/src/react/router/routes.js +104 -0
- package/src/react/styles/details.js +571 -0
- package/src/store/people_categories/index.js +72 -0
- package/src/tests/browser/manager/client-details-create-user.spec.js +321 -0
- package/src/tests/browser/manager/client-details-employee-unlink.spec.js +215 -0
- package/src/tests/browser/manager/client-details-employees-refresh.spec.js +271 -0
- package/src/tests/browser/manager/client-details-enable.spec.js +241 -0
- package/src/tests/browser/manager/client-details-fiscal-tabs.spec.js +410 -0
- package/src/tests/browser/manager/client-details-franchise-commission.spec.js +318 -0
- package/src/tests/browser/manager/client-details-franchise-links.spec.js +365 -0
- package/src/tests/browser/manager/client-details-media.spec.js +405 -0
- package/src/tests/browser/manager/client-details-relationships.spec.js +313 -0
- package/src/tests/browser/manager/client-details-salesman-commission.spec.js +291 -0
- package/src/tests/browser/manager/client-details-salesman-manage.spec.js +448 -0
- package/src/tests/react/components/tabs/contactMutationMessage.test.js +25 -0
- package/src/tests/react/components/tabs/employeeContacts.test.js +266 -0
- package/src/tests/react/components/tabs/employeeUnlink.test.js +67 -0
- package/src/tests/react/components/tabs/employeesTabHelpers.media.test.js +78 -0
- package/src/tests/react/components/tabs/employeesTabHelpers.test.js +64 -0
- package/src/tests/react/components/tabs/fiscalTabContract.test.mjs +29 -0
- package/src/tests/react/components/tabs/franchiseCommissionHelpers.test.js +128 -0
- package/src/tests/react/components/tabs/franchiseLinksTab.helpers.test.mjs +166 -0
- package/src/tests/react/components/tabs/generalTabHelpers.test.js +130 -0
- package/src/tests/react/components/tabs/peopleMediaTabs.test.js +322 -0
- package/src/tests/react/components/tabs/salesmanTab.helpers.test.mjs +75 -0
- package/src/tests/react/components/tabs/salesmanTabHelpers.test.js +142 -0
- package/src/tests/react/components/tabs/salesmanTabHelpers.test.mjs +92 -0
- package/src/tests/react/components/tabs/usersTabHelpers.test.js +59 -0
- package/src/tests/react/domain/productSupplierFlow.test.mjs +55 -0
- package/src/tests/react/pages/clientDetailsHelpers.test.js +98 -0
- package/src/tests/react/pages/employees.test.js +44 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser smoke: client-details franchise commission (ui-customers#10).
|
|
3
|
+
* Covers:
|
|
4
|
+
* 1) superadmin sees editable pencil and can save comission/minimum_comission
|
|
5
|
+
* 2) non-owner non-super sees read-only (no pencil)
|
|
6
|
+
*/
|
|
7
|
+
const {expect, test} = require('playwright/test');
|
|
8
|
+
const {API_ORIGIN} = require('../../../../../../../src/tests/browser/apiOrigin');
|
|
9
|
+
const {version: appVersion} = require('../../../../../../../package.json');
|
|
10
|
+
|
|
11
|
+
const CORS_HEADERS = {
|
|
12
|
+
'access-control-allow-origin': '*',
|
|
13
|
+
'access-control-allow-headers':
|
|
14
|
+
'API-TOKEN, APP-DOMAIN, DEVICE, ACCEPT, CONTENT-TYPE, X-Requested-With',
|
|
15
|
+
'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const collection = (member = [], summary = {}) => ({
|
|
19
|
+
member,
|
|
20
|
+
'hydra:member': member,
|
|
21
|
+
totalItems: member.length,
|
|
22
|
+
'hydra:totalItems': member.length,
|
|
23
|
+
summary,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const jsonHeaders = () => ({
|
|
27
|
+
...CORS_HEADERS,
|
|
28
|
+
'content-type': 'application/ld+json; charset=utf-8',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const textHeaders = () => ({
|
|
32
|
+
...CORS_HEADERS,
|
|
33
|
+
'content-type': 'text/css; charset=utf-8',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const createCompany = ({ownerEnabled = true} = {}) => ({
|
|
37
|
+
id: 3,
|
|
38
|
+
name: 'Franqueadora',
|
|
39
|
+
alias: 'FRANQUEADORA',
|
|
40
|
+
panel_enabled: true,
|
|
41
|
+
enabled: true,
|
|
42
|
+
commercial_enabled: true,
|
|
43
|
+
theme: {
|
|
44
|
+
colors: {
|
|
45
|
+
primary: '#0EA5E9',
|
|
46
|
+
secondary: '#F97316',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
configs: {},
|
|
50
|
+
user: {
|
|
51
|
+
id: 7,
|
|
52
|
+
name: 'Admin',
|
|
53
|
+
owner_enabled: ownerEnabled,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const franchiseePerson = {
|
|
58
|
+
'@id': '/people/42',
|
|
59
|
+
id: 42,
|
|
60
|
+
name: 'FRANQUIA SUL',
|
|
61
|
+
alias: 'FRANQUIA SUL',
|
|
62
|
+
peopleType: 'J',
|
|
63
|
+
enable: true,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const franchiseeLink = {
|
|
67
|
+
'@id': '/people_links/99',
|
|
68
|
+
id: 99,
|
|
69
|
+
linkType: 'franchisee',
|
|
70
|
+
people: {'@id': '/people/42', id: 42},
|
|
71
|
+
company: {'@id': '/people/3', id: 3},
|
|
72
|
+
comission: 10,
|
|
73
|
+
minimum_comission: 2000,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const mockFranchiseCommissionApi = async (
|
|
77
|
+
page,
|
|
78
|
+
{roles = ['ROLE_SUPER'], ownerEnabled = true} = {},
|
|
79
|
+
) => {
|
|
80
|
+
const company = createCompany({ownerEnabled});
|
|
81
|
+
let currentLink = {...franchiseeLink};
|
|
82
|
+
const putBodies = [];
|
|
83
|
+
const peopleLinkGetUrls = [];
|
|
84
|
+
|
|
85
|
+
await page.route(`${API_ORIGIN}/**`, async route => {
|
|
86
|
+
const request = route.request();
|
|
87
|
+
const url = new URL(request.url());
|
|
88
|
+
const pathname = url.pathname.replace(/^\/+/, '');
|
|
89
|
+
const method = request.method().toUpperCase();
|
|
90
|
+
|
|
91
|
+
if (method === 'OPTIONS') {
|
|
92
|
+
return route.fulfill({status: 204, headers: CORS_HEADERS, body: ''});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (pathname === 'themes-colors.css') {
|
|
96
|
+
return route.fulfill({
|
|
97
|
+
status: 200,
|
|
98
|
+
headers: textHeaders(),
|
|
99
|
+
body: ':root { --primary: #0ea5e9; --secondary: #f97316; }',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (pathname === 'runtime/ip') {
|
|
104
|
+
return route.fulfill({
|
|
105
|
+
status: 200,
|
|
106
|
+
headers: jsonHeaders(),
|
|
107
|
+
body: JSON.stringify({ip: '127.0.0.1'}),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (pathname === 'people/company/default') {
|
|
112
|
+
return route.fulfill({
|
|
113
|
+
status: 200,
|
|
114
|
+
headers: jsonHeaders(),
|
|
115
|
+
body: JSON.stringify(company),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (pathname === 'people/companies/my') {
|
|
120
|
+
return route.fulfill({
|
|
121
|
+
status: 200,
|
|
122
|
+
headers: jsonHeaders(),
|
|
123
|
+
body: JSON.stringify(collection([company])),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (pathname === 'people/42') {
|
|
128
|
+
return route.fulfill({
|
|
129
|
+
status: 200,
|
|
130
|
+
headers: jsonHeaders(),
|
|
131
|
+
body: JSON.stringify(franchiseePerson),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (pathname === 'people_links' || pathname.startsWith('people_links/')) {
|
|
136
|
+
if (method === 'GET') {
|
|
137
|
+
peopleLinkGetUrls.push(url.toString());
|
|
138
|
+
const linkTypes = [
|
|
139
|
+
...url.searchParams.getAll('linkType[]'),
|
|
140
|
+
...url.searchParams.getAll('linkType[0]'),
|
|
141
|
+
];
|
|
142
|
+
if (url.searchParams.has('linkType')) {
|
|
143
|
+
return route.fulfill({
|
|
144
|
+
status: 400,
|
|
145
|
+
headers: jsonHeaders(),
|
|
146
|
+
body: JSON.stringify({
|
|
147
|
+
detail: 'Unexpected value for parameter "linkType": expecting "array", got "string".',
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const people = url.searchParams.get('people') || '';
|
|
152
|
+
if (linkTypes.includes('franchisee') || people === '42') {
|
|
153
|
+
return route.fulfill({
|
|
154
|
+
status: 200,
|
|
155
|
+
headers: jsonHeaders(),
|
|
156
|
+
body: JSON.stringify(collection([currentLink])),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return route.fulfill({
|
|
160
|
+
status: 200,
|
|
161
|
+
headers: jsonHeaders(),
|
|
162
|
+
body: JSON.stringify(collection([])),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (method === 'PUT' || method === 'PATCH') {
|
|
167
|
+
const body = await request.postDataJSON().catch(() => ({}));
|
|
168
|
+
putBodies.push(body);
|
|
169
|
+
if (typeof body.comission === 'number') {
|
|
170
|
+
currentLink = {...currentLink, comission: body.comission};
|
|
171
|
+
}
|
|
172
|
+
if (typeof body.minimum_comission === 'number') {
|
|
173
|
+
currentLink = {
|
|
174
|
+
...currentLink,
|
|
175
|
+
minimum_comission: body.minimum_comission,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return route.fulfill({
|
|
179
|
+
status: 200,
|
|
180
|
+
headers: jsonHeaders(),
|
|
181
|
+
body: JSON.stringify(currentLink),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return route.fulfill({
|
|
186
|
+
status: 405,
|
|
187
|
+
headers: jsonHeaders(),
|
|
188
|
+
body: JSON.stringify({detail: 'Method Not Allowed'}),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (pathname === 'menus-people') {
|
|
193
|
+
return route.fulfill({
|
|
194
|
+
status: 200,
|
|
195
|
+
headers: jsonHeaders(),
|
|
196
|
+
body: JSON.stringify({modules: {}}),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (pathname === 'configs/discovery-configs') {
|
|
201
|
+
return route.fulfill({
|
|
202
|
+
status: 200,
|
|
203
|
+
headers: jsonHeaders(),
|
|
204
|
+
body: JSON.stringify({configs: {}}),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return route.fulfill({
|
|
209
|
+
status: 200,
|
|
210
|
+
headers: jsonHeaders(),
|
|
211
|
+
body: JSON.stringify(collection([])),
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
await page.addInitScript(
|
|
216
|
+
({appVersion, roles, ownerEnabled}) => {
|
|
217
|
+
const setLocalStorageItem = (key, value) => {
|
|
218
|
+
try {
|
|
219
|
+
localStorage.setItem(key, value);
|
|
220
|
+
} catch {
|
|
221
|
+
// ignore
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
setLocalStorageItem(
|
|
226
|
+
'session',
|
|
227
|
+
JSON.stringify({
|
|
228
|
+
id: 7,
|
|
229
|
+
people: '/people/7',
|
|
230
|
+
api_key: 'test-api-key',
|
|
231
|
+
active: 1,
|
|
232
|
+
mycompany: 3,
|
|
233
|
+
roles,
|
|
234
|
+
}),
|
|
235
|
+
);
|
|
236
|
+
setLocalStorageItem('config', JSON.stringify({language: 'pt-br'}));
|
|
237
|
+
setLocalStorageItem('app-type', 'MANAGER');
|
|
238
|
+
setLocalStorageItem(
|
|
239
|
+
'device',
|
|
240
|
+
JSON.stringify({
|
|
241
|
+
id: 'web-manager',
|
|
242
|
+
device: 'web-manager',
|
|
243
|
+
type: 'WEB',
|
|
244
|
+
appName: 'Browser Manager',
|
|
245
|
+
appVersion,
|
|
246
|
+
buildNumber: appVersion,
|
|
247
|
+
systemName: 'web',
|
|
248
|
+
systemVersion: 'web',
|
|
249
|
+
deviceType: 'web',
|
|
250
|
+
metadata: {},
|
|
251
|
+
}),
|
|
252
|
+
);
|
|
253
|
+
// seed current company with owner flag for store bootstrap if needed
|
|
254
|
+
setLocalStorageItem(
|
|
255
|
+
'currentCompany',
|
|
256
|
+
JSON.stringify({
|
|
257
|
+
id: 3,
|
|
258
|
+
alias: 'FRANQUEADORA',
|
|
259
|
+
user: {id: 7, owner_enabled: ownerEnabled},
|
|
260
|
+
}),
|
|
261
|
+
);
|
|
262
|
+
},
|
|
263
|
+
{appVersion, roles, ownerEnabled},
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
getPutBodies: () => putBodies,
|
|
268
|
+
getPeopleLinkGetUrls: () => peopleLinkGetUrls,
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
test.describe('client-details franchise commission browser smoke', () => {
|
|
273
|
+
test('superadmin can edit franchise commission and persist', async ({page}) => {
|
|
274
|
+
const api = await mockFranchiseCommissionApi(page, {
|
|
275
|
+
roles: ['ROLE_SUPER'],
|
|
276
|
+
ownerEnabled: false,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
await page.goto('/client-details?clientId=42&contextKey=client');
|
|
280
|
+
await expect(page.getByText(/Comissão da franquia|franchiseCommission/i)).toBeVisible({
|
|
281
|
+
timeout: 15000,
|
|
282
|
+
});
|
|
283
|
+
await expect(page.getByText(/10%/)).toBeVisible();
|
|
284
|
+
await expect(page.getByText(/2000/)).toBeVisible();
|
|
285
|
+
expect(api.getPeopleLinkGetUrls()).toEqual(
|
|
286
|
+
expect.arrayContaining([expect.stringMatching(/linkType(%5B%5D|%5B0%5D)=franchisee/)]),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const editBtn = page.getByTestId('edit-franchise-commission');
|
|
290
|
+
await expect(editBtn).toBeVisible();
|
|
291
|
+
await editBtn.click();
|
|
292
|
+
|
|
293
|
+
await page.getByTestId('franchise-comission-input').fill('15');
|
|
294
|
+
await page.getByTestId('franchise-minimum-comission-input').fill('2500');
|
|
295
|
+
await page.getByTestId('save-franchise-commission').click();
|
|
296
|
+
|
|
297
|
+
await expect.poll(() => api.getPutBodies().length).toBeGreaterThan(0);
|
|
298
|
+
const body = api.getPutBodies()[0];
|
|
299
|
+
expect(body.comission).toBe(15);
|
|
300
|
+
expect(body.minimum_comission).toBe(2500);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('non-owner non-super sees read-only commission (no pencil)', async ({
|
|
304
|
+
page,
|
|
305
|
+
}) => {
|
|
306
|
+
await mockFranchiseCommissionApi(page, {
|
|
307
|
+
roles: ['ROLE_MANAGER'],
|
|
308
|
+
ownerEnabled: false,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
await page.goto('/client-details?clientId=42&contextKey=client');
|
|
312
|
+
await expect(page.getByText(/Comissão da franquia|franchiseCommission/i)).toBeVisible({
|
|
313
|
+
timeout: 15000,
|
|
314
|
+
});
|
|
315
|
+
await expect(page.getByText(/10%/)).toBeVisible();
|
|
316
|
+
await expect(page.getByTestId('edit-franchise-commission')).toHaveCount(0);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
const {expect, test} = require('playwright/test');
|
|
2
|
+
const {API_ORIGIN} = require('../../../../../../../src/tests/browser/apiOrigin');
|
|
3
|
+
const {version: appVersion} = require('../../../../../../../package.json');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Browser smoke — client-details aba Franquia/Filial (PJ → PJ).
|
|
7
|
+
* Cobre critérios de aceite de app-community#453 / task-453:
|
|
8
|
+
* - abrir client-details de PJ no MANAGER → aba Franquia/Filial
|
|
9
|
+
* - listagem de vínculos franchisee/filial
|
|
10
|
+
* - botão + (vincular) e ações de edição/remoção (MANAGER)
|
|
11
|
+
* - PF não entra na listagem
|
|
12
|
+
* - console/pageerror limpo
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const CORS_HEADERS = {
|
|
16
|
+
'access-control-allow-origin': '*',
|
|
17
|
+
'access-control-allow-headers':
|
|
18
|
+
'API-TOKEN, APP-DOMAIN, DEVICE, ACCEPT, CONTENT-TYPE, X-Requested-With',
|
|
19
|
+
'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const jsonHeaders = () => ({
|
|
23
|
+
...CORS_HEADERS,
|
|
24
|
+
'content-type': 'application/ld+json; charset=utf-8',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const textHeaders = () => ({
|
|
28
|
+
...CORS_HEADERS,
|
|
29
|
+
'content-type': 'text/css; charset=utf-8',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const collection = member => ({
|
|
33
|
+
member,
|
|
34
|
+
'hydra:member': member,
|
|
35
|
+
totalItems: member.length,
|
|
36
|
+
'hydra:totalItems': member.length,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const themeColors = {
|
|
40
|
+
primary: '#1C8FBD',
|
|
41
|
+
secondary: '#0169D9',
|
|
42
|
+
background: '#F3F7FB',
|
|
43
|
+
text: '#0F172A',
|
|
44
|
+
textSecondary: '#6C7787',
|
|
45
|
+
border: '#D7E1EC',
|
|
46
|
+
headerBackground: '#F3F7FB',
|
|
47
|
+
headerBorder: '#D7E1EC',
|
|
48
|
+
headerText: '#0F172A',
|
|
49
|
+
cardBackground: '#FFFFFF',
|
|
50
|
+
cardBorder: '#D7E1EC',
|
|
51
|
+
cardText: '#0F172A',
|
|
52
|
+
cardIcon: '#1C8FBD',
|
|
53
|
+
cardShadow: '#1C8FBD',
|
|
54
|
+
listItemBackground: '#F1F8FB',
|
|
55
|
+
listItemText: '#0F172A',
|
|
56
|
+
listItemSubtitleText: '#6C7787',
|
|
57
|
+
loadingSpinner: '#1C8FBD',
|
|
58
|
+
buttonBackground: '#1C8FBD',
|
|
59
|
+
buttonBorder: '#1C8FBD',
|
|
60
|
+
buttonIcon: '#FFFFFF',
|
|
61
|
+
buttonText: '#FFFFFF',
|
|
62
|
+
buttonShadow: '#1C8FBD',
|
|
63
|
+
buttonBackgroundSecondary: '#FFFFFF',
|
|
64
|
+
buttonBorderSecondary: '#D7E1EC',
|
|
65
|
+
buttonIconSecondary: '#0169D9',
|
|
66
|
+
buttonTextSecondary: '#0169D9',
|
|
67
|
+
buttonDisabledBackground: '#CBD5E1',
|
|
68
|
+
tabBarBackground: '#FFFFFF',
|
|
69
|
+
tabBarBorder: '#D7E1EC',
|
|
70
|
+
menuSelectedBorder: '#1C8FBD',
|
|
71
|
+
menuSelectedText: '#1C8FBD',
|
|
72
|
+
pageBackground: '#F3F7FB',
|
|
73
|
+
modalOverlay: 'rgba(15,23,42,0.45)',
|
|
74
|
+
modalBackground: '#FFFFFF',
|
|
75
|
+
modalText: '#0F172A',
|
|
76
|
+
modalShadow: '#1C8FBD',
|
|
77
|
+
inputBackground: '#FFFFFF',
|
|
78
|
+
inputBorder: '#D7E1EC',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const themeCss = `:root { ${Object.entries(themeColors)
|
|
82
|
+
.map(([key, value]) => `--${key}: ${value};`)
|
|
83
|
+
.join(' ')} }`;
|
|
84
|
+
|
|
85
|
+
const company = {
|
|
86
|
+
'@id': '/people/2',
|
|
87
|
+
id: 2,
|
|
88
|
+
name: 'CONTROLE ONLINE',
|
|
89
|
+
alias: 'CONTROLE ONLINE',
|
|
90
|
+
panel_enabled: true,
|
|
91
|
+
enabled: true,
|
|
92
|
+
commercial_enabled: true,
|
|
93
|
+
theme: {colors: themeColors},
|
|
94
|
+
configs: {},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const clientPj = {
|
|
98
|
+
'@id': '/people/29',
|
|
99
|
+
id: 29,
|
|
100
|
+
name: 'LAVE GO FRANQUIAS LTDA',
|
|
101
|
+
alias: 'LAVE-GO',
|
|
102
|
+
peopleType: 'J',
|
|
103
|
+
enable: true,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const franchiseePj = {
|
|
107
|
+
'@id': '/people/40',
|
|
108
|
+
id: 40,
|
|
109
|
+
name: 'FRANQUIA SUL LTDA',
|
|
110
|
+
alias: 'FRANQUIA-SUL',
|
|
111
|
+
peopleType: 'J',
|
|
112
|
+
enable: true,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const filialPj = {
|
|
116
|
+
'@id': '/people/41',
|
|
117
|
+
id: 41,
|
|
118
|
+
name: 'FILIAL NORTE LTDA',
|
|
119
|
+
alias: 'FILIAL-NORTE',
|
|
120
|
+
peopleType: 'J',
|
|
121
|
+
enable: true,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const pfPerson = {
|
|
125
|
+
'@id': '/people/25',
|
|
126
|
+
id: 25,
|
|
127
|
+
name: 'PESSOA FISICA TESTE',
|
|
128
|
+
alias: 'PF-TESTE',
|
|
129
|
+
peopleType: 'F',
|
|
130
|
+
enable: true,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const franchiseLink = {
|
|
134
|
+
'@id': '/people_links/501',
|
|
135
|
+
id: 501,
|
|
136
|
+
company: clientPj,
|
|
137
|
+
people: franchiseePj,
|
|
138
|
+
linkType: 'franchisee',
|
|
139
|
+
enable: true,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const filialLink = {
|
|
143
|
+
'@id': '/people_links/502',
|
|
144
|
+
id: 502,
|
|
145
|
+
company: clientPj,
|
|
146
|
+
people: filialPj,
|
|
147
|
+
linkType: 'filial',
|
|
148
|
+
enable: true,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const pfLinkShouldBeFiltered = {
|
|
152
|
+
'@id': '/people_links/503',
|
|
153
|
+
id: 503,
|
|
154
|
+
company: clientPj,
|
|
155
|
+
people: pfPerson,
|
|
156
|
+
linkType: 'franchisee',
|
|
157
|
+
enable: true,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const mockManagerApi = async page => {
|
|
161
|
+
const peopleLinkQueries = [];
|
|
162
|
+
const pageErrors = [];
|
|
163
|
+
|
|
164
|
+
page.on('pageerror', err => {
|
|
165
|
+
pageErrors.push(String(err?.message || err));
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
await page.route(`${API_ORIGIN}/**`, async route => {
|
|
169
|
+
const request = route.request();
|
|
170
|
+
const url = new URL(request.url());
|
|
171
|
+
const pathname = url.pathname.replace(/^\/+/, '');
|
|
172
|
+
|
|
173
|
+
if (request.method().toUpperCase() === 'OPTIONS') {
|
|
174
|
+
return route.fulfill({status: 204, headers: CORS_HEADERS, body: ''});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (pathname === 'themes-colors.css') {
|
|
178
|
+
return route.fulfill({
|
|
179
|
+
status: 200,
|
|
180
|
+
headers: textHeaders(),
|
|
181
|
+
body: themeCss,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (pathname === 'runtime/ip') {
|
|
186
|
+
return route.fulfill({
|
|
187
|
+
status: 200,
|
|
188
|
+
headers: jsonHeaders(),
|
|
189
|
+
body: JSON.stringify({ip: '127.0.0.1'}),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (pathname === 'people/companies/my') {
|
|
194
|
+
return route.fulfill({
|
|
195
|
+
status: 200,
|
|
196
|
+
headers: jsonHeaders(),
|
|
197
|
+
body: JSON.stringify(collection([company])),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (pathname === 'people/company/default') {
|
|
202
|
+
return route.fulfill({
|
|
203
|
+
status: 200,
|
|
204
|
+
headers: jsonHeaders(),
|
|
205
|
+
body: JSON.stringify(company),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (pathname === 'people/29') {
|
|
210
|
+
return route.fulfill({
|
|
211
|
+
status: 200,
|
|
212
|
+
headers: jsonHeaders(),
|
|
213
|
+
body: JSON.stringify(clientPj),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (pathname === 'people' || pathname.startsWith('people?')) {
|
|
218
|
+
// candidates for picker (PJ only)
|
|
219
|
+
return route.fulfill({
|
|
220
|
+
status: 200,
|
|
221
|
+
headers: jsonHeaders(),
|
|
222
|
+
body: JSON.stringify(collection([franchiseePj, filialPj, pfPerson])),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (pathname === 'people_links' || pathname.startsWith('people_links')) {
|
|
227
|
+
const query = Object.fromEntries(url.searchParams);
|
|
228
|
+
peopleLinkQueries.push(query);
|
|
229
|
+
|
|
230
|
+
if (request.method().toUpperCase() === 'POST') {
|
|
231
|
+
return route.fulfill({
|
|
232
|
+
status: 201,
|
|
233
|
+
headers: jsonHeaders(),
|
|
234
|
+
body: JSON.stringify({
|
|
235
|
+
...franchiseLink,
|
|
236
|
+
id: 599,
|
|
237
|
+
'@id': '/people_links/599',
|
|
238
|
+
}),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (request.method().toUpperCase() === 'PUT' || request.method().toUpperCase() === 'PATCH') {
|
|
243
|
+
return route.fulfill({
|
|
244
|
+
status: 200,
|
|
245
|
+
headers: jsonHeaders(),
|
|
246
|
+
body: JSON.stringify(franchiseLink),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (request.method().toUpperCase() === 'DELETE') {
|
|
251
|
+
return route.fulfill({
|
|
252
|
+
status: 204,
|
|
253
|
+
headers: CORS_HEADERS,
|
|
254
|
+
body: '',
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// GET list — include PF link to assert helper filters it out
|
|
259
|
+
return route.fulfill({
|
|
260
|
+
status: 200,
|
|
261
|
+
headers: jsonHeaders(),
|
|
262
|
+
body: JSON.stringify(
|
|
263
|
+
collection([franchiseLink, filialLink, pfLinkShouldBeFiltered]),
|
|
264
|
+
),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return route.fulfill({
|
|
269
|
+
status: 200,
|
|
270
|
+
headers: jsonHeaders(),
|
|
271
|
+
body: JSON.stringify(collection([])),
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
await page.addInitScript(
|
|
276
|
+
({version}) => {
|
|
277
|
+
localStorage.setItem(
|
|
278
|
+
'session',
|
|
279
|
+
JSON.stringify({
|
|
280
|
+
id: 19,
|
|
281
|
+
people: '/people/19',
|
|
282
|
+
api_key: 'test-api-key',
|
|
283
|
+
active: 1,
|
|
284
|
+
mycompany: 2,
|
|
285
|
+
roles: ['ROLE_SUPER'],
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
288
|
+
localStorage.setItem('app-type', 'MANAGER');
|
|
289
|
+
localStorage.setItem('config', JSON.stringify({language: 'pt-br'}));
|
|
290
|
+
localStorage.setItem(
|
|
291
|
+
'device',
|
|
292
|
+
JSON.stringify({
|
|
293
|
+
id: 'web-manager',
|
|
294
|
+
device: 'web-manager',
|
|
295
|
+
type: 'WEB',
|
|
296
|
+
appName: 'Browser Manager',
|
|
297
|
+
appVersion: version,
|
|
298
|
+
buildNumber: version,
|
|
299
|
+
systemName: 'web',
|
|
300
|
+
systemVersion: 'web',
|
|
301
|
+
deviceType: 'web',
|
|
302
|
+
metadata: {},
|
|
303
|
+
}),
|
|
304
|
+
);
|
|
305
|
+
},
|
|
306
|
+
{version: appVersion},
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
return {peopleLinkQueries, pageErrors};
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
test.describe('manager client-details franchise/filial links', () => {
|
|
313
|
+
test('lists PJ franchisee/filial links and exposes MANAGER CRUD controls', async ({
|
|
314
|
+
page,
|
|
315
|
+
}) => {
|
|
316
|
+
const {peopleLinkQueries, pageErrors} = await mockManagerApi(page);
|
|
317
|
+
|
|
318
|
+
await page.goto('/client-details?clientId=29&contextKey=client');
|
|
319
|
+
|
|
320
|
+
await expect(
|
|
321
|
+
page.getByText('LAVE GO FRANQUIAS LTDA', {exact: true}).first(),
|
|
322
|
+
).toBeVisible({timeout: 15000});
|
|
323
|
+
|
|
324
|
+
// Open Franquia/Filial tab
|
|
325
|
+
const franchiseTab = page.getByText(/Franquia\/Filial|Franquia/i).first();
|
|
326
|
+
await expect(franchiseTab).toBeVisible({timeout: 10000});
|
|
327
|
+
await franchiseTab.click();
|
|
328
|
+
|
|
329
|
+
// Listagem: PJ franchisee + filial; PF filtrado
|
|
330
|
+
await expect(page.getByText('FRANQUIA SUL LTDA', {exact: true})).toBeVisible({
|
|
331
|
+
timeout: 10000,
|
|
332
|
+
});
|
|
333
|
+
await expect(page.getByText('FILIAL NORTE LTDA', {exact: true})).toBeVisible();
|
|
334
|
+
await expect(page.getByText('PESSOA FISICA TESTE', {exact: true})).toHaveCount(0);
|
|
335
|
+
|
|
336
|
+
// MANAGER: botão vincular + ações de edição/remoção
|
|
337
|
+
await expect(page.getByTestId('franchise-links-add-btn')).toBeVisible();
|
|
338
|
+
await expect(page.getByTestId('franchise-links-edit-501')).toBeVisible();
|
|
339
|
+
await expect(page.getByTestId('franchise-links-delete-501')).toBeVisible();
|
|
340
|
+
await expect(page.getByTestId('franchise-link-row-501')).toBeVisible();
|
|
341
|
+
await expect(page.getByTestId('franchise-link-row-502')).toBeVisible();
|
|
342
|
+
|
|
343
|
+
// Abrir modal de vincular
|
|
344
|
+
await page.getByTestId('franchise-links-add-btn').click();
|
|
345
|
+
await expect(page.getByTestId('franchise-link-people-picker')).toBeVisible({
|
|
346
|
+
timeout: 5000,
|
|
347
|
+
});
|
|
348
|
+
await expect(page.getByTestId('franchise-link-type-picker')).toBeVisible();
|
|
349
|
+
await expect(page.getByTestId('franchise-link-comission-input')).toBeVisible();
|
|
350
|
+
await expect(
|
|
351
|
+
page.getByTestId('franchise-link-minimum-comission-input'),
|
|
352
|
+
).toBeVisible();
|
|
353
|
+
await expect(page.getByTestId('franchise-link-save-btn')).toBeVisible();
|
|
354
|
+
|
|
355
|
+
// people_links foi consultado com company do cliente
|
|
356
|
+
await expect.poll(() => peopleLinkQueries.length).toBeGreaterThanOrEqual(1);
|
|
357
|
+
expect(
|
|
358
|
+
peopleLinkQueries.some(
|
|
359
|
+
q => String(q.company || '') === '29' || String(q.company || '').includes('29'),
|
|
360
|
+
),
|
|
361
|
+
).toBe(true);
|
|
362
|
+
|
|
363
|
+
expect(pageErrors).toEqual([]);
|
|
364
|
+
});
|
|
365
|
+
});
|