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 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 = {
@@ -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">{{ title }}</span>
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
- <base-form-input
22
- v-for="(question, index) in firstQuestionList.filter(i => i.first.definedAnswers === 'N')"
23
- :key="index"
24
- v-model="question.first.answerText"
25
- :label="question.first.name"
26
- :maska="$maska.threeDigit"
27
- :readonly="formStore.isDisabled[whichSurvey] || !$dataStore.isTask()"
28
- :rules="$rules.required"
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 errors = document.querySelector('.v-input--error');
403
- if (errors) {
404
- const errorText = errors.parentElement?.children[0].innerHTML;
405
- if (errorText) {
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" class="max-h-[75svh] overflow-y-scroll">
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
- <base-content-block v-for="document of documentListFiltered" :key="document.id" :class="[$styles.textSimple]">
122
- <h5 class="text-center font-medium mb-4">
123
- {{ document.fileTypeName }}
124
- </h5>
125
- <div :class="[$styles.whiteBg, $styles.rounded]" class="p-2 h-12 flex items-center relative">
126
- <span class="ml-2">{{ document.fileName }}</span>
127
- <i
128
- class="transition-all cursor-pointer mdi mdi-dots-vertical pl-2 mr-3 border-l-[1px] text-xl absolute right-0"
129
- :class="[$styles.greenTextHover]"
130
- @click="openPanel(document)"
131
- ></i>
132
- </div>
133
- </base-content-block>
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 :disabled="documentLoading" :loading="documentLoading" :text="$appContextStore.t('download')" @click="getDoc('download', $dataStore.isSupport() ? downloadType : undefined)" />
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('form.documentDate')"
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('form.documentExpire')"
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('form.documentDate')"
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('form.documentExpire')"
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 norm = (s?: string) => s?.trim().toLowerCase() ?? '';
2305
- const normalizeDate = (d?: string) => d?.slice(0, 10) ?? '';
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
- if (!firstNameMatch || !lastNameMatch || !birthDateMatch) {
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 (responseData.firstName) member.value.firstName = responseData.firstName;
2340
- if (responseData.lastName) member.value.lastName = responseData.lastName;
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;