@webitel/api-services 26.8.74 → 26.8.76
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/emails/emails.ts +1 -1
- package/src/api/clients/imClients/imClients.ts +1 -1
- package/src/api/clients/phones/phones.ts +1 -1
- package/src/api/clients/shiftTemplates/__tests__/shiftTemplates.spec.ts +145 -0
- package/src/api/clients/shiftTemplates/shiftTemplates.ts +34 -45
- package/src/api/clients/variables/variables.ts +1 -1
- package/src/validations/index.ts +1 -0
- package/src/validations/shiftTemplate/__tests__/shiftTemplate.validations.spec.ts +146 -0
- package/src/validations/shiftTemplate/shiftTemplate.validations.ts +38 -0
- package/types/.tsbuildinfo +1 -1
- package/types/api/clients/shiftTemplates/shiftTemplates.d.ts +1 -6
- package/types/validations/index.d.ts +1 -0
- package/types/validations/shiftTemplate/shiftTemplate.validations.d.ts +23 -0
package/package.json
CHANGED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const wireItem = {
|
|
4
|
+
id: '1',
|
|
5
|
+
name: 'Day',
|
|
6
|
+
description: 'desc',
|
|
7
|
+
times: [
|
|
8
|
+
{
|
|
9
|
+
start: 540,
|
|
10
|
+
end: 1200,
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
domain_id: '1',
|
|
14
|
+
created_at: '1',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const service = {
|
|
18
|
+
shiftTemplateServiceSearchShiftTemplate: vi.fn(() =>
|
|
19
|
+
Promise.resolve({
|
|
20
|
+
data: {
|
|
21
|
+
items: [
|
|
22
|
+
wireItem,
|
|
23
|
+
],
|
|
24
|
+
next: true,
|
|
25
|
+
},
|
|
26
|
+
}),
|
|
27
|
+
),
|
|
28
|
+
shiftTemplateServiceReadShiftTemplate: vi.fn(() =>
|
|
29
|
+
Promise.resolve({
|
|
30
|
+
data: {
|
|
31
|
+
item: wireItem,
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
35
|
+
shiftTemplateServiceCreateShiftTemplate: vi.fn(() =>
|
|
36
|
+
Promise.resolve({
|
|
37
|
+
data: {
|
|
38
|
+
item: wireItem,
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
shiftTemplateServiceUpdateShiftTemplate: vi.fn(() =>
|
|
43
|
+
Promise.resolve({
|
|
44
|
+
data: {
|
|
45
|
+
item: wireItem,
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
vi.mock('../../../../gen-wire', async (importOriginal) => ({
|
|
52
|
+
...(await importOriginal<Record<string, unknown>>()),
|
|
53
|
+
getShiftTemplateService: () => service,
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
const { ShiftTemplatesAPI } = await import('../shiftTemplates');
|
|
57
|
+
|
|
58
|
+
const formItem = {
|
|
59
|
+
id: '1',
|
|
60
|
+
name: 'Day',
|
|
61
|
+
description: 'desc',
|
|
62
|
+
times: [
|
|
63
|
+
{
|
|
64
|
+
start: 540,
|
|
65
|
+
end: 1200,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
domainId: '1',
|
|
69
|
+
createdAt: '1',
|
|
70
|
+
_dirty: true,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
describe('ShiftTemplatesAPI', () => {
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
vi.clearAllMocks();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('getList sends the search as `q`', async () => {
|
|
79
|
+
const { items, next } = await ShiftTemplatesAPI.getList({
|
|
80
|
+
search: 'day',
|
|
81
|
+
page: 2,
|
|
82
|
+
size: 10,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(
|
|
86
|
+
service.shiftTemplateServiceSearchShiftTemplate,
|
|
87
|
+
).toHaveBeenCalledWith(
|
|
88
|
+
expect.objectContaining({
|
|
89
|
+
q: 'day*',
|
|
90
|
+
page: 2,
|
|
91
|
+
size: 10,
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
expect(items[0].name).toBe('Day');
|
|
95
|
+
expect(next).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('get and update unwrap the `item` envelope', async () => {
|
|
99
|
+
const item = await ShiftTemplatesAPI.get({
|
|
100
|
+
itemId: '1',
|
|
101
|
+
});
|
|
102
|
+
expect(item.times).toEqual(wireItem.times);
|
|
103
|
+
|
|
104
|
+
const updated = await ShiftTemplatesAPI.update({
|
|
105
|
+
itemId: '1',
|
|
106
|
+
itemInstance: formItem,
|
|
107
|
+
});
|
|
108
|
+
expect(updated.name).toBe('Day');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('add wraps the item and sends only the form fields', async () => {
|
|
112
|
+
await ShiftTemplatesAPI.add({
|
|
113
|
+
itemInstance: formItem,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const [body] =
|
|
117
|
+
service.shiftTemplateServiceCreateShiftTemplate.mock.calls[0];
|
|
118
|
+
expect(body).toEqual({
|
|
119
|
+
item: {
|
|
120
|
+
name: 'Day',
|
|
121
|
+
description: 'desc',
|
|
122
|
+
times: [
|
|
123
|
+
{
|
|
124
|
+
start: 540,
|
|
125
|
+
end: 1200,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('update sends the id separately and drops it from the item', async () => {
|
|
133
|
+
await ShiftTemplatesAPI.update({
|
|
134
|
+
itemId: '1',
|
|
135
|
+
itemInstance: formItem,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const [id, body] =
|
|
139
|
+
service.shiftTemplateServiceUpdateShiftTemplate.mock.calls[0];
|
|
140
|
+
expect(id).toBe('1');
|
|
141
|
+
expect(body.item).not.toHaveProperty('id');
|
|
142
|
+
expect(body.item).not.toHaveProperty('domain_id');
|
|
143
|
+
expect(body.item).not.toHaveProperty('_dirty');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getShallowFieldsToSendFromZodSchema } from '@webitel/api-services/gen/utils';
|
|
2
|
+
import { shiftTemplateSchema } from '@webitel/api-services/validations';
|
|
3
|
+
import {
|
|
4
|
+
getShiftTemplateService,
|
|
5
|
+
ShiftTemplateServiceSearchShiftTemplateQueryParams,
|
|
6
|
+
} from '../../../gen-wire';
|
|
2
7
|
import { getDefaultGetListResponse, getDefaultGetParams } from '../../defaults';
|
|
3
8
|
import {
|
|
4
9
|
applyTransform,
|
|
5
10
|
camelToSnake,
|
|
6
11
|
merge,
|
|
7
12
|
notify,
|
|
8
|
-
|
|
13
|
+
sanitizeToWire,
|
|
9
14
|
snakeToCamel,
|
|
15
|
+
starToSearch,
|
|
10
16
|
} from '../../transformers';
|
|
11
17
|
import type {
|
|
12
18
|
AddItemParams,
|
|
@@ -16,48 +22,36 @@ import type {
|
|
|
16
22
|
UpdateItemParams,
|
|
17
23
|
} from '../_shared/types';
|
|
18
24
|
|
|
19
|
-
const fieldsToSend =
|
|
20
|
-
'name',
|
|
21
|
-
'description',
|
|
22
|
-
'times',
|
|
23
|
-
];
|
|
25
|
+
const fieldsToSend = getShallowFieldsToSendFromZodSchema(shiftTemplateSchema);
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
|
-
* WFM services wrap both request and response payloads in an `item` envelope.
|
|
28
|
+
* [Claude] WFM services wrap both request and response payloads in an `item` envelope.
|
|
27
29
|
*/
|
|
28
|
-
const itemResponseHandler = (response: ApiParams) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
copy.times = copy.times?.map((time: ApiParams) => ({
|
|
34
|
-
...time,
|
|
35
|
-
duration: time.end - time.start,
|
|
36
|
-
}));
|
|
37
|
-
|
|
38
|
-
return copy;
|
|
39
|
-
};
|
|
30
|
+
const itemResponseHandler = (response: ApiParams) => ({
|
|
31
|
+
...response.item,
|
|
32
|
+
});
|
|
40
33
|
|
|
41
34
|
const getShiftTemplatesList = async (params: ApiParams) => {
|
|
42
|
-
const
|
|
43
|
-
search: q,
|
|
44
|
-
page,
|
|
45
|
-
size,
|
|
46
|
-
sort,
|
|
47
|
-
fields,
|
|
48
|
-
} = applyTransform(params, [
|
|
35
|
+
const requestParams = applyTransform(params, [
|
|
49
36
|
merge(getDefaultGetParams()),
|
|
37
|
+
starToSearch('search'),
|
|
38
|
+
(params: ApiParams) => ({
|
|
39
|
+
...params,
|
|
40
|
+
q: params.q ?? params.search,
|
|
41
|
+
}),
|
|
42
|
+
sanitizeToWire(
|
|
43
|
+
getShallowFieldsToSendFromZodSchema(
|
|
44
|
+
ShiftTemplateServiceSearchShiftTemplateQueryParams,
|
|
45
|
+
),
|
|
46
|
+
),
|
|
47
|
+
camelToSnake(),
|
|
50
48
|
]);
|
|
51
49
|
|
|
52
50
|
try {
|
|
53
51
|
const response =
|
|
54
|
-
await getShiftTemplateService().shiftTemplateServiceSearchShiftTemplate(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
size,
|
|
58
|
-
sort,
|
|
59
|
-
fields,
|
|
60
|
-
});
|
|
52
|
+
await getShiftTemplateService().shiftTemplateServiceSearchShiftTemplate(
|
|
53
|
+
requestParams,
|
|
54
|
+
);
|
|
61
55
|
const { items, next } = applyTransform(response.data, [
|
|
62
56
|
snakeToCamel(),
|
|
63
57
|
merge(getDefaultGetListResponse()),
|
|
@@ -92,15 +86,13 @@ const getShiftTemplate = async ({ itemId: id }: GetItemParams) => {
|
|
|
92
86
|
|
|
93
87
|
const addShiftTemplate = async ({ itemInstance }: AddItemParams) => {
|
|
94
88
|
const item = applyTransform(itemInstance, [
|
|
95
|
-
|
|
89
|
+
sanitizeToWire(fieldsToSend),
|
|
96
90
|
camelToSnake(),
|
|
97
91
|
]);
|
|
98
92
|
try {
|
|
99
93
|
const response =
|
|
100
94
|
await getShiftTemplateService().shiftTemplateServiceCreateShiftTemplate({
|
|
101
|
-
item
|
|
102
|
-
...item,
|
|
103
|
-
},
|
|
95
|
+
item,
|
|
104
96
|
});
|
|
105
97
|
return applyTransform(response.data, [
|
|
106
98
|
snakeToCamel(),
|
|
@@ -118,7 +110,7 @@ const updateShiftTemplate = async ({
|
|
|
118
110
|
itemId: id,
|
|
119
111
|
}: UpdateItemParams) => {
|
|
120
112
|
const item = applyTransform(itemInstance, [
|
|
121
|
-
|
|
113
|
+
sanitizeToWire(fieldsToSend),
|
|
122
114
|
camelToSnake(),
|
|
123
115
|
]);
|
|
124
116
|
try {
|
|
@@ -126,13 +118,12 @@ const updateShiftTemplate = async ({
|
|
|
126
118
|
await getShiftTemplateService().shiftTemplateServiceUpdateShiftTemplate(
|
|
127
119
|
String(id),
|
|
128
120
|
{
|
|
129
|
-
item
|
|
130
|
-
...item,
|
|
131
|
-
},
|
|
121
|
+
item,
|
|
132
122
|
},
|
|
133
123
|
);
|
|
134
124
|
return applyTransform(response.data, [
|
|
135
125
|
snakeToCamel(),
|
|
126
|
+
itemResponseHandler,
|
|
136
127
|
]);
|
|
137
128
|
} catch (err) {
|
|
138
129
|
throw applyTransform(err, [
|
|
@@ -155,9 +146,7 @@ const deleteShiftTemplate = async ({ id }: DeleteItemParams) => {
|
|
|
155
146
|
}
|
|
156
147
|
};
|
|
157
148
|
|
|
158
|
-
const getShiftTemplatesLookup = (
|
|
159
|
-
params: Parameters<typeof getShiftTemplatesList>[0],
|
|
160
|
-
) =>
|
|
149
|
+
const getShiftTemplatesLookup = (params: ApiParams) =>
|
|
161
150
|
getShiftTemplatesList({
|
|
162
151
|
...params,
|
|
163
152
|
fields: params.fields || [
|
package/src/validations/index.ts
CHANGED
|
@@ -49,6 +49,7 @@ export * from './resource/resource.validations';
|
|
|
49
49
|
export * from './resourceDisplay/resourceDisplay.validations';
|
|
50
50
|
export * from './resourceGroup/resourceGroup.validations';
|
|
51
51
|
export * from './resourceInGroup/resourceInGroup.validations';
|
|
52
|
+
export * from './shiftTemplate/shiftTemplate.validations';
|
|
52
53
|
export * from './sla/sla.validations';
|
|
53
54
|
export * from './slaCondition/slaCondition.validations';
|
|
54
55
|
export * from './team/team.validations';
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { getDefaultsFromZodSchema } from '@webitel/api-services/gen/utils';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getDefaultShiftTemplateTime,
|
|
6
|
+
getShiftTemplateTimeRangeErrors,
|
|
7
|
+
shiftTemplateSchema,
|
|
8
|
+
} from '../shiftTemplate.validations';
|
|
9
|
+
|
|
10
|
+
const valid = {
|
|
11
|
+
name: 'Day',
|
|
12
|
+
times: [
|
|
13
|
+
{
|
|
14
|
+
start: 540,
|
|
15
|
+
end: 1200,
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe('shiftTemplateSchema', () => {
|
|
21
|
+
it('accepts a template with a name and a time range', () => {
|
|
22
|
+
expect(shiftTemplateSchema.safeParse(valid).success).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('fills a new template with one 9:00–18:00 row', () => {
|
|
26
|
+
expect(getDefaultShiftTemplateTime()).toEqual({
|
|
27
|
+
start: 540,
|
|
28
|
+
end: 1080,
|
|
29
|
+
});
|
|
30
|
+
expect(getDefaultsFromZodSchema(shiftTemplateSchema, {})).toEqual(
|
|
31
|
+
expect.objectContaining({
|
|
32
|
+
times: [
|
|
33
|
+
getDefaultShiftTemplateTime(),
|
|
34
|
+
],
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('accepts a new template once the name is filled', () => {
|
|
40
|
+
const defaults = getDefaultsFromZodSchema(
|
|
41
|
+
shiftTemplateSchema,
|
|
42
|
+
{},
|
|
43
|
+
) as object;
|
|
44
|
+
|
|
45
|
+
expect(
|
|
46
|
+
shiftTemplateSchema.safeParse({
|
|
47
|
+
...defaults,
|
|
48
|
+
name: 'Day',
|
|
49
|
+
}).success,
|
|
50
|
+
).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('requires a name', () => {
|
|
54
|
+
expect(
|
|
55
|
+
shiftTemplateSchema.safeParse({
|
|
56
|
+
...valid,
|
|
57
|
+
name: '',
|
|
58
|
+
}).success,
|
|
59
|
+
).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('rejects a name longer than 250 symbols', () => {
|
|
63
|
+
expect(
|
|
64
|
+
shiftTemplateSchema.safeParse({
|
|
65
|
+
...valid,
|
|
66
|
+
name: 'a'.repeat(251),
|
|
67
|
+
}).success,
|
|
68
|
+
).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('requires at least one time range', () => {
|
|
72
|
+
expect(
|
|
73
|
+
shiftTemplateSchema.safeParse({
|
|
74
|
+
...valid,
|
|
75
|
+
times: [],
|
|
76
|
+
}).success,
|
|
77
|
+
).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it.each([
|
|
81
|
+
[
|
|
82
|
+
'end before start',
|
|
83
|
+
{
|
|
84
|
+
start: 600,
|
|
85
|
+
end: 540,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
[
|
|
89
|
+
'zero duration',
|
|
90
|
+
{
|
|
91
|
+
start: 600,
|
|
92
|
+
end: 600,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
[
|
|
96
|
+
'end out of the day',
|
|
97
|
+
{
|
|
98
|
+
start: 600,
|
|
99
|
+
end: 1440,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
[
|
|
103
|
+
'empty start',
|
|
104
|
+
{
|
|
105
|
+
end: 600,
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
])('rejects a row with %s', (_, time) => {
|
|
109
|
+
expect(
|
|
110
|
+
shiftTemplateSchema.safeParse({
|
|
111
|
+
...valid,
|
|
112
|
+
times: [
|
|
113
|
+
time,
|
|
114
|
+
],
|
|
115
|
+
}).success,
|
|
116
|
+
).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('getShiftTemplateTimeRangeErrors', () => {
|
|
121
|
+
it('marks both ends of a row whose start is not before its end', () => {
|
|
122
|
+
expect(
|
|
123
|
+
getShiftTemplateTimeRangeErrors([
|
|
124
|
+
{
|
|
125
|
+
start: 540,
|
|
126
|
+
end: 1200,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
start: 600,
|
|
130
|
+
end: 540,
|
|
131
|
+
},
|
|
132
|
+
]),
|
|
133
|
+
).toEqual([
|
|
134
|
+
{
|
|
135
|
+
index: 1,
|
|
136
|
+
prop: 'start',
|
|
137
|
+
key: 'timerangeStartLessThanEnd',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
index: 1,
|
|
141
|
+
prop: 'end',
|
|
142
|
+
key: 'timerangeStartLessThanEnd',
|
|
143
|
+
},
|
|
144
|
+
]);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { WfmShiftTemplate } from '@webitel/api-services/gen/models';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getTimeRangeErrors,
|
|
6
|
+
type TimeRange,
|
|
7
|
+
timeRangeSchema,
|
|
8
|
+
} from '../_shared/timeRange.validations';
|
|
9
|
+
import type { ZodShape } from '../types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* [Claude] each row is a part of the shift within a day: start < end, both in minutes
|
|
13
|
+
*/
|
|
14
|
+
const shiftTemplateTimesSchema = z.array(timeRangeSchema).min(1);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* [Claude] row errors for `useTimeRangesValidation`: regle marks a row as a plain field,
|
|
18
|
+
* so the custom range issues are read from the schema directly
|
|
19
|
+
*/
|
|
20
|
+
export const getShiftTemplateTimeRangeErrors = (items: unknown) =>
|
|
21
|
+
getTimeRangeErrors(shiftTemplateTimesSchema, items);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* [Claude] a new template row: 9:00–18:00, 9 hours (minutes of the day).
|
|
25
|
+
* Shared by the schema default and the form's "add row" action.
|
|
26
|
+
*/
|
|
27
|
+
export const getDefaultShiftTemplateTime = (): TimeRange => ({
|
|
28
|
+
start: 9 * 60,
|
|
29
|
+
end: 18 * 60,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const shiftTemplateSchema = z.object<ZodShape<WfmShiftTemplate>>({
|
|
33
|
+
name: z.string().min(1).max(250).default(''),
|
|
34
|
+
description: z.string().optional().default(''),
|
|
35
|
+
times: shiftTemplateTimesSchema.default(() => [
|
|
36
|
+
getDefaultShiftTemplateTime(),
|
|
37
|
+
]),
|
|
38
|
+
});
|