hl-core 0.0.10-beta.81 → 0.0.10-beta.83
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/api/base.api.ts +8 -0
- package/api/interceptors.ts +26 -0
- package/components/Dialog/Dialog.vue +33 -0
- package/components/Form/FormTextSection.vue +2 -2
- package/components/Layout/Header.vue +31 -1
- package/components/Menu/MenuHover.vue +2 -2
- package/components/Pages/Anketa.vue +92 -21
- package/components/Pages/ContragentForm.vue +1 -1
- package/components/Pages/Documents.vue +37 -14
- package/components/Pages/MemberForm.vue +44 -13
- package/components/Pages/ProductConditions.vue +45 -5
- package/composables/classes.ts +662 -1108
- package/composables/index.ts +89 -38
- package/layouts/default.vue +1 -1
- package/nuxt.config.ts +19 -2
- package/package.json +6 -14
- package/store/form.store.ts +2 -2
- package/store/legalEntity.store.ts +4 -5
- package/store/member.store.ts +3 -1
- package/types/enum.ts +1 -0
- package/types/index.ts +14 -1
package/api/base.api.ts
CHANGED
|
@@ -1227,6 +1227,14 @@ export class ApiClass {
|
|
|
1227
1227
|
params: { processInstanceId },
|
|
1228
1228
|
});
|
|
1229
1229
|
},
|
|
1230
|
+
checkContractDsPension: async (processInstanceId: string | number) => {
|
|
1231
|
+
return await this.axiosCall<Types.CheckContractDsPensionResponse>({
|
|
1232
|
+
method: Methods.GET,
|
|
1233
|
+
baseURL: getStrValuePerEnv('efoBaseApi'),
|
|
1234
|
+
url: `${this.pensionannuityNew.base}/SetContractDsPensionAsync`,
|
|
1235
|
+
params: { processInstanceId },
|
|
1236
|
+
});
|
|
1237
|
+
},
|
|
1230
1238
|
};
|
|
1231
1239
|
|
|
1232
1240
|
externalServices = {
|
package/api/interceptors.ts
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
import { AxiosError, type AxiosInstance, type InternalAxiosRequestConfig, isAxiosError } from 'axios';
|
|
2
|
+
|
|
3
|
+
const INTERCEPTOR_HANDLED_STATUSES = new Set([401, 403, 404, 413, 500]);
|
|
4
|
+
|
|
5
|
+
function markErrorAsHandled(err: unknown, status?: number): void {
|
|
6
|
+
if (typeof status === 'number' && !INTERCEPTOR_HANDLED_STATUSES.has(status)) return;
|
|
7
|
+
|
|
8
|
+
const axiosError = err as { config?: Record<string, unknown>; handledByInterceptor?: boolean; handledStatus?: number };
|
|
9
|
+
axiosError.handledByInterceptor = true;
|
|
10
|
+
if (typeof status === 'number') {
|
|
11
|
+
axiosError.handledStatus = status;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (axiosError.config) {
|
|
15
|
+
axiosError.config.handledByInterceptor = true;
|
|
16
|
+
if (typeof status === 'number') {
|
|
17
|
+
axiosError.config.handledStatus = status;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
2
21
|
/**
|
|
3
22
|
* Обновляет baseURL в зависимости от текущего хоста
|
|
4
23
|
*/
|
|
@@ -118,6 +137,7 @@ export default function setupInterceptors(axios: AxiosInstance): void {
|
|
|
118
137
|
|
|
119
138
|
// 1) Не-Axios ошибка (например, thrown Error)
|
|
120
139
|
if (!isAxiosError(err)) {
|
|
140
|
+
markErrorAsHandled(err);
|
|
121
141
|
showToaster('error', appContextStore.t('toaster.unknownError'), 5000);
|
|
122
142
|
return Promise.reject(err);
|
|
123
143
|
}
|
|
@@ -126,6 +146,7 @@ export default function setupInterceptors(axios: AxiosInstance): void {
|
|
|
126
146
|
|
|
127
147
|
// 2) Нет ответа вообще (timeout, сеть, CORS)
|
|
128
148
|
if (!response) {
|
|
149
|
+
markErrorAsHandled(err);
|
|
129
150
|
const isTimeout = code === 'ECONNABORTED' || /timeout/i.test(String(message));
|
|
130
151
|
showToaster('error', isTimeout ? appContextStore.t('toaster.timeout') : appContextStore.t('toaster.networkError'), 5000);
|
|
131
152
|
return Promise.reject(err);
|
|
@@ -137,12 +158,14 @@ export default function setupInterceptors(axios: AxiosInstance): void {
|
|
|
137
158
|
|
|
138
159
|
// 3) 401 — простая обработка, показываем ошибку
|
|
139
160
|
if (status === 401) {
|
|
161
|
+
markErrorAsHandled(err, status);
|
|
140
162
|
showToaster('error', appContextStore.t('error.401'), 5000);
|
|
141
163
|
return Promise.reject(err);
|
|
142
164
|
}
|
|
143
165
|
|
|
144
166
|
// 4) 403 — вытаскиваем только path без query/hash
|
|
145
167
|
if (status === 403) {
|
|
168
|
+
markErrorAsHandled(err, status);
|
|
146
169
|
try {
|
|
147
170
|
const rawUrl = response.config?.url ?? '';
|
|
148
171
|
const urlObj = new URL(rawUrl, window.location.origin);
|
|
@@ -157,18 +180,21 @@ export default function setupInterceptors(axios: AxiosInstance): void {
|
|
|
157
180
|
|
|
158
181
|
// 5) 404
|
|
159
182
|
if (status === 404 && !isSilent) {
|
|
183
|
+
markErrorAsHandled(err, status);
|
|
160
184
|
showToaster('error', appContextStore.t('error.404'), 5000);
|
|
161
185
|
return Promise.reject(err);
|
|
162
186
|
}
|
|
163
187
|
|
|
164
188
|
// 6) 413
|
|
165
189
|
if (status === 413) {
|
|
190
|
+
markErrorAsHandled(err, status);
|
|
166
191
|
showToaster('error', appContextStore.t('error.exceedUploadLimitFile'), 5000);
|
|
167
192
|
return Promise.reject(err);
|
|
168
193
|
}
|
|
169
194
|
|
|
170
195
|
// 7) 500 — явная обработка
|
|
171
196
|
if (status === 500) {
|
|
197
|
+
markErrorAsHandled(err, status);
|
|
172
198
|
const errorMessage = extractErrorMessage(data, status, appContextStore);
|
|
173
199
|
showToaster('error', String(errorMessage), 5000);
|
|
174
200
|
return Promise.reject(err);
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-dialog class="base-dialog" :model-value="Boolean(modelValue)" @update:modelValue="$emit('update:modelValue', $event)" :persistent="persistent">
|
|
3
3
|
<v-card class="self-center w-full sm:w-4/4 md:w-2/3 lg:w-[35%] xl:w-[500px] rounded-lg !px-[15px] !py-[30px] sm:!p-[36px]" :class="innerClass">
|
|
4
|
+
<v-btn
|
|
5
|
+
v-if="showCloseButton"
|
|
6
|
+
icon="mdi-close"
|
|
7
|
+
variant="text"
|
|
8
|
+
rounded="0"
|
|
9
|
+
:size="smAndDown ? 'small' : 'default'"
|
|
10
|
+
:ripple="false"
|
|
11
|
+
class="no-hover position-absolute right-0 top-0"
|
|
12
|
+
@click="$emit('update:modelValue', false)"
|
|
13
|
+
/>
|
|
4
14
|
<div class="flex sm:flex-row flex-col place-items-center sm:place-items-start">
|
|
5
15
|
<div class="h-20 w-20 place-items-start pt-1">
|
|
6
16
|
<div class="bg-[#F7F7F7] h-24 w-24 sm:h-16 sm:w-16 rounded grid place-items-center invisible sm:visible">
|
|
@@ -79,8 +89,23 @@ export default defineComponent({
|
|
|
79
89
|
type: String,
|
|
80
90
|
default: '',
|
|
81
91
|
},
|
|
92
|
+
showCloseButton: {
|
|
93
|
+
type: Boolean,
|
|
94
|
+
default: false,
|
|
95
|
+
},
|
|
82
96
|
},
|
|
83
97
|
emits: ['update:modelValue', 'yes', 'no'],
|
|
98
|
+
setup() {
|
|
99
|
+
const display = useDisplayInfo();
|
|
100
|
+
|
|
101
|
+
const smAndDown = computed(() => {
|
|
102
|
+
return display.smAndDown.value;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
smAndDown,
|
|
107
|
+
};
|
|
108
|
+
},
|
|
84
109
|
});
|
|
85
110
|
</script>
|
|
86
111
|
<style>
|
|
@@ -90,6 +115,14 @@ export default defineComponent({
|
|
|
90
115
|
.base-dialog .v-card-subtitle {
|
|
91
116
|
font-size: 14px;
|
|
92
117
|
}
|
|
118
|
+
|
|
119
|
+
.no-hover:hover {
|
|
120
|
+
background: transparent !important;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.no-hover .v-btn__overlay {
|
|
124
|
+
opacity: 0 !important;
|
|
125
|
+
}
|
|
93
126
|
@media (max-width: 640px) {
|
|
94
127
|
.base-dialog .v-card-title {
|
|
95
128
|
margin-top: 10px;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="flex flex-col justify-between rounded-lg transition-all" :class="[$styles.whiteBg]">
|
|
3
|
-
<span v-if="title" class="p-4" :class="[titleClass ?? $styles.textTitle]">{{ title }}</span>
|
|
4
|
-
<span v-if="subtitle" class="p-4 text-[#99A3B3] border-t-[1px]" :class="[subtitleClass ?? $styles.textSimple]">{{ subtitle }}</span>
|
|
3
|
+
<span v-if="title" class="p-4 text-section-label" :class="[titleClass ?? $styles.textTitle]">{{ title }}</span>
|
|
4
|
+
<span v-if="subtitle" class="p-4 text-[#99A3B3] border-t-[1px] text-section-label" :class="[subtitleClass ?? $styles.textSimple]">{{ subtitle }}</span>
|
|
5
5
|
<slot></slot>
|
|
6
6
|
</div>
|
|
7
7
|
</template>
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<header class="relative w-full min-h-[70px] text-center font-medium align-middle flex items-center border-b-[1px]" :class="[$styles.blueBgLight, $styles.textSimple]">
|
|
3
3
|
<i v-if="hasBack" @click="$emit('onBack')" class="absolute left-5 mdi text-xl cursor-pointer transition-all" :class="[backIcon, backIconAnim]"></i>
|
|
4
|
-
<span class="mx-10">
|
|
4
|
+
<span class="mx-10">
|
|
5
|
+
<v-tooltip v-if="hasMore" :text="$dataStore.isSupport() ? `Нажатий: ${homeClickCount}` : ''" :disabled="!$dataStore.isSupport()">
|
|
6
|
+
<template #activator="{ props: tooltipProps }">
|
|
7
|
+
<v-btn v-bind="tooltipProps" variant="text" size="32" :ripple="false" class="absolute z-10" @click="onHomeClick" @mouseenter="onHomeHover">
|
|
8
|
+
<img src="../../../efo/public/favicon.svg" class="w-6 h-6" alt="home" />
|
|
9
|
+
</v-btn>
|
|
10
|
+
</template>
|
|
11
|
+
</v-tooltip>
|
|
12
|
+
{{ title }}
|
|
13
|
+
</span>
|
|
5
14
|
<i
|
|
6
15
|
v-if="hasInfo && infoItems && infoItems.length && infoItems.filter(i => (typeof i.show === 'boolean' ? i.show : true)).length"
|
|
7
16
|
class="mdi text-xl cursor-pointer transition-all opacity-70"
|
|
@@ -65,6 +74,22 @@ export default defineComponent({
|
|
|
65
74
|
setup(props) {
|
|
66
75
|
const dataStore = useDataStore();
|
|
67
76
|
|
|
77
|
+
const homeClickCount = ref(0);
|
|
78
|
+
const COUNTER_URL = `${import.meta.env.VITE_COUNTER_URL ?? 'http://localhost:3001'}/counter`;
|
|
79
|
+
|
|
80
|
+
const onHomeClick = async () => {
|
|
81
|
+
// umTrackEvent('click-logo', {
|
|
82
|
+
// user: dataStore.user.fullName,
|
|
83
|
+
// branch: dataStore.user.branchCode,
|
|
84
|
+
// });
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const onHomeHover = async () => {
|
|
88
|
+
if (!dataStore.isSupport()) return;
|
|
89
|
+
try {
|
|
90
|
+
} catch {}
|
|
91
|
+
};
|
|
92
|
+
|
|
68
93
|
const onClickOutside = () => {
|
|
69
94
|
dataStore.settings.open = false;
|
|
70
95
|
};
|
|
@@ -97,7 +122,12 @@ export default defineComponent({
|
|
|
97
122
|
backIconAnim,
|
|
98
123
|
moreIconAnim,
|
|
99
124
|
|
|
125
|
+
// Counter
|
|
126
|
+
homeClickCount,
|
|
127
|
+
|
|
100
128
|
// Functions
|
|
129
|
+
onHomeClick,
|
|
130
|
+
onHomeHover,
|
|
101
131
|
onClickOutside,
|
|
102
132
|
};
|
|
103
133
|
},
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-menu v-if="items.length" :activator="activator" location="bottom center" :offset="top" transition="scale-transition">
|
|
3
3
|
<base-form-text-section class="p-4 border-[1px] flex flex-col gap-3 elevation-3 w-[250px]">
|
|
4
|
-
<div v-for="item of items.filter(i => $dataStore.filters.show(i))" :key="item.id">
|
|
5
|
-
<base-menu-nav-item :class="[$styles.textSimple]" :menu-item="item" @click="$emit(item.id)" />
|
|
4
|
+
<div v-for="item of items.filter(i => $dataStore.filters.show(i))" :key="(item as any).id">
|
|
5
|
+
<base-menu-nav-item :class="[$styles.textSimple]" :menu-item="item" @click="$emit((item as any).id)" />
|
|
6
6
|
</div>
|
|
7
7
|
</base-form-text-section>
|
|
8
8
|
</v-menu>
|
|
@@ -18,15 +18,25 @@
|
|
|
18
18
|
:class="[$styles.blueBgLight, $styles.rounded]"
|
|
19
19
|
class="mx-[10px] p-4 flex flex-col gap-4"
|
|
20
20
|
>
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
<div v-for="(question, index) in firstQuestionList.filter(i => i.first.definedAnswers === 'N')" :key="index">
|
|
22
|
+
<base-form-input
|
|
23
|
+
v-if="question.first.answerType === 'N'"
|
|
24
|
+
v-model="question.first.answerText"
|
|
25
|
+
:label="getQuestionLabel(question)"
|
|
26
|
+
:maska="$maska.threeDigit"
|
|
27
|
+
:readonly="formStore.isDisabled[whichSurvey] || !$dataStore.isTask()"
|
|
28
|
+
:rules="$rules.required"
|
|
29
|
+
/>
|
|
30
|
+
<base-form-input
|
|
31
|
+
v-if="question.first.answerType === 'T'"
|
|
32
|
+
v-model="question.first.answerText"
|
|
33
|
+
:label="getQuestionLabel(question)"
|
|
34
|
+
:readonly="formStore.isDisabled[whichSurvey] || !$dataStore.isTask()"
|
|
35
|
+
:rules="$rules.required"
|
|
36
|
+
@input="question.first.answerText = formatPressure($event)"
|
|
37
|
+
placeholder="###/##"
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
30
40
|
</section>
|
|
31
41
|
<section
|
|
32
42
|
v-if="surveyType === 'critical' && ($appContextStore.isBaiterek || $appContextStore.isMycar || $appContextStore.isBolashak || $appContextStore.isLiferenta)"
|
|
@@ -170,7 +180,7 @@
|
|
|
170
180
|
ref="vSecondaryForm"
|
|
171
181
|
@submit="submitSecondaryForm"
|
|
172
182
|
>
|
|
173
|
-
<base-form-text-section v-for="question in currentQuestion.second" :title="question.name" :key="question.name">
|
|
183
|
+
<base-form-text-section class="text-section" v-for="question in currentQuestion.second" :title="question.name" :key="question.name">
|
|
174
184
|
<div v-if="question.definedAnswers === 'N'">
|
|
175
185
|
<base-form-input
|
|
176
186
|
v-if="question.answerType === 'T' || question.answerType === 'N'"
|
|
@@ -311,6 +321,53 @@ export default defineComponent({
|
|
|
311
321
|
const firstQuestions = computed(() =>
|
|
312
322
|
filterType.value !== null ? firstQuestionList.value.filter(i => i.first.answerName?.includes(filterType.value === 'Иә/Да' ? 'Да' : 'Нет')) : firstQuestionList.value,
|
|
313
323
|
);
|
|
324
|
+
|
|
325
|
+
function formatPressure(event: any) {
|
|
326
|
+
const raw = event.target.value;
|
|
327
|
+
const isDeleting = event.inputType?.startsWith('delete');
|
|
328
|
+
|
|
329
|
+
let cleaned = raw.replace(/[^\d/]/g, '');
|
|
330
|
+
|
|
331
|
+
if ((cleaned.match(/\//g) || []).length > 1) {
|
|
332
|
+
const idx = cleaned.indexOf('/');
|
|
333
|
+
const noSlash = cleaned.replace(/\//g, '');
|
|
334
|
+
cleaned = noSlash.slice(0, idx) + '/' + noSlash.slice(idx);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const hasSlash = cleaned.includes('/');
|
|
338
|
+
|
|
339
|
+
if (hasSlash) {
|
|
340
|
+
const slashIdx = cleaned.indexOf('/');
|
|
341
|
+
const left = cleaned.slice(0, slashIdx);
|
|
342
|
+
const right = cleaned.slice(slashIdx + 1);
|
|
343
|
+
|
|
344
|
+
const leftPart = left.slice(0, 3);
|
|
345
|
+
|
|
346
|
+
if (leftPart.length < 2) {
|
|
347
|
+
return leftPart;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const maxRight = leftPart.length === 2 ? 2 : 3;
|
|
351
|
+
const rightPart = right.slice(0, maxRight);
|
|
352
|
+
|
|
353
|
+
return `${leftPart}/${rightPart}`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (cleaned.length > 3 && !isDeleting) {
|
|
357
|
+
const leftPart = cleaned.slice(0, 3);
|
|
358
|
+
const rightPart = cleaned.slice(3, 6);
|
|
359
|
+
return `${leftPart}/${rightPart}`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const digits = cleaned.slice(0, 3);
|
|
363
|
+
|
|
364
|
+
if (digits.length === 3 && !isDeleting) {
|
|
365
|
+
return `${digits}/`;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return digits;
|
|
369
|
+
}
|
|
370
|
+
|
|
314
371
|
const scrollForm = (direction: 'up' | 'down') => {
|
|
315
372
|
const scrollObject = { top: direction === 'up' ? 0 : screen.height * 10, behavior: 'smooth' };
|
|
316
373
|
if (firstPanel.value) {
|
|
@@ -320,6 +377,13 @@ export default defineComponent({
|
|
|
320
377
|
}
|
|
321
378
|
};
|
|
322
379
|
|
|
380
|
+
const getQuestionLabel = (question: AnketaBody) => {
|
|
381
|
+
if (question.first.unitOfMeasurement) {
|
|
382
|
+
return `${question.first.name} (${question.first.unitOfMeasurement})`;
|
|
383
|
+
}
|
|
384
|
+
return question.first.name;
|
|
385
|
+
};
|
|
386
|
+
|
|
323
387
|
const submitForm = async () => {
|
|
324
388
|
await vForm.value.validate().then(async (v: { valid: Boolean; errors: any }) => {
|
|
325
389
|
if (v.valid) {
|
|
@@ -399,18 +463,23 @@ export default defineComponent({
|
|
|
399
463
|
firstPanel.value = false;
|
|
400
464
|
secondPanel.value = false;
|
|
401
465
|
} else {
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
showToaster('error', appContextStore.t('toaster.errorFormField', { text: errorText?.replace(/[-<>!//.]/g, '') }));
|
|
407
|
-
}
|
|
408
|
-
errors.scrollIntoView({
|
|
409
|
-
behavior: 'smooth',
|
|
410
|
-
block: 'center',
|
|
411
|
-
inline: 'nearest',
|
|
412
|
-
});
|
|
466
|
+
const errorElement = document.querySelector<HTMLElement>('.v-input--error');
|
|
467
|
+
|
|
468
|
+
if (!errorElement) {
|
|
469
|
+
return;
|
|
413
470
|
}
|
|
471
|
+
|
|
472
|
+
const section = errorElement.closest<HTMLElement>('.text-section');
|
|
473
|
+
const label = section?.querySelector<HTMLElement>('.text-section-label');
|
|
474
|
+
const errorText = label?.textContent?.replace(/[-<>!/.]/g, '');
|
|
475
|
+
|
|
476
|
+
showToaster('error', errorText ? appContextStore.t('toaster.errorFormField', { text: errorText }) : 'Заполните все необходимые поля');
|
|
477
|
+
|
|
478
|
+
errorElement.scrollIntoView({
|
|
479
|
+
behavior: 'smooth',
|
|
480
|
+
block: 'center',
|
|
481
|
+
inline: 'nearest',
|
|
482
|
+
});
|
|
414
483
|
}
|
|
415
484
|
});
|
|
416
485
|
};
|
|
@@ -564,6 +633,8 @@ export default defineComponent({
|
|
|
564
633
|
openFirstPanel,
|
|
565
634
|
openSecondPanel,
|
|
566
635
|
closeSecondPanel,
|
|
636
|
+
getQuestionLabel,
|
|
637
|
+
formatPressure,
|
|
567
638
|
};
|
|
568
639
|
},
|
|
569
640
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<section v-show="showForm" class="flex flex-col gap-4 px-[10px]">
|
|
3
|
-
<v-form v-if="member" ref="vForm" @submit="submitForm"
|
|
3
|
+
<v-form v-if="member" ref="vForm" @submit="submitForm">
|
|
4
4
|
<base-form-section :title="$appContextStore.t('form.personalData')" class="mt-[14px]">
|
|
5
5
|
<base-form-input
|
|
6
6
|
v-model="member.phoneNumber"
|
|
@@ -118,19 +118,27 @@
|
|
|
118
118
|
<base-form-input v-model="formStore.contractDate" :label="$appContextStore.t('form.date')" :readonly="true" append-inner-icon="mdi mdi-calendar-blank-outline" />
|
|
119
119
|
<base-file-input :readonly="isDisabled" @input.prevent="onFileChange($event)" />
|
|
120
120
|
</base-content-block>
|
|
121
|
-
<
|
|
122
|
-
<
|
|
123
|
-
{{
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
class="
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
121
|
+
<template v-for="group of documentListGrouped" :key="group.name">
|
|
122
|
+
<base-content-block :class="[$styles.textSimple]">
|
|
123
|
+
<h5 class="text-center font-medium mb-4">{{ group.name }}</h5>
|
|
124
|
+
<div
|
|
125
|
+
v-for="document of group.items"
|
|
126
|
+
:key="document.id"
|
|
127
|
+
:class="[$styles.whiteBg, $styles.rounded]"
|
|
128
|
+
class="p-2 h-12 flex items-center relative mb-2 last:mb-0"
|
|
129
|
+
>
|
|
130
|
+
<div class="flex justify-between w-full items-center">
|
|
131
|
+
<span class="ml-2 block">{{ document.fileName }}</span>
|
|
132
|
+
<p class="mr-12">{{ reformatDateWithHours(document.createdDate!, true) }}</p>
|
|
133
|
+
</div>
|
|
134
|
+
<i
|
|
135
|
+
class="transition-all cursor-pointer mdi mdi-dots-vertical pl-2 mr-3 border-l-[1px] text-xl absolute right-0"
|
|
136
|
+
:class="[$styles.greenTextHover]"
|
|
137
|
+
@click="openPanel(document)"
|
|
138
|
+
></i>
|
|
139
|
+
</div>
|
|
140
|
+
</base-content-block>
|
|
141
|
+
</template>
|
|
134
142
|
</section>
|
|
135
143
|
<section class="w-full px-[10px] pt-[14px]" v-if="$appContextStore.isPension && formStore.hasRepresentative">
|
|
136
144
|
<base-form-section
|
|
@@ -281,7 +289,12 @@
|
|
|
281
289
|
@click="getDoc('view')"
|
|
282
290
|
/>
|
|
283
291
|
</base-animation>
|
|
284
|
-
<base-btn
|
|
292
|
+
<base-btn
|
|
293
|
+
:disabled="documentLoading"
|
|
294
|
+
:loading="documentLoading"
|
|
295
|
+
:text="$appContextStore.t('download')"
|
|
296
|
+
@click="getDoc('download', $dataStore.isSupport() ? downloadType : undefined)"
|
|
297
|
+
/>
|
|
285
298
|
<base-animation>
|
|
286
299
|
<base-btn v-if="canDeleteFiles" :disabled="documentLoading" :loading="documentLoading" :text="$appContextStore.t('buttons.delete')" @click="deletionDialog = true" />
|
|
287
300
|
</base-animation>
|
|
@@ -547,6 +560,15 @@ export default defineComponent({
|
|
|
547
560
|
const beneficiaryFiltered = computed(() => formStore.beneficiaryForm.filter(i => i.iin !== formStore.policyholderForm.iin) as Base.Document.Digital[]);
|
|
548
561
|
const slaveInsuredForm = computed(() => formStore.slaveInsuredForm as Base.Document.Digital);
|
|
549
562
|
const documentListFiltered = computed(() => formStore.signedDocumentList.filter(i => !['1', '2', '3', '4'].includes(String(i.fileTypeCode))));
|
|
563
|
+
const documentListGrouped = computed(() => {
|
|
564
|
+
const groups = new Map<string, typeof documentListFiltered.value>();
|
|
565
|
+
for (const doc of documentListFiltered.value) {
|
|
566
|
+
const key = doc.fileTypeNameRu || 'Без категории';
|
|
567
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
568
|
+
groups.get(key)!.push(doc);
|
|
569
|
+
}
|
|
570
|
+
return Array.from(groups.entries()).map(([name, items]) => ({ name, items }));
|
|
571
|
+
});
|
|
550
572
|
const jointMembers = ref<Member[]>([formStore.insuredForm[0]]);
|
|
551
573
|
|
|
552
574
|
const openPanel = async (document: DocumentItem) => {
|
|
@@ -968,6 +990,7 @@ export default defineComponent({
|
|
|
968
990
|
beneficiaryFiltered,
|
|
969
991
|
hasDigitalDocuments,
|
|
970
992
|
documentListFiltered,
|
|
993
|
+
documentListGrouped,
|
|
971
994
|
isDigitalDocDisabled,
|
|
972
995
|
isUnderwriterDocuments,
|
|
973
996
|
|
|
@@ -252,6 +252,7 @@
|
|
|
252
252
|
</base-animation>
|
|
253
253
|
</base-form-section>
|
|
254
254
|
<base-form-section :title="$appContextStore.t('policyholdersRepresentative.PowerOfAttorney')" v-if="whichForm === formStore.policyholdersRepresentativeFormKey">
|
|
255
|
+
<h2 :class="[$styles.textTitle]" class="pt-[14px] pb-3 pl-4 font-medium text-[#071222]">{{ $appContextStore.t('policyholdersRepresentative.mainData') }}</h2>
|
|
255
256
|
<base-form-input
|
|
256
257
|
v-model.trim="member.fullNameRod"
|
|
257
258
|
:label="$appContextStore.t('policyholdersRepresentative.NameParentCase')"
|
|
@@ -280,6 +281,7 @@
|
|
|
280
281
|
:clearable="!isDisabled"
|
|
281
282
|
:rules="$rules.required"
|
|
282
283
|
/>
|
|
284
|
+
<h2 :class="[$styles.textTitle]" class="pt-[14px] pb-3 pl-4 font-medium text-[#071222]">{{ $appContextStore.t('policyholdersRepresentative.doc') }}</h2>
|
|
283
285
|
<base-form-input
|
|
284
286
|
v-model.trim="member.confirmDocNumber"
|
|
285
287
|
:label="$appContextStore.t('policyholdersRepresentative.numberDoc')"
|
|
@@ -289,7 +291,7 @@
|
|
|
289
291
|
/>
|
|
290
292
|
<base-form-input
|
|
291
293
|
v-model="member.confirmDocIssueDate"
|
|
292
|
-
:label="$appContextStore.t('
|
|
294
|
+
:label="$appContextStore.t('policyholdersRepresentative.issueDateDoc')"
|
|
293
295
|
:readonly="isDisabled"
|
|
294
296
|
:clearable="!isDisabled"
|
|
295
297
|
:rules="$rules.date.concat($rules.checkPastOrToday)"
|
|
@@ -299,7 +301,7 @@
|
|
|
299
301
|
/>
|
|
300
302
|
<base-form-input
|
|
301
303
|
v-model="member.confirmDocExpireDate"
|
|
302
|
-
:label="$appContextStore.t('
|
|
304
|
+
:label="$appContextStore.t('policyholdersRepresentative.expiryDateDoc')"
|
|
303
305
|
:readonly="isDisabled"
|
|
304
306
|
:clearable="!isDisabled"
|
|
305
307
|
:rules="$rules.date.concat($rules.checkTodayOrFuture)"
|
|
@@ -307,25 +309,27 @@
|
|
|
307
309
|
:min-date="getToday()"
|
|
308
310
|
append-inner-icon="mdi mdi-calendar-blank-outline"
|
|
309
311
|
/>
|
|
312
|
+
<h2 :class="[$styles.textTitle]" class="pt-[14px] pb-3 pl-4 font-medium text-[#071222]">{{ $appContextStore.t('policyholdersRepresentative.visa') }}</h2>
|
|
310
313
|
<base-form-input v-model.trim="member.migrationCard" :label="$appContextStore.t('policyholdersRepresentative.numberVisa')" />
|
|
311
314
|
<base-form-input
|
|
312
315
|
v-model="member.migrationCardIssueDate"
|
|
313
|
-
:label="$appContextStore.t('
|
|
316
|
+
:label="$appContextStore.t('policyholdersRepresentative.issueDateVisa')"
|
|
314
317
|
:readonly="isDisabled"
|
|
315
318
|
:clearable="!isDisabled"
|
|
316
|
-
:rules="$rules.date"
|
|
319
|
+
:rules="member.migrationCard ? $rules.required.concat($rules.date) : $rules.date"
|
|
317
320
|
:maska="$maska.date"
|
|
318
321
|
append-inner-icon="mdi mdi-calendar-blank-outline"
|
|
319
322
|
/>
|
|
320
323
|
<base-form-input
|
|
321
324
|
v-model="member.migrationCardExpireDate"
|
|
322
|
-
:label="$appContextStore.t('
|
|
325
|
+
:label="$appContextStore.t('policyholdersRepresentative.expiryDateVisa')"
|
|
323
326
|
:readonly="isDisabled"
|
|
324
327
|
:clearable="!isDisabled"
|
|
325
|
-
:rules="$rules.date"
|
|
328
|
+
:rules="member.migrationCard ? $rules.required.concat($rules.date) : $rules.date"
|
|
326
329
|
:maska="$maska.date"
|
|
327
330
|
append-inner-icon="mdi mdi-calendar-blank-outline"
|
|
328
331
|
/>
|
|
332
|
+
<h2 :class="[$styles.textTitle]" class="pt-[14px] pb-3 pl-4 font-medium text-[#071222]">{{ $appContextStore.t('policyholdersRepresentative.notary') }}</h2>
|
|
329
333
|
<base-form-toggle
|
|
330
334
|
v-model="formStore.policyholdersRepresentativeForm.isNotary"
|
|
331
335
|
:disabled="isDisabled"
|
|
@@ -2267,6 +2271,17 @@ export default {
|
|
|
2267
2271
|
}
|
|
2268
2272
|
};
|
|
2269
2273
|
|
|
2274
|
+
const norm = (s?: string) => s?.trim().toLowerCase().replace(/ё/g, 'е').replace(/\s+/g, ' ') ?? '';
|
|
2275
|
+
|
|
2276
|
+
const normalizeDate = (d?: string) => d?.slice(0, 10) ?? '';
|
|
2277
|
+
|
|
2278
|
+
const containsAllWords = (source: string, target: string) => {
|
|
2279
|
+
const sourceWords = source.split(' ');
|
|
2280
|
+
const targetWords = target.split(' ');
|
|
2281
|
+
|
|
2282
|
+
return targetWords.every(w => sourceWords.includes(w));
|
|
2283
|
+
};
|
|
2284
|
+
|
|
2270
2285
|
const getDigitalDocument = async (otpCode: string) => {
|
|
2271
2286
|
if (!digitalDocumentOwnerType.value) return;
|
|
2272
2287
|
let iin = '';
|
|
@@ -2301,15 +2316,23 @@ export default {
|
|
|
2301
2316
|
if (digitalDocumentOwnerType.value === 'child' && selectedChild.value && responseData) {
|
|
2302
2317
|
const { firstName, lastName, birthDate } = selectedChild.value;
|
|
2303
2318
|
|
|
2304
|
-
const
|
|
2305
|
-
const
|
|
2319
|
+
const first = norm(firstName);
|
|
2320
|
+
const last = norm(lastName);
|
|
2321
|
+
|
|
2322
|
+
const respFirst = norm(responseData.firstName);
|
|
2323
|
+
const respLast = norm(responseData.lastName);
|
|
2324
|
+
const respFull = norm(responseData.fullName);
|
|
2306
2325
|
|
|
2307
|
-
const firstNameMatch = norm(firstName) === norm(responseData.firstName);
|
|
2308
|
-
const lastNameMatch = norm(lastName) === norm(responseData.lastName);
|
|
2309
2326
|
const birthDateMatch =
|
|
2310
2327
|
normalizeDate(birthDate) !== '' && normalizeDate(responseData.birthDate) !== '' && normalizeDate(birthDate) === normalizeDate(responseData.birthDate);
|
|
2311
2328
|
|
|
2312
|
-
|
|
2329
|
+
const strictMatch = first === respFirst && last === respLast;
|
|
2330
|
+
|
|
2331
|
+
const fullNameMatch = containsAllWords(respFull, `${first} ${last}`);
|
|
2332
|
+
|
|
2333
|
+
const nameMatch = strictMatch || fullNameMatch;
|
|
2334
|
+
|
|
2335
|
+
if (!nameMatch) {
|
|
2313
2336
|
let message;
|
|
2314
2337
|
if (currentLocale.value === 'kz') {
|
|
2315
2338
|
message = `Таңдалған бала (${lastName} ${firstName}) мен алынған деректер (${responseData.lastName} ${responseData.firstName}) өзара сәйкес келмейді.`;
|
|
@@ -2319,6 +2342,9 @@ export default {
|
|
|
2319
2342
|
showToaster('error', message);
|
|
2320
2343
|
return;
|
|
2321
2344
|
}
|
|
2345
|
+
if (!birthDateMatch) {
|
|
2346
|
+
showToaster('warning', 'Проверьте дату рождения');
|
|
2347
|
+
}
|
|
2322
2348
|
}
|
|
2323
2349
|
|
|
2324
2350
|
if (appContextStore.isLifetrip) {
|
|
@@ -2336,8 +2362,13 @@ export default {
|
|
|
2336
2362
|
member.value.chooseChild = `${responseData.lastName} ${responseData.firstName} ${responseData.middleName ? responseData.middleName : ''}`;
|
|
2337
2363
|
}
|
|
2338
2364
|
if (responseData.iin) member.value.iin = reformatIin(responseData.iin);
|
|
2339
|
-
if (
|
|
2340
|
-
|
|
2365
|
+
if (digitalDocumentOwnerType.value === 'child' && selectedChild.value) {
|
|
2366
|
+
if (selectedChild.value.firstName) member.value.firstName = selectedChild.value.firstName;
|
|
2367
|
+
if (selectedChild.value.lastName) member.value.lastName = selectedChild.value.lastName;
|
|
2368
|
+
} else {
|
|
2369
|
+
if (responseData.firstName) member.value.firstName = responseData.firstName;
|
|
2370
|
+
if (responseData.lastName) member.value.lastName = responseData.lastName;
|
|
2371
|
+
}
|
|
2341
2372
|
if (responseData.firstNameLatin && appContextStore.isLifetrip) member.value.firstNameLat = responseData.firstNameLatin;
|
|
2342
2373
|
if (responseData.lastNameLatin && appContextStore.isLifetrip) member.value.lastNameLat = responseData.lastNameLatin;
|
|
2343
2374
|
if (responseData.middleName) member.value.middleName = responseData.middleName;
|