@webitel/api-services 26.8.76 → 26.8.77
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 +1 -1
- package/src/api/clients/pauseTemplates/__tests__/pauseTemplates.spec.ts +144 -0
- package/src/api/clients/pauseTemplates/pauseTemplates.ts +33 -71
- package/src/validations/index.ts +1 -0
- package/src/validations/pauseTemplate/__tests__/pauseTemplate.validations.spec.ts +85 -0
- package/src/validations/pauseTemplate/pauseTemplate.validations.ts +51 -0
- package/types/.tsbuildinfo +1 -1
- package/types/api/clients/pauseTemplates/pauseTemplates.d.ts +1 -6
- package/types/validations/index.d.ts +1 -0
- package/types/validations/pauseTemplate/pauseTemplate.validations.d.ts +30 -0
package/package.json
CHANGED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const wireItem = {
|
|
4
|
+
id: '1',
|
|
5
|
+
name: 'lunch',
|
|
6
|
+
description: 'desc',
|
|
7
|
+
causes: [
|
|
8
|
+
{
|
|
9
|
+
cause: {
|
|
10
|
+
id: '7',
|
|
11
|
+
name: 'Coffee',
|
|
12
|
+
},
|
|
13
|
+
duration: '15',
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
domain_id: '1',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const service = {
|
|
20
|
+
pauseTemplateServiceSearchPauseTemplate: vi.fn(() =>
|
|
21
|
+
Promise.resolve({
|
|
22
|
+
data: {
|
|
23
|
+
items: [
|
|
24
|
+
wireItem,
|
|
25
|
+
],
|
|
26
|
+
next: true,
|
|
27
|
+
},
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
pauseTemplateServiceReadPauseTemplate: vi.fn(() =>
|
|
31
|
+
Promise.resolve({
|
|
32
|
+
data: {
|
|
33
|
+
item: wireItem,
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
pauseTemplateServiceCreatePauseTemplate: vi.fn(() =>
|
|
38
|
+
Promise.resolve({
|
|
39
|
+
data: {
|
|
40
|
+
item: wireItem,
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
),
|
|
44
|
+
pauseTemplateServiceUpdatePauseTemplate: vi.fn(() =>
|
|
45
|
+
Promise.resolve({
|
|
46
|
+
data: {
|
|
47
|
+
item: wireItem,
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
vi.mock('../../../../gen-wire', async (importOriginal) => ({
|
|
54
|
+
...(await importOriginal<Record<string, unknown>>()),
|
|
55
|
+
getPauseTemplateService: () => service,
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
const { PauseTemplatesAPI } = await import('../pauseTemplates');
|
|
59
|
+
|
|
60
|
+
const formItem = {
|
|
61
|
+
id: '1',
|
|
62
|
+
name: 'lunch',
|
|
63
|
+
description: 'desc',
|
|
64
|
+
causes: [
|
|
65
|
+
{
|
|
66
|
+
cause: {
|
|
67
|
+
id: '7',
|
|
68
|
+
name: 'Coffee',
|
|
69
|
+
},
|
|
70
|
+
duration: 15,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
duration: 30,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
domainId: '1',
|
|
77
|
+
_dirty: true,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
describe('PauseTemplatesAPI', () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
vi.clearAllMocks();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('getList sends the search as `q`', async () => {
|
|
86
|
+
const { items, next } = await PauseTemplatesAPI.getList({
|
|
87
|
+
search: 'lun',
|
|
88
|
+
page: 2,
|
|
89
|
+
size: 10,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(
|
|
93
|
+
service.pauseTemplateServiceSearchPauseTemplate,
|
|
94
|
+
).toHaveBeenCalledWith(
|
|
95
|
+
expect.objectContaining({
|
|
96
|
+
q: 'lun*',
|
|
97
|
+
page: 2,
|
|
98
|
+
size: 10,
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
expect(items[0].domainId).toBe('1');
|
|
102
|
+
expect(next).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('get unwraps the `item` envelope and keeps causes nested', async () => {
|
|
106
|
+
const item = await PauseTemplatesAPI.get({
|
|
107
|
+
itemId: '1',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(item.name).toBe('lunch');
|
|
111
|
+
expect(item.causes).toEqual(wireItem.causes);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('add wraps the item and sends only the form fields', async () => {
|
|
115
|
+
await PauseTemplatesAPI.add({
|
|
116
|
+
itemInstance: formItem,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const [body] =
|
|
120
|
+
service.pauseTemplateServiceCreatePauseTemplate.mock.calls[0];
|
|
121
|
+
expect(body).toEqual({
|
|
122
|
+
item: {
|
|
123
|
+
name: 'lunch',
|
|
124
|
+
description: 'desc',
|
|
125
|
+
causes: formItem.causes,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('update sends the id separately and drops it from the item', async () => {
|
|
131
|
+
await PauseTemplatesAPI.update({
|
|
132
|
+
itemId: '1',
|
|
133
|
+
itemInstance: formItem,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const [id, body] =
|
|
137
|
+
service.pauseTemplateServiceUpdatePauseTemplate.mock.calls[0];
|
|
138
|
+
expect(id).toBe('1');
|
|
139
|
+
expect(body.item).not.toHaveProperty('id');
|
|
140
|
+
expect(body.item).not.toHaveProperty('domain_id');
|
|
141
|
+
expect(body.item).not.toHaveProperty('_dirty');
|
|
142
|
+
expect(body.item.causes).toEqual(formItem.causes);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { getShallowFieldsToSendFromZodSchema } from '@webitel/api-services/gen/utils';
|
|
2
|
+
import { pauseTemplateSchema } from '@webitel/api-services/validations';
|
|
3
|
+
import {
|
|
4
|
+
getPauseTemplateService,
|
|
5
|
+
PauseTemplateServiceSearchPauseTemplateQueryParams,
|
|
6
|
+
} from '../../../gen-wire';
|
|
3
7
|
import { getDefaultGetListResponse, getDefaultGetParams } from '../../defaults';
|
|
4
8
|
import {
|
|
5
9
|
applyTransform,
|
|
6
10
|
camelToSnake,
|
|
7
11
|
merge,
|
|
8
12
|
notify,
|
|
9
|
-
|
|
13
|
+
sanitizeToWire,
|
|
10
14
|
snakeToCamel,
|
|
15
|
+
starToSearch,
|
|
11
16
|
} from '../../transformers';
|
|
12
17
|
import type {
|
|
13
18
|
AddItemParams,
|
|
@@ -17,71 +22,36 @@ import type {
|
|
|
17
22
|
UpdateItemParams,
|
|
18
23
|
} from '../_shared/types';
|
|
19
24
|
|
|
20
|
-
const fieldsToSend =
|
|
21
|
-
'name',
|
|
22
|
-
'description',
|
|
23
|
-
'causes',
|
|
24
|
-
'domainId',
|
|
25
|
-
'createdAt',
|
|
26
|
-
'createdBy',
|
|
27
|
-
'updatedAt',
|
|
28
|
-
'updatedBy',
|
|
29
|
-
];
|
|
25
|
+
const fieldsToSend = getShallowFieldsToSendFromZodSchema(pauseTemplateSchema);
|
|
30
26
|
|
|
31
27
|
/**
|
|
32
|
-
* WFM services wrap both request and response payloads in an `item` envelope
|
|
33
|
-
* and nest each pause cause under a `cause` key. The form binds a flat
|
|
34
|
-
* `{ id, name, duration }`.
|
|
28
|
+
* [Claude] WFM services wrap both request and response payloads in an `item` envelope.
|
|
35
29
|
*/
|
|
36
|
-
const itemResponseHandler = (response: ApiParams) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
item.causes = item.causes?.map((cause: ApiParams) => ({
|
|
42
|
-
id: cause.cause?.id,
|
|
43
|
-
name: cause.cause?.name,
|
|
44
|
-
duration: cause?.duration,
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
return item;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const preRequestHandler = (item: ApiParams) => {
|
|
51
|
-
const copy = deepCopy(item);
|
|
52
|
-
copy.causes = copy.causes.map((cause: ApiParams) => {
|
|
53
|
-
if (!cause.name && !cause.id) return cause;
|
|
54
|
-
return {
|
|
55
|
-
cause: {
|
|
56
|
-
id: cause?.id,
|
|
57
|
-
name: cause?.name,
|
|
58
|
-
},
|
|
59
|
-
duration: cause.duration,
|
|
60
|
-
};
|
|
61
|
-
});
|
|
62
|
-
return copy;
|
|
63
|
-
};
|
|
30
|
+
const itemResponseHandler = (response: ApiParams) => ({
|
|
31
|
+
...response.item,
|
|
32
|
+
});
|
|
64
33
|
|
|
65
34
|
const getPauseTemplatesList = async (params: ApiParams) => {
|
|
66
|
-
const
|
|
67
|
-
search: q,
|
|
68
|
-
page,
|
|
69
|
-
size,
|
|
70
|
-
sort,
|
|
71
|
-
fields,
|
|
72
|
-
} = applyTransform(params, [
|
|
35
|
+
const requestParams = applyTransform(params, [
|
|
73
36
|
merge(getDefaultGetParams()),
|
|
37
|
+
starToSearch('search'),
|
|
38
|
+
(params: ApiParams) => ({
|
|
39
|
+
...params,
|
|
40
|
+
q: params.q ?? params.search,
|
|
41
|
+
}),
|
|
42
|
+
sanitizeToWire(
|
|
43
|
+
getShallowFieldsToSendFromZodSchema(
|
|
44
|
+
PauseTemplateServiceSearchPauseTemplateQueryParams,
|
|
45
|
+
),
|
|
46
|
+
),
|
|
47
|
+
camelToSnake(),
|
|
74
48
|
]);
|
|
75
49
|
|
|
76
50
|
try {
|
|
77
51
|
const response =
|
|
78
|
-
await getPauseTemplateService().pauseTemplateServiceSearchPauseTemplate(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
size,
|
|
82
|
-
sort,
|
|
83
|
-
fields,
|
|
84
|
-
});
|
|
52
|
+
await getPauseTemplateService().pauseTemplateServiceSearchPauseTemplate(
|
|
53
|
+
requestParams,
|
|
54
|
+
);
|
|
85
55
|
const { items, next } = applyTransform(response.data, [
|
|
86
56
|
snakeToCamel(),
|
|
87
57
|
merge(getDefaultGetListResponse()),
|
|
@@ -116,16 +86,13 @@ const getPauseTemplate = async ({ itemId: id }: GetItemParams) => {
|
|
|
116
86
|
|
|
117
87
|
const addPauseTemplate = async ({ itemInstance }: AddItemParams) => {
|
|
118
88
|
const item = applyTransform(itemInstance, [
|
|
119
|
-
|
|
89
|
+
sanitizeToWire(fieldsToSend),
|
|
120
90
|
camelToSnake(),
|
|
121
|
-
preRequestHandler,
|
|
122
91
|
]);
|
|
123
92
|
try {
|
|
124
93
|
const response =
|
|
125
94
|
await getPauseTemplateService().pauseTemplateServiceCreatePauseTemplate({
|
|
126
|
-
item
|
|
127
|
-
...item,
|
|
128
|
-
},
|
|
95
|
+
item,
|
|
129
96
|
});
|
|
130
97
|
return applyTransform(response.data, [
|
|
131
98
|
snakeToCamel(),
|
|
@@ -143,18 +110,15 @@ const updatePauseTemplate = async ({
|
|
|
143
110
|
itemId: id,
|
|
144
111
|
}: UpdateItemParams) => {
|
|
145
112
|
const item = applyTransform(itemInstance, [
|
|
146
|
-
|
|
113
|
+
sanitizeToWire(fieldsToSend),
|
|
147
114
|
camelToSnake(),
|
|
148
|
-
preRequestHandler,
|
|
149
115
|
]);
|
|
150
116
|
try {
|
|
151
117
|
const response =
|
|
152
118
|
await getPauseTemplateService().pauseTemplateServiceUpdatePauseTemplate(
|
|
153
119
|
String(id),
|
|
154
120
|
{
|
|
155
|
-
item
|
|
156
|
-
...item,
|
|
157
|
-
},
|
|
121
|
+
item,
|
|
158
122
|
},
|
|
159
123
|
);
|
|
160
124
|
return applyTransform(response.data, [
|
|
@@ -182,9 +146,7 @@ const deletePauseTemplate = async ({ id }: DeleteItemParams) => {
|
|
|
182
146
|
}
|
|
183
147
|
};
|
|
184
148
|
|
|
185
|
-
const getPauseTemplatesLookup = (
|
|
186
|
-
params: Parameters<typeof getPauseTemplatesList>[0],
|
|
187
|
-
) =>
|
|
149
|
+
const getPauseTemplatesLookup = (params: ApiParams) =>
|
|
188
150
|
getPauseTemplatesList({
|
|
189
151
|
...params,
|
|
190
152
|
fields: params.fields || [
|
package/src/validations/index.ts
CHANGED
|
@@ -34,6 +34,7 @@ export * from './device/device.validations';
|
|
|
34
34
|
export * from './license/licenseImport.validations';
|
|
35
35
|
export * from './OAuth/OAuth.validations';
|
|
36
36
|
export * from './onlineSkill/onlineSkill.validations';
|
|
37
|
+
export * from './pauseTemplate/pauseTemplate.validations';
|
|
37
38
|
export * from './queue/queue.rules';
|
|
38
39
|
export * from './queue/queue.validations';
|
|
39
40
|
export * from './queue/queueAmd.validations';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { pauseTemplateSchema } from '../pauseTemplate.validations';
|
|
4
|
+
|
|
5
|
+
describe('pauseTemplateSchema', () => {
|
|
6
|
+
it('fills a new template with one 30 min row and no cause', () => {
|
|
7
|
+
expect(pauseTemplateSchema.parse({}).causes).toEqual([
|
|
8
|
+
{
|
|
9
|
+
duration: 30,
|
|
10
|
+
},
|
|
11
|
+
]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('accepts a row without a pause cause', () => {
|
|
15
|
+
const result = pauseTemplateSchema.safeParse({
|
|
16
|
+
name: 'lunch',
|
|
17
|
+
causes: [
|
|
18
|
+
{
|
|
19
|
+
duration: 15,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
expect(result.success).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('rejects an empty name', () => {
|
|
27
|
+
expect(
|
|
28
|
+
pauseTemplateSchema.safeParse({
|
|
29
|
+
name: '',
|
|
30
|
+
}).success,
|
|
31
|
+
).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('rejects a name longer than 250 symbols', () => {
|
|
35
|
+
expect(
|
|
36
|
+
pauseTemplateSchema.safeParse({
|
|
37
|
+
name: 'a'.repeat(251),
|
|
38
|
+
}).success,
|
|
39
|
+
).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('rejects a template without rows', () => {
|
|
43
|
+
expect(
|
|
44
|
+
pauseTemplateSchema.safeParse({
|
|
45
|
+
name: 'lunch',
|
|
46
|
+
causes: [],
|
|
47
|
+
}).success,
|
|
48
|
+
).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('accepts a duration the API returns as a string', () => {
|
|
52
|
+
expect(
|
|
53
|
+
pauseTemplateSchema.safeParse({
|
|
54
|
+
name: 'lunch',
|
|
55
|
+
causes: [
|
|
56
|
+
{
|
|
57
|
+
cause: null,
|
|
58
|
+
duration: '15',
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
}).success,
|
|
62
|
+
).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it.each([
|
|
66
|
+
0,
|
|
67
|
+
'0',
|
|
68
|
+
null,
|
|
69
|
+
])('rejects a row duration of %j', (duration) => {
|
|
70
|
+
expect(
|
|
71
|
+
pauseTemplateSchema.safeParse({
|
|
72
|
+
name: 'lunch',
|
|
73
|
+
causes: [
|
|
74
|
+
{
|
|
75
|
+
cause: {
|
|
76
|
+
id: '1',
|
|
77
|
+
name: 'Coffee',
|
|
78
|
+
},
|
|
79
|
+
duration,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
}).success,
|
|
83
|
+
).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
WfmPauseTemplate,
|
|
3
|
+
WfmPauseTemplateCause,
|
|
4
|
+
} from '@webitel/api-services/gen/models';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
import { flexibleLookupSchema } from '../_shared/lookup.validations';
|
|
8
|
+
import type { ZodShape } from '../types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* [Claude] the API returns the duration (minutes) as an int64 string. An emptied
|
|
12
|
+
* field becomes `undefined` (not `NaN` as with `z.coerce`), so the form shows
|
|
13
|
+
* "required" instead of zod's raw "expected number, received NaN".
|
|
14
|
+
*/
|
|
15
|
+
const durationMinutesSchema = z.preprocess(
|
|
16
|
+
(value) =>
|
|
17
|
+
value === '' || value === null || value === undefined
|
|
18
|
+
? undefined
|
|
19
|
+
: Number(value),
|
|
20
|
+
z.number().int().min(1),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* [Claude] the pause cause is optional ("Not selected"), the duration is not.
|
|
25
|
+
*/
|
|
26
|
+
export const pauseTemplateCauseSchema = z.object({
|
|
27
|
+
cause: flexibleLookupSchema.nullish(),
|
|
28
|
+
duration: durationMinutesSchema,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* [Claude] a new template row: no pause cause ("Not selected"), 30 minutes.
|
|
33
|
+
* Shared by the schema default and the form's "add row" action.
|
|
34
|
+
*/
|
|
35
|
+
export const getDefaultPauseTemplateCause = (): WfmPauseTemplateCause & {
|
|
36
|
+
duration: string;
|
|
37
|
+
} => ({
|
|
38
|
+
duration: '30',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const pauseTemplateSchema = z.object<ZodShape<WfmPauseTemplate>>({
|
|
42
|
+
name: z.string().min(1).max(250).default(''),
|
|
43
|
+
description: z.string().optional().default(''),
|
|
44
|
+
causes: z
|
|
45
|
+
.array(pauseTemplateCauseSchema)
|
|
46
|
+
.min(1)
|
|
47
|
+
// [Claude] `.prefault`, not `.default`: the API-shaped row (duration as a string) goes through the schema
|
|
48
|
+
.prefault(() => [
|
|
49
|
+
getDefaultPauseTemplateCause(),
|
|
50
|
+
]),
|
|
51
|
+
});
|