@webitel/api-services 26.8.76 → 26.8.78
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/api/clients/workingConditions/__tests__/workingConditions.spec.ts +162 -0
- package/src/api/clients/workingConditions/workingConditions.ts +31 -46
- package/src/validations/index.ts +2 -0
- package/src/validations/pauseTemplate/__tests__/pauseTemplate.validations.spec.ts +85 -0
- package/src/validations/pauseTemplate/pauseTemplate.validations.ts +51 -0
- package/src/validations/workingCondition/__tests__/workingCondition.validations.spec.ts +81 -0
- package/src/validations/workingCondition/workingCondition.validations.ts +25 -0
- package/types/.tsbuildinfo +1 -1
- package/types/api/clients/pauseTemplates/pauseTemplates.d.ts +1 -6
- package/types/api/clients/workingConditions/workingConditions.d.ts +1 -6
- package/types/validations/index.d.ts +2 -0
- package/types/validations/pauseTemplate/pauseTemplate.validations.d.ts +30 -0
- package/types/validations/workingCondition/workingCondition.validations.d.ts +19 -0
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { workingConditionSchema } from '../workingCondition.validations';
|
|
4
|
+
|
|
5
|
+
const valid = {
|
|
6
|
+
name: 'FTE 1',
|
|
7
|
+
pauseTemplate: {
|
|
8
|
+
id: '1',
|
|
9
|
+
name: '30-15-15',
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe('workingConditionSchema', () => {
|
|
14
|
+
it('accepts a condition with only a name and a pause template', () => {
|
|
15
|
+
expect(workingConditionSchema.safeParse(valid).success).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('requires a pause template', () => {
|
|
19
|
+
expect(
|
|
20
|
+
workingConditionSchema.safeParse({
|
|
21
|
+
name: 'FTE 1',
|
|
22
|
+
}).success,
|
|
23
|
+
).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('keeps the workday duration optional and clearable', () => {
|
|
27
|
+
expect(
|
|
28
|
+
workingConditionSchema.safeParse({
|
|
29
|
+
...valid,
|
|
30
|
+
workdayHours: null,
|
|
31
|
+
}).success,
|
|
32
|
+
).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('rejects a name longer than 250 symbols', () => {
|
|
36
|
+
expect(
|
|
37
|
+
workingConditionSchema.safeParse({
|
|
38
|
+
...valid,
|
|
39
|
+
name: 'a'.repeat(251),
|
|
40
|
+
}).success,
|
|
41
|
+
).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it.each([
|
|
45
|
+
[
|
|
46
|
+
'workdayHours',
|
|
47
|
+
25,
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
'workdaysPerMonth',
|
|
51
|
+
32,
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
'vacation',
|
|
55
|
+
366,
|
|
56
|
+
],
|
|
57
|
+
[
|
|
58
|
+
'sickLeaves',
|
|
59
|
+
366,
|
|
60
|
+
],
|
|
61
|
+
[
|
|
62
|
+
'daysOff',
|
|
63
|
+
366,
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
'pauseDuration',
|
|
67
|
+
1441,
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
'workdayHours',
|
|
71
|
+
-1,
|
|
72
|
+
],
|
|
73
|
+
])('rejects %s = %j', (field, value) => {
|
|
74
|
+
expect(
|
|
75
|
+
workingConditionSchema.safeParse({
|
|
76
|
+
...valid,
|
|
77
|
+
[field]: value,
|
|
78
|
+
}).success,
|
|
79
|
+
).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WfmWorkingCondition } from '@webitel/api-services/gen/models';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
filledLookupSchema,
|
|
6
|
+
flexibleLookupSchema,
|
|
7
|
+
} from '../_shared/lookup.validations';
|
|
8
|
+
import type { ZodShape } from '../types';
|
|
9
|
+
|
|
10
|
+
/** [Claude] an optional whole number the user may clear (`wt-input-number` emits `null`) */
|
|
11
|
+
const optionalCountSchema = (max: number) =>
|
|
12
|
+
z.number().int().min(0).max(max).nullish();
|
|
13
|
+
|
|
14
|
+
export const workingConditionSchema = z.object<ZodShape<WfmWorkingCondition>>({
|
|
15
|
+
name: z.string().min(1).max(250),
|
|
16
|
+
description: z.string().optional().default(''),
|
|
17
|
+
workdayHours: optionalCountSchema(24),
|
|
18
|
+
workdaysPerMonth: optionalCountSchema(31),
|
|
19
|
+
vacation: optionalCountSchema(365),
|
|
20
|
+
sickLeaves: optionalCountSchema(365),
|
|
21
|
+
daysOff: optionalCountSchema(365),
|
|
22
|
+
pauseDuration: optionalCountSchema(1440),
|
|
23
|
+
pauseTemplate: filledLookupSchema,
|
|
24
|
+
shiftTemplate: flexibleLookupSchema.nullish(),
|
|
25
|
+
});
|