@rolatech/angular-onboarding 20.3.0-beta.2 → 20.3.0-beta.4
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/README.md +1 -1
- package/fesm2022/rolatech-angular-onboarding-admin-onboarding-detail-page-DREEMvoT.mjs +720 -0
- package/fesm2022/rolatech-angular-onboarding-admin-onboarding-detail-page-DREEMvoT.mjs.map +1 -0
- package/fesm2022/rolatech-angular-onboarding-admin-onboarding-index-page-DP7wffLd.mjs +313 -0
- package/fesm2022/rolatech-angular-onboarding-admin-onboarding-index-page-DP7wffLd.mjs.map +1 -0
- package/fesm2022/rolatech-angular-onboarding-agent-apply-form-page-y02hYlN_.mjs +446 -0
- package/fesm2022/rolatech-angular-onboarding-agent-apply-form-page-y02hYlN_.mjs.map +1 -0
- package/fesm2022/rolatech-angular-onboarding-agent-apply-result-page-CEL4nWb_.mjs +141 -0
- package/fesm2022/rolatech-angular-onboarding-agent-apply-result-page-CEL4nWb_.mjs.map +1 -0
- package/fesm2022/rolatech-angular-onboarding-agent-apply-review-page-DG_D03YW.mjs +453 -0
- package/fesm2022/rolatech-angular-onboarding-agent-apply-review-page-DG_D03YW.mjs.map +1 -0
- package/fesm2022/{rolatech-angular-onboarding-agent-apply-shell-page-DibWYeD1.mjs → rolatech-angular-onboarding-agent-apply-shell-page-CaTvnFzk.mjs} +27 -40
- package/fesm2022/rolatech-angular-onboarding-agent-apply-shell-page-CaTvnFzk.mjs.map +1 -0
- package/fesm2022/{rolatech-angular-onboarding-agent-apply-start-page-DC7gyOnS.mjs → rolatech-angular-onboarding-agent-apply-start-page-BfqO2YWB.mjs} +31 -29
- package/fesm2022/rolatech-angular-onboarding-agent-apply-start-page-BfqO2YWB.mjs.map +1 -0
- package/fesm2022/{rolatech-angular-onboarding-agent-onboarding-documents-page-DWBGTj5J.mjs → rolatech-angular-onboarding-agent-onboarding-documents-page-BKDYZE0e.mjs} +79 -6
- package/fesm2022/rolatech-angular-onboarding-agent-onboarding-documents-page-BKDYZE0e.mjs.map +1 -0
- package/fesm2022/rolatech-angular-onboarding.mjs +2251 -233
- package/fesm2022/rolatech-angular-onboarding.mjs.map +1 -1
- package/package.json +1 -1
- package/types/rolatech-angular-onboarding.d.ts +219 -12
- package/fesm2022/rolatech-angular-onboarding-admin-onboarding-detail-page-DKJQX3cs.mjs +0 -224
- package/fesm2022/rolatech-angular-onboarding-admin-onboarding-detail-page-DKJQX3cs.mjs.map +0 -1
- package/fesm2022/rolatech-angular-onboarding-admin-onboarding-index-page-BO4pC_NU.mjs +0 -206
- package/fesm2022/rolatech-angular-onboarding-admin-onboarding-index-page-BO4pC_NU.mjs.map +0 -1
- package/fesm2022/rolatech-angular-onboarding-agent-apply-banking-page-VYNfR4fy.mjs +0 -133
- package/fesm2022/rolatech-angular-onboarding-agent-apply-banking-page-VYNfR4fy.mjs.map +0 -1
- package/fesm2022/rolatech-angular-onboarding-agent-apply-financial-page-Ck3Rowke.mjs +0 -132
- package/fesm2022/rolatech-angular-onboarding-agent-apply-financial-page-Ck3Rowke.mjs.map +0 -1
- package/fesm2022/rolatech-angular-onboarding-agent-apply-profile-page-DNepDxHu.mjs +0 -122
- package/fesm2022/rolatech-angular-onboarding-agent-apply-profile-page-DNepDxHu.mjs.map +0 -1
- package/fesm2022/rolatech-angular-onboarding-agent-apply-qualification-page-CSwupuKt.mjs +0 -108
- package/fesm2022/rolatech-angular-onboarding-agent-apply-qualification-page-CSwupuKt.mjs.map +0 -1
- package/fesm2022/rolatech-angular-onboarding-agent-apply-review-page-DugCjfvK.mjs +0 -182
- package/fesm2022/rolatech-angular-onboarding-agent-apply-review-page-DugCjfvK.mjs.map +0 -1
- package/fesm2022/rolatech-angular-onboarding-agent-apply-shell-page-DibWYeD1.mjs.map +0 -1
- package/fesm2022/rolatech-angular-onboarding-agent-apply-start-page-DC7gyOnS.mjs.map +0 -1
- package/fesm2022/rolatech-angular-onboarding-agent-apply-submitted-page-BMkV2V8K.mjs +0 -55
- package/fesm2022/rolatech-angular-onboarding-agent-apply-submitted-page-BMkV2V8K.mjs.map +0 -1
- package/fesm2022/rolatech-angular-onboarding-agent-onboarding-documents-page-DWBGTj5J.mjs.map +0 -1
|
@@ -1,43 +1,121 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, signal, computed, Injectable, input, ViewEncapsulation, ChangeDetectionStrategy, Component, output } from '@angular/core';
|
|
2
|
+
import { inject, DestroyRef, signal, computed, effect, Injectable, input, ViewEncapsulation, ChangeDetectionStrategy, Component, output } from '@angular/core';
|
|
3
3
|
import { Router, RouterLink } from '@angular/router';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
5
|
+
import * as i1 from '@angular/forms';
|
|
6
|
+
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
7
|
+
import { OnboardingApplicantService, OnboardingAdminService, onboardingStatusLabel, onboardingIssueTypeLabel, onboardingDocumentTypeLabel, onboardingVatModeLabel } from '@rolatech/angular-services';
|
|
8
|
+
import { firstValueFrom, debounceTime } from 'rxjs';
|
|
8
9
|
import { NgClass, DatePipe } from '@angular/common';
|
|
10
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
9
11
|
|
|
10
12
|
const applicationIdGuard = (route) => Boolean(route.paramMap.get('applicationId'));
|
|
11
13
|
|
|
12
|
-
const INDIVIDUAL_REQUIRED_DOCUMENTS = [
|
|
13
|
-
{
|
|
14
|
-
|
|
14
|
+
const INDIVIDUAL_REQUIRED_DOCUMENTS$1 = [
|
|
15
|
+
{
|
|
16
|
+
type: 'PASSPORT',
|
|
17
|
+
label: 'Applicant passport',
|
|
18
|
+
helperText: 'Upload a clear PDF, JPG, or PNG of the passport identity page.',
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
const COMPANY_BASE_REQUIRED_DOCUMENTS$1 = [
|
|
22
|
+
{
|
|
23
|
+
type: 'PASSPORT',
|
|
24
|
+
label: 'Director passport',
|
|
25
|
+
helperText: 'Upload the passport for the company director handling the application.',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: 'CERTIFICATE_OF_INCORPORATION',
|
|
29
|
+
label: 'Certificate of incorporation',
|
|
30
|
+
helperText: 'Use the latest registered incorporation certificate.',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: 'INDUSTRY_CERTIFICATE',
|
|
34
|
+
label: 'Industry certificate',
|
|
35
|
+
helperText: 'Accepted certificates include RICS, ARLA, NAEA, or Propertymark.',
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
const UK_COMPANY_REQUIRED_DOCUMENTS$1 = [
|
|
39
|
+
...COMPANY_BASE_REQUIRED_DOCUMENTS$1,
|
|
40
|
+
{
|
|
41
|
+
type: 'OTHER',
|
|
42
|
+
label: 'Redress / AML evidence',
|
|
43
|
+
helperText: 'Upload the redress scheme certificate and HMRC AML evidence in one file if needed.',
|
|
44
|
+
},
|
|
15
45
|
];
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
|
|
46
|
+
const CHINA_COMPANY_REQUIRED_DOCUMENTS$1 = [
|
|
47
|
+
...COMPANY_BASE_REQUIRED_DOCUMENTS$1,
|
|
48
|
+
{
|
|
49
|
+
type: 'OTHER',
|
|
50
|
+
label: 'Stamped business license',
|
|
51
|
+
helperText: 'Upload the original scanned business license.',
|
|
52
|
+
warning: 'China company business licenses must include the official company stamp.',
|
|
53
|
+
},
|
|
20
54
|
];
|
|
55
|
+
const SORT_CODE_PATTERN = /^\d{6}$/;
|
|
56
|
+
const ACCOUNT_NUMBER_PATTERN = /^\d{8}$/;
|
|
21
57
|
class AgentApplyFacade {
|
|
22
58
|
onboardingService = inject(OnboardingApplicantService);
|
|
23
59
|
fb = inject(FormBuilder);
|
|
24
|
-
|
|
60
|
+
destroyRef = inject(DestroyRef);
|
|
25
61
|
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
26
62
|
saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
|
|
27
63
|
submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : []));
|
|
28
64
|
error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
65
|
+
lastLocalDraftSavedAt = signal(null, ...(ngDevMode ? [{ debugName: "lastLocalDraftSavedAt" }] : []));
|
|
29
66
|
application = signal(null, ...(ngDevMode ? [{ debugName: "application" }] : []));
|
|
30
67
|
detail = signal(null, ...(ngDevMode ? [{ debugName: "detail" }] : []));
|
|
68
|
+
uploadStates = signal({}, ...(ngDevMode ? [{ debugName: "uploadStates" }] : []));
|
|
31
69
|
loadedApplicationId = signal(null, ...(ngDevMode ? [{ debugName: "loadedApplicationId" }] : []));
|
|
32
70
|
applicationId = computed(() => this.detail()?.id ?? this.application()?.id ?? null, ...(ngDevMode ? [{ debugName: "applicationId" }] : []));
|
|
33
71
|
status = computed(() => this.detail()?.status ?? this.application()?.status ?? null, ...(ngDevMode ? [{ debugName: "status" }] : []));
|
|
72
|
+
applicantType = computed(() => this.detail()?.applicantType ?? this.application()?.applicantType ?? 'INDIVIDUAL', ...(ngDevMode ? [{ debugName: "applicantType" }] : []));
|
|
73
|
+
companyCountry = computed(() => (this.detail()?.companyCountry ?? this.application()?.companyCountry ?? 'UK').toUpperCase(), ...(ngDevMode ? [{ debugName: "companyCountry" }] : []));
|
|
74
|
+
isCompany = computed(() => this.applicantType() === 'COMPANY', ...(ngDevMode ? [{ debugName: "isCompany" }] : []));
|
|
75
|
+
isIndividual = computed(() => this.applicantType() === 'INDIVIDUAL', ...(ngDevMode ? [{ debugName: "isIndividual" }] : []));
|
|
76
|
+
isUkCompany = computed(() => this.isCompany() && this.companyCountry() === 'UK', ...(ngDevMode ? [{ debugName: "isUkCompany" }] : []));
|
|
77
|
+
isChinaCompany = computed(() => this.isCompany() && this.companyCountry() === 'CN', ...(ngDevMode ? [{ debugName: "isChinaCompany" }] : []));
|
|
78
|
+
hasSubmittedState = computed(() => {
|
|
79
|
+
const status = this.status();
|
|
80
|
+
return status === 'SUBMITTED' || status === 'IN_REVIEW' || status === 'APPROVED' || status === 'FAILED';
|
|
81
|
+
}, ...(ngDevMode ? [{ debugName: "hasSubmittedState" }] : []));
|
|
82
|
+
hasGeneralIssue = computed(() => this.issues().some((issue) => !issue.resolved && issue.issueType === 'GENERAL'), ...(ngDevMode ? [{ debugName: "hasGeneralIssue" }] : []));
|
|
34
83
|
documents = computed(() => this.detail()?.documents ?? [], ...(ngDevMode ? [{ debugName: "documents" }] : []));
|
|
35
84
|
issues = computed(() => this.detail()?.issues ?? [], ...(ngDevMode ? [{ debugName: "issues" }] : []));
|
|
36
85
|
timeline = computed(() => this.detail()?.timeline ?? [], ...(ngDevMode ? [{ debugName: "timeline" }] : []));
|
|
86
|
+
fieldIssueComments = computed(() => {
|
|
87
|
+
const comments = {};
|
|
88
|
+
for (const issue of this.issues()) {
|
|
89
|
+
if (issue.resolved || !issue.fieldKey || comments[issue.fieldKey]) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
comments[issue.fieldKey] = issue.comment;
|
|
93
|
+
}
|
|
94
|
+
return comments;
|
|
95
|
+
}, ...(ngDevMode ? [{ debugName: "fieldIssueComments" }] : []));
|
|
96
|
+
documentIssueComments = computed(() => {
|
|
97
|
+
const comments = {};
|
|
98
|
+
for (const issue of this.issues()) {
|
|
99
|
+
if (issue.resolved || !issue.documentType || comments[issue.documentType]) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
comments[issue.documentType] = issue.comment;
|
|
103
|
+
}
|
|
104
|
+
return comments;
|
|
105
|
+
}, ...(ngDevMode ? [{ debugName: "documentIssueComments" }] : []));
|
|
37
106
|
requiredDocuments = computed(() => {
|
|
38
|
-
|
|
39
|
-
|
|
107
|
+
if (!this.isCompany()) {
|
|
108
|
+
return INDIVIDUAL_REQUIRED_DOCUMENTS$1;
|
|
109
|
+
}
|
|
110
|
+
if (this.isChinaCompany()) {
|
|
111
|
+
return CHINA_COMPANY_REQUIRED_DOCUMENTS$1;
|
|
112
|
+
}
|
|
113
|
+
if (this.isUkCompany()) {
|
|
114
|
+
return UK_COMPANY_REQUIRED_DOCUMENTS$1;
|
|
115
|
+
}
|
|
116
|
+
return COMPANY_BASE_REQUIRED_DOCUMENTS$1;
|
|
40
117
|
}, ...(ngDevMode ? [{ debugName: "requiredDocuments" }] : []));
|
|
118
|
+
hasActiveUploads = computed(() => Object.values(this.uploadStates()).some((state) => state?.status === 'uploading'), ...(ngDevMode ? [{ debugName: "hasActiveUploads" }] : []));
|
|
41
119
|
completion = computed(() => {
|
|
42
120
|
const detail = this.detail();
|
|
43
121
|
if (!detail) {
|
|
@@ -46,25 +124,38 @@ class AgentApplyFacade {
|
|
|
46
124
|
qualification: false,
|
|
47
125
|
financial: false,
|
|
48
126
|
banking: false,
|
|
127
|
+
form: false,
|
|
49
128
|
};
|
|
50
129
|
}
|
|
51
130
|
if (detail.progress) {
|
|
131
|
+
const profile = detail.progress.profileCompleted;
|
|
132
|
+
const qualification = detail.progress.qualificationCompleted;
|
|
133
|
+
const financial = detail.progress.financialCompleted;
|
|
134
|
+
const banking = detail.progress.bankingCompleted;
|
|
52
135
|
return {
|
|
53
|
-
profile
|
|
54
|
-
qualification
|
|
55
|
-
financial
|
|
56
|
-
banking
|
|
136
|
+
profile,
|
|
137
|
+
qualification,
|
|
138
|
+
financial,
|
|
139
|
+
banking,
|
|
140
|
+
form: profile && qualification && financial && banking,
|
|
57
141
|
};
|
|
58
142
|
}
|
|
143
|
+
const profile = this.isProfileComplete(detail);
|
|
144
|
+
const qualification = this.isQualificationComplete(detail);
|
|
145
|
+
const financial = this.isFinancialComplete(detail);
|
|
146
|
+
const banking = this.isBankingComplete(detail);
|
|
59
147
|
return {
|
|
60
|
-
profile
|
|
61
|
-
qualification
|
|
62
|
-
financial
|
|
63
|
-
banking
|
|
148
|
+
profile,
|
|
149
|
+
qualification,
|
|
150
|
+
financial,
|
|
151
|
+
banking,
|
|
152
|
+
form: profile && qualification && financial && banking,
|
|
64
153
|
};
|
|
65
154
|
}, ...(ngDevMode ? [{ debugName: "completion" }] : []));
|
|
155
|
+
readyForReview = computed(() => this.completion().form && !this.hasActiveUploads(), ...(ngDevMode ? [{ debugName: "readyForReview" }] : []));
|
|
156
|
+
canSubmit = computed(() => this.readyForReview() && !this.submitting() && !this.saving(), ...(ngDevMode ? [{ debugName: "canSubmit" }] : []));
|
|
66
157
|
profileForm = this.fb.nonNullable.group({
|
|
67
|
-
organizationName: ['', [Validators.
|
|
158
|
+
organizationName: ['', [Validators.maxLength(255)]],
|
|
68
159
|
contactName: ['', [Validators.required, Validators.maxLength(255)]],
|
|
69
160
|
contactEmail: ['', [Validators.required, Validators.email]],
|
|
70
161
|
contactPhone: ['', [Validators.required, Validators.maxLength(64)]],
|
|
@@ -72,14 +163,24 @@ class AgentApplyFacade {
|
|
|
72
163
|
});
|
|
73
164
|
financialForm = this.fb.nonNullable.group({
|
|
74
165
|
vatMode: ['NON_VAT_REGISTERED', [Validators.required]],
|
|
75
|
-
vatNumber: [''],
|
|
166
|
+
vatNumber: ['', [Validators.maxLength(64)]],
|
|
76
167
|
});
|
|
77
168
|
bankingForm = this.fb.nonNullable.group({
|
|
78
169
|
bankName: ['', [Validators.required, Validators.maxLength(255)]],
|
|
79
|
-
accountHolderName: ['', [Validators.required, Validators.maxLength(255)]],
|
|
80
|
-
sortCode: ['', [Validators.required, Validators.
|
|
81
|
-
accountNumber: ['', [Validators.required, Validators.
|
|
170
|
+
accountHolderName: ['', [Validators.required, Validators.maxLength(255), this.accountHolderNameValidator()]],
|
|
171
|
+
sortCode: ['', [Validators.required, Validators.pattern(SORT_CODE_PATTERN)]],
|
|
172
|
+
accountNumber: ['', [Validators.required, Validators.pattern(ACCOUNT_NUMBER_PATTERN)]],
|
|
82
173
|
});
|
|
174
|
+
constructor() {
|
|
175
|
+
this.setupAutosave();
|
|
176
|
+
effect(() => {
|
|
177
|
+
this.applicantType();
|
|
178
|
+
this.companyCountry();
|
|
179
|
+
this.syncProfileValidators();
|
|
180
|
+
this.syncFinancialRules();
|
|
181
|
+
this.bankingForm.controls.accountHolderName.updateValueAndValidity({ emitEvent: false });
|
|
182
|
+
});
|
|
183
|
+
}
|
|
83
184
|
async start(request) {
|
|
84
185
|
this.loading.set(true);
|
|
85
186
|
this.error.set(null);
|
|
@@ -112,6 +213,8 @@ class AgentApplyFacade {
|
|
|
112
213
|
this.detail.set(response);
|
|
113
214
|
this.loadedApplicationId.set(response.id);
|
|
114
215
|
this.patchForms(response);
|
|
216
|
+
this.restoreLocalDraft(response.id);
|
|
217
|
+
this.syncFinancialRules();
|
|
115
218
|
}
|
|
116
219
|
catch (error) {
|
|
117
220
|
this.error.set(this.toErrorMessage(error));
|
|
@@ -120,7 +223,7 @@ class AgentApplyFacade {
|
|
|
120
223
|
this.loading.set(false);
|
|
121
224
|
}
|
|
122
225
|
}
|
|
123
|
-
async saveProfile() {
|
|
226
|
+
async saveProfile(reload = true) {
|
|
124
227
|
const applicationId = this.applicationId();
|
|
125
228
|
if (!applicationId) {
|
|
126
229
|
return false;
|
|
@@ -132,10 +235,19 @@ class AgentApplyFacade {
|
|
|
132
235
|
this.saving.set(true);
|
|
133
236
|
this.error.set(null);
|
|
134
237
|
try {
|
|
135
|
-
const
|
|
238
|
+
const rawValue = this.profileForm.getRawValue();
|
|
239
|
+
const payload = {
|
|
240
|
+
organizationName: this.isCompany() ? rawValue.organizationName.trim() : rawValue.contactName.trim(),
|
|
241
|
+
contactName: rawValue.contactName.trim(),
|
|
242
|
+
contactEmail: rawValue.contactEmail.trim(),
|
|
243
|
+
contactPhone: rawValue.contactPhone.trim(),
|
|
244
|
+
countryCode: rawValue.countryCode.trim().toUpperCase(),
|
|
245
|
+
};
|
|
136
246
|
const response = await firstValueFrom(this.onboardingService.saveProfile(applicationId, payload));
|
|
137
247
|
this.application.set(response);
|
|
138
|
-
|
|
248
|
+
if (reload) {
|
|
249
|
+
await this.ensureLoaded(applicationId, true);
|
|
250
|
+
}
|
|
139
251
|
return true;
|
|
140
252
|
}
|
|
141
253
|
catch (error) {
|
|
@@ -146,11 +258,12 @@ class AgentApplyFacade {
|
|
|
146
258
|
this.saving.set(false);
|
|
147
259
|
}
|
|
148
260
|
}
|
|
149
|
-
async saveFinancial() {
|
|
261
|
+
async saveFinancial(reload = true) {
|
|
150
262
|
const applicationId = this.applicationId();
|
|
151
263
|
if (!applicationId) {
|
|
152
264
|
return false;
|
|
153
265
|
}
|
|
266
|
+
this.syncFinancialRules();
|
|
154
267
|
if (this.financialForm.invalid) {
|
|
155
268
|
this.financialForm.markAllAsTouched();
|
|
156
269
|
return false;
|
|
@@ -159,13 +272,20 @@ class AgentApplyFacade {
|
|
|
159
272
|
this.error.set(null);
|
|
160
273
|
try {
|
|
161
274
|
const rawValue = this.financialForm.getRawValue();
|
|
162
|
-
const payload =
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
275
|
+
const payload = this.isIndividual()
|
|
276
|
+
? {
|
|
277
|
+
vatMode: 'NON_VAT_REGISTERED',
|
|
278
|
+
vatNumber: undefined,
|
|
279
|
+
}
|
|
280
|
+
: {
|
|
281
|
+
vatMode: rawValue.vatMode,
|
|
282
|
+
vatNumber: rawValue.vatNumber.trim() || undefined,
|
|
283
|
+
};
|
|
166
284
|
const response = await firstValueFrom(this.onboardingService.saveFinancial(applicationId, payload));
|
|
167
285
|
this.application.set(response);
|
|
168
|
-
|
|
286
|
+
if (reload) {
|
|
287
|
+
await this.ensureLoaded(applicationId, true);
|
|
288
|
+
}
|
|
169
289
|
return true;
|
|
170
290
|
}
|
|
171
291
|
catch (error) {
|
|
@@ -176,11 +296,13 @@ class AgentApplyFacade {
|
|
|
176
296
|
this.saving.set(false);
|
|
177
297
|
}
|
|
178
298
|
}
|
|
179
|
-
async saveBanking() {
|
|
299
|
+
async saveBanking(reload = true) {
|
|
180
300
|
const applicationId = this.applicationId();
|
|
181
301
|
if (!applicationId) {
|
|
182
302
|
return false;
|
|
183
303
|
}
|
|
304
|
+
this.normalizeBankingFields();
|
|
305
|
+
this.bankingForm.controls.accountHolderName.updateValueAndValidity({ emitEvent: false });
|
|
184
306
|
if (this.bankingForm.invalid) {
|
|
185
307
|
this.bankingForm.markAllAsTouched();
|
|
186
308
|
return false;
|
|
@@ -188,10 +310,18 @@ class AgentApplyFacade {
|
|
|
188
310
|
this.saving.set(true);
|
|
189
311
|
this.error.set(null);
|
|
190
312
|
try {
|
|
191
|
-
const
|
|
313
|
+
const rawValue = this.bankingForm.getRawValue();
|
|
314
|
+
const payload = {
|
|
315
|
+
bankName: rawValue.bankName.trim(),
|
|
316
|
+
accountHolderName: rawValue.accountHolderName.trim(),
|
|
317
|
+
sortCode: this.onlyDigits(rawValue.sortCode),
|
|
318
|
+
accountNumber: this.onlyDigits(rawValue.accountNumber),
|
|
319
|
+
};
|
|
192
320
|
const response = await firstValueFrom(this.onboardingService.saveBanking(applicationId, payload));
|
|
193
321
|
this.application.set(response);
|
|
194
|
-
|
|
322
|
+
if (reload) {
|
|
323
|
+
await this.ensureLoaded(applicationId, true);
|
|
324
|
+
}
|
|
195
325
|
return true;
|
|
196
326
|
}
|
|
197
327
|
catch (error) {
|
|
@@ -202,6 +332,33 @@ class AgentApplyFacade {
|
|
|
202
332
|
this.saving.set(false);
|
|
203
333
|
}
|
|
204
334
|
}
|
|
335
|
+
async saveFormProgress() {
|
|
336
|
+
if (this.hasActiveUploads()) {
|
|
337
|
+
this.error.set('Wait for all document uploads to finish before continuing.');
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
if (!this.completion().qualification) {
|
|
341
|
+
this.error.set('Upload every required qualification document before moving to review.');
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
const profileSaved = await this.saveProfile(false);
|
|
345
|
+
if (!profileSaved) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
const financialSaved = await this.saveFinancial(false);
|
|
349
|
+
if (!financialSaved) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
const bankingSaved = await this.saveBanking(false);
|
|
353
|
+
if (!bankingSaved) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
const applicationId = this.applicationId();
|
|
357
|
+
if (applicationId) {
|
|
358
|
+
await this.ensureLoaded(applicationId, true);
|
|
359
|
+
}
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
205
362
|
async uploadDocument(documentType, file, replacedDocumentId) {
|
|
206
363
|
const applicationId = this.applicationId();
|
|
207
364
|
if (!applicationId) {
|
|
@@ -209,51 +366,59 @@ class AgentApplyFacade {
|
|
|
209
366
|
}
|
|
210
367
|
this.saving.set(true);
|
|
211
368
|
this.error.set(null);
|
|
369
|
+
this.setUploadState(documentType, {
|
|
370
|
+
filename: file.name,
|
|
371
|
+
progress: 0,
|
|
372
|
+
status: 'uploading',
|
|
373
|
+
error: null,
|
|
374
|
+
});
|
|
212
375
|
try {
|
|
213
376
|
const uploadRequest = {
|
|
214
377
|
documentType,
|
|
215
378
|
filename: file.name,
|
|
216
|
-
contentType: file.type,
|
|
379
|
+
contentType: file.type || 'application/octet-stream',
|
|
217
380
|
fileSize: file.size,
|
|
218
381
|
replacedDocumentId: replacedDocumentId ?? undefined,
|
|
219
382
|
};
|
|
220
383
|
const uploadUrl = await firstValueFrom(this.onboardingService.requestUploadUrl(applicationId, uploadRequest));
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// const uploadResponse = await firstValueFrom(
|
|
230
|
-
// this.http.put(uploadUrl.uploadUrl, file, {
|
|
231
|
-
// headers: {
|
|
232
|
-
// 'Content-Type': file.type || 'application/octet-stream',
|
|
233
|
-
// },
|
|
234
|
-
// }),
|
|
235
|
-
// );
|
|
236
|
-
// console.log(uploadResponse);
|
|
237
|
-
// if (!uploadResponse .ok) {
|
|
238
|
-
// throw new Error(`Document upload failed with status ${uploadResponse.status}`);
|
|
239
|
-
// }
|
|
384
|
+
await this.uploadToSignedUrl(uploadUrl, file, (progress) => {
|
|
385
|
+
this.setUploadState(documentType, {
|
|
386
|
+
filename: file.name,
|
|
387
|
+
progress,
|
|
388
|
+
status: 'uploading',
|
|
389
|
+
error: null,
|
|
390
|
+
});
|
|
391
|
+
});
|
|
240
392
|
const confirmRequest = {
|
|
241
393
|
documentType,
|
|
242
394
|
storageType: uploadUrl.storageType,
|
|
243
395
|
objectKey: uploadUrl.objectKey,
|
|
244
396
|
objectUrl: uploadUrl.uploadUrl,
|
|
245
397
|
originalFilename: file.name,
|
|
246
|
-
contentType: file.type,
|
|
398
|
+
contentType: file.type || 'application/octet-stream',
|
|
247
399
|
fileSize: file.size,
|
|
248
400
|
replacedDocumentId: replacedDocumentId ?? undefined,
|
|
249
401
|
};
|
|
250
402
|
const detail = await firstValueFrom(this.onboardingService.confirmDocumentUpload(applicationId, confirmRequest));
|
|
251
403
|
this.detail.set(detail);
|
|
252
404
|
this.loadedApplicationId.set(detail.id);
|
|
405
|
+
this.setUploadState(documentType, {
|
|
406
|
+
filename: file.name,
|
|
407
|
+
progress: 100,
|
|
408
|
+
status: 'uploaded',
|
|
409
|
+
error: null,
|
|
410
|
+
});
|
|
253
411
|
return true;
|
|
254
412
|
}
|
|
255
413
|
catch (error) {
|
|
256
|
-
this.
|
|
414
|
+
const message = this.toErrorMessage(error);
|
|
415
|
+
this.error.set(message);
|
|
416
|
+
this.setUploadState(documentType, {
|
|
417
|
+
filename: file.name,
|
|
418
|
+
progress: 0,
|
|
419
|
+
status: 'failed',
|
|
420
|
+
error: message,
|
|
421
|
+
});
|
|
257
422
|
return false;
|
|
258
423
|
}
|
|
259
424
|
finally {
|
|
@@ -265,11 +430,16 @@ class AgentApplyFacade {
|
|
|
265
430
|
if (!applicationId) {
|
|
266
431
|
return false;
|
|
267
432
|
}
|
|
433
|
+
if (!this.readyForReview()) {
|
|
434
|
+
this.error.set('Complete the form and finish document uploads before submitting.');
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
268
437
|
this.submitting.set(true);
|
|
269
438
|
this.error.set(null);
|
|
270
439
|
try {
|
|
271
440
|
const response = await firstValueFrom(this.onboardingService.submit(applicationId));
|
|
272
441
|
this.application.set(response);
|
|
442
|
+
this.clearLocalDraft(applicationId);
|
|
273
443
|
await this.ensureLoaded(applicationId, true);
|
|
274
444
|
return true;
|
|
275
445
|
}
|
|
@@ -285,44 +455,122 @@ class AgentApplyFacade {
|
|
|
285
455
|
return this.documents().find((item) => item.documentType === documentType) ?? null;
|
|
286
456
|
}
|
|
287
457
|
getIssueComment(documentType) {
|
|
288
|
-
return this.
|
|
458
|
+
return this.documentIssueComments()[documentType] ?? null;
|
|
459
|
+
}
|
|
460
|
+
getFieldIssueComment(fieldKey) {
|
|
461
|
+
return this.fieldIssueComments()[fieldKey] ?? null;
|
|
462
|
+
}
|
|
463
|
+
isFieldLocked(fieldKey) {
|
|
464
|
+
if (this.hasSubmittedState()) {
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
if (this.status() !== 'NEED_MORE_INFO') {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
if (this.hasGeneralIssue()) {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
const editableFields = Object.keys(this.fieldIssueComments());
|
|
474
|
+
if (editableFields.length === 0) {
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
return !editableFields.includes(fieldKey);
|
|
478
|
+
}
|
|
479
|
+
isDocumentLocked(documentType) {
|
|
480
|
+
if (this.hasSubmittedState()) {
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
if (this.status() !== 'NEED_MORE_INFO') {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
if (this.hasGeneralIssue()) {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
const editableDocuments = Object.keys(this.documentIssueComments());
|
|
490
|
+
if (editableDocuments.length === 0) {
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
return !editableDocuments.includes(documentType);
|
|
289
494
|
}
|
|
290
495
|
canAccessStep(step) {
|
|
291
|
-
const completion = this.completion();
|
|
292
496
|
switch (step) {
|
|
293
|
-
case '
|
|
497
|
+
case 'form':
|
|
294
498
|
return true;
|
|
295
|
-
case 'qualification':
|
|
296
|
-
return completion.profile;
|
|
297
|
-
case 'financial':
|
|
298
|
-
return completion.profile && completion.qualification;
|
|
299
|
-
case 'banking':
|
|
300
|
-
return completion.profile && completion.qualification && completion.financial;
|
|
301
499
|
case 'review':
|
|
302
|
-
return
|
|
303
|
-
case '
|
|
500
|
+
return this.completion().form;
|
|
501
|
+
case 'result': {
|
|
304
502
|
const status = this.status();
|
|
305
|
-
return status === 'SUBMITTED' ||
|
|
503
|
+
return (status === 'SUBMITTED' ||
|
|
504
|
+
status === 'IN_REVIEW' ||
|
|
505
|
+
status === 'NEED_MORE_INFO' ||
|
|
506
|
+
status === 'APPROVED' ||
|
|
507
|
+
status === 'FAILED');
|
|
306
508
|
}
|
|
307
509
|
default:
|
|
308
510
|
return false;
|
|
309
511
|
}
|
|
310
512
|
}
|
|
311
513
|
firstIncompleteStep() {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
514
|
+
return this.completion().form ? 'review' : 'form';
|
|
515
|
+
}
|
|
516
|
+
uploadState(documentType) {
|
|
517
|
+
return (this.uploadStates()[documentType] ?? {
|
|
518
|
+
filename: null,
|
|
519
|
+
progress: 0,
|
|
520
|
+
status: 'idle',
|
|
521
|
+
error: null,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
expectedAccountHolderName() {
|
|
525
|
+
const rawProfile = this.profileForm.getRawValue();
|
|
526
|
+
return this.isCompany() ? rawProfile.organizationName.trim() : rawProfile.contactName.trim();
|
|
527
|
+
}
|
|
528
|
+
formatSortCode(value) {
|
|
529
|
+
const digits = this.onlyDigits(value);
|
|
530
|
+
if (!digits) {
|
|
531
|
+
return '';
|
|
318
532
|
}
|
|
319
|
-
|
|
320
|
-
|
|
533
|
+
return digits.match(/.{1,2}/g)?.join('-') ?? digits;
|
|
534
|
+
}
|
|
535
|
+
setupAutosave() {
|
|
536
|
+
const autosave = () => this.persistLocalDraft();
|
|
537
|
+
this.profileForm.valueChanges.pipe(debounceTime(250), takeUntilDestroyed(this.destroyRef)).subscribe(() => autosave());
|
|
538
|
+
this.financialForm.valueChanges.pipe(debounceTime(250), takeUntilDestroyed(this.destroyRef)).subscribe(() => autosave());
|
|
539
|
+
this.bankingForm.valueChanges.pipe(debounceTime(250), takeUntilDestroyed(this.destroyRef)).subscribe(() => autosave());
|
|
540
|
+
this.profileForm.controls.contactName.valueChanges
|
|
541
|
+
.pipe(debounceTime(50), takeUntilDestroyed(this.destroyRef))
|
|
542
|
+
.subscribe(() => this.bankingForm.controls.accountHolderName.updateValueAndValidity({ emitEvent: false }));
|
|
543
|
+
this.profileForm.controls.organizationName.valueChanges
|
|
544
|
+
.pipe(debounceTime(50), takeUntilDestroyed(this.destroyRef))
|
|
545
|
+
.subscribe(() => this.bankingForm.controls.accountHolderName.updateValueAndValidity({ emitEvent: false }));
|
|
546
|
+
this.financialForm.controls.vatMode.valueChanges
|
|
547
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
548
|
+
.subscribe(() => this.syncFinancialRules());
|
|
549
|
+
}
|
|
550
|
+
syncProfileValidators() {
|
|
551
|
+
const organizationName = this.profileForm.controls.organizationName;
|
|
552
|
+
const validators = this.isCompany() ? [Validators.required, Validators.maxLength(255)] : [Validators.maxLength(255)];
|
|
553
|
+
organizationName.setValidators(validators);
|
|
554
|
+
organizationName.updateValueAndValidity({ emitEvent: false });
|
|
555
|
+
}
|
|
556
|
+
syncFinancialRules() {
|
|
557
|
+
const vatMode = this.financialForm.controls.vatMode;
|
|
558
|
+
const vatNumber = this.financialForm.controls.vatNumber;
|
|
559
|
+
if (this.isIndividual()) {
|
|
560
|
+
if (vatMode.value !== 'NON_VAT_REGISTERED') {
|
|
561
|
+
vatMode.setValue('NON_VAT_REGISTERED', { emitEvent: false });
|
|
562
|
+
}
|
|
563
|
+
if (vatNumber.value) {
|
|
564
|
+
vatNumber.setValue('', { emitEvent: false });
|
|
565
|
+
}
|
|
321
566
|
}
|
|
322
|
-
if (
|
|
323
|
-
|
|
567
|
+
else if (vatMode.value === 'NON_VAT_REGISTERED') {
|
|
568
|
+
vatMode.setValue('VAT_REGISTERED', { emitEvent: false });
|
|
324
569
|
}
|
|
325
|
-
|
|
570
|
+
const requiresVatNumber = this.isCompany() && vatMode.value === 'VAT_REGISTERED';
|
|
571
|
+
const validators = requiresVatNumber ? [Validators.required, Validators.maxLength(64)] : [Validators.maxLength(64)];
|
|
572
|
+
vatNumber.setValidators(validators);
|
|
573
|
+
vatNumber.updateValueAndValidity({ emitEvent: false });
|
|
326
574
|
}
|
|
327
575
|
patchForms(detail) {
|
|
328
576
|
this.profileForm.patchValue({
|
|
@@ -330,29 +578,39 @@ class AgentApplyFacade {
|
|
|
330
578
|
contactName: detail.contactName ?? '',
|
|
331
579
|
contactEmail: detail.contactEmail ?? '',
|
|
332
580
|
contactPhone: detail.contactPhone ?? '',
|
|
333
|
-
countryCode: detail.countryCode ??
|
|
334
|
-
});
|
|
581
|
+
countryCode: detail.countryCode ?? this.defaultCountryCode(),
|
|
582
|
+
}, { emitEvent: false });
|
|
335
583
|
this.financialForm.patchValue({
|
|
336
|
-
vatMode: detail.vatMode ?? 'NON_VAT_REGISTERED',
|
|
584
|
+
vatMode: detail.vatMode ?? (detail.applicantType === 'COMPANY' ? 'VAT_REGISTERED' : 'NON_VAT_REGISTERED'),
|
|
337
585
|
vatNumber: detail.vatNumber ?? '',
|
|
338
|
-
});
|
|
586
|
+
}, { emitEvent: false });
|
|
339
587
|
this.bankingForm.patchValue({
|
|
340
588
|
bankName: detail.bankName ?? '',
|
|
341
589
|
accountHolderName: detail.accountHolderName ?? '',
|
|
342
|
-
sortCode: detail.sortCode
|
|
343
|
-
accountNumber: detail.accountNumber
|
|
344
|
-
});
|
|
590
|
+
sortCode: this.onlyDigits(detail.sortCode),
|
|
591
|
+
accountNumber: this.onlyDigits(detail.accountNumber),
|
|
592
|
+
}, { emitEvent: false });
|
|
345
593
|
}
|
|
346
594
|
isProfileComplete(detail) {
|
|
347
|
-
|
|
595
|
+
const hasApplicantName = Boolean(detail.contactName && detail.contactEmail && detail.contactPhone);
|
|
596
|
+
const hasCountryCode = Boolean(detail.countryCode);
|
|
597
|
+
if (!hasApplicantName || !hasCountryCode) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
if (detail.applicantType === 'COMPANY') {
|
|
601
|
+
return Boolean(detail.organizationName);
|
|
602
|
+
}
|
|
603
|
+
return true;
|
|
348
604
|
}
|
|
349
605
|
isQualificationComplete(detail) {
|
|
350
606
|
const uploadedTypes = new Set(detail.documents.map((item) => item.documentType));
|
|
351
|
-
|
|
352
|
-
return required.every((item) => uploadedTypes.has(item.type));
|
|
607
|
+
return this.requiredDocuments().every((item) => uploadedTypes.has(item.type));
|
|
353
608
|
}
|
|
354
609
|
isFinancialComplete(detail) {
|
|
355
|
-
if (
|
|
610
|
+
if (detail.applicantType === 'INDIVIDUAL') {
|
|
611
|
+
return detail.vatMode === 'NON_VAT_REGISTERED';
|
|
612
|
+
}
|
|
613
|
+
if (!detail.vatMode || detail.vatMode === 'NON_VAT_REGISTERED') {
|
|
356
614
|
return false;
|
|
357
615
|
}
|
|
358
616
|
if (detail.vatMode === 'VAT_REGISTERED') {
|
|
@@ -361,9 +619,121 @@ class AgentApplyFacade {
|
|
|
361
619
|
return true;
|
|
362
620
|
}
|
|
363
621
|
isBankingComplete(detail) {
|
|
364
|
-
|
|
622
|
+
if (!detail.bankName || !detail.accountHolderName) {
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
return (SORT_CODE_PATTERN.test(this.onlyDigits(detail.sortCode)) &&
|
|
626
|
+
ACCOUNT_NUMBER_PATTERN.test(this.onlyDigits(detail.accountNumber)));
|
|
627
|
+
}
|
|
628
|
+
accountHolderNameValidator() {
|
|
629
|
+
return (control) => {
|
|
630
|
+
const expected = this.expectedAccountHolderName();
|
|
631
|
+
const value = typeof control.value === 'string' ? control.value.trim() : '';
|
|
632
|
+
if (!value || !expected) {
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
return value.toLowerCase() === expected.toLowerCase() ? null : { accountHolderMismatch: true };
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
normalizeBankingFields() {
|
|
639
|
+
this.bankingForm.patchValue({
|
|
640
|
+
sortCode: this.onlyDigits(this.bankingForm.controls.sortCode.value),
|
|
641
|
+
accountNumber: this.onlyDigits(this.bankingForm.controls.accountNumber.value),
|
|
642
|
+
}, { emitEvent: false });
|
|
643
|
+
}
|
|
644
|
+
persistLocalDraft() {
|
|
645
|
+
const applicationId = this.applicationId();
|
|
646
|
+
if (!applicationId || typeof localStorage === 'undefined') {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const snapshot = {
|
|
650
|
+
profile: this.profileForm.getRawValue(),
|
|
651
|
+
financial: this.financialForm.getRawValue(),
|
|
652
|
+
banking: this.bankingForm.getRawValue(),
|
|
653
|
+
updatedAt: new Date().toISOString(),
|
|
654
|
+
};
|
|
655
|
+
localStorage.setItem(this.localDraftKey(applicationId), JSON.stringify(snapshot));
|
|
656
|
+
this.lastLocalDraftSavedAt.set(snapshot.updatedAt);
|
|
657
|
+
}
|
|
658
|
+
restoreLocalDraft(applicationId) {
|
|
659
|
+
if (typeof localStorage === 'undefined') {
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
const rawSnapshot = localStorage.getItem(this.localDraftKey(applicationId));
|
|
663
|
+
if (!rawSnapshot) {
|
|
664
|
+
this.lastLocalDraftSavedAt.set(null);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
try {
|
|
668
|
+
const snapshot = JSON.parse(rawSnapshot);
|
|
669
|
+
this.profileForm.patchValue(snapshot.profile, { emitEvent: false });
|
|
670
|
+
this.financialForm.patchValue(snapshot.financial, { emitEvent: false });
|
|
671
|
+
this.bankingForm.patchValue({
|
|
672
|
+
...snapshot.banking,
|
|
673
|
+
sortCode: this.onlyDigits(snapshot.banking.sortCode),
|
|
674
|
+
accountNumber: this.onlyDigits(snapshot.banking.accountNumber),
|
|
675
|
+
}, { emitEvent: false });
|
|
676
|
+
this.lastLocalDraftSavedAt.set(snapshot.updatedAt ?? null);
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
localStorage.removeItem(this.localDraftKey(applicationId));
|
|
680
|
+
this.lastLocalDraftSavedAt.set(null);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
clearLocalDraft(applicationId) {
|
|
684
|
+
if (typeof localStorage === 'undefined') {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
localStorage.removeItem(this.localDraftKey(applicationId));
|
|
688
|
+
this.lastLocalDraftSavedAt.set(null);
|
|
689
|
+
}
|
|
690
|
+
localDraftKey(applicationId) {
|
|
691
|
+
return `rolatech.agent-apply.${applicationId}`;
|
|
692
|
+
}
|
|
693
|
+
setUploadState(documentType, state) {
|
|
694
|
+
this.uploadStates.update((current) => ({
|
|
695
|
+
...current,
|
|
696
|
+
[documentType]: state,
|
|
697
|
+
}));
|
|
698
|
+
}
|
|
699
|
+
uploadToSignedUrl(upload, file, onProgress) {
|
|
700
|
+
return new Promise((resolve, reject) => {
|
|
701
|
+
const request = new XMLHttpRequest();
|
|
702
|
+
request.open('PUT', upload.uploadUrl, true);
|
|
703
|
+
const headers = upload.uploadHeaders ?? {};
|
|
704
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
705
|
+
request.setRequestHeader(key, value);
|
|
706
|
+
}
|
|
707
|
+
request.upload.onprogress = (event) => {
|
|
708
|
+
if (!event.lengthComputable) {
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
onProgress(Math.min(100, Math.round((event.loaded / event.total) * 100)));
|
|
712
|
+
};
|
|
713
|
+
request.onload = () => {
|
|
714
|
+
if (request.status >= 200 && request.status < 300) {
|
|
715
|
+
resolve();
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
reject(new Error(`Document upload failed with status ${request.status}.`));
|
|
719
|
+
};
|
|
720
|
+
request.onerror = () => reject(new Error('Document upload failed.'));
|
|
721
|
+
request.send(file);
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
defaultCountryCode() {
|
|
725
|
+
return this.companyCountry() === 'CN' ? 'CN' : 'GB';
|
|
726
|
+
}
|
|
727
|
+
onlyDigits(value) {
|
|
728
|
+
return (value ?? '').replace(/\D/g, '');
|
|
365
729
|
}
|
|
366
730
|
toErrorMessage(error) {
|
|
731
|
+
if (error &&
|
|
732
|
+
typeof error === 'object' &&
|
|
733
|
+
'message' in error &&
|
|
734
|
+
typeof error.message === 'string') {
|
|
735
|
+
return error.message;
|
|
736
|
+
}
|
|
367
737
|
if (error instanceof Error && error.message) {
|
|
368
738
|
return error.message;
|
|
369
739
|
}
|
|
@@ -374,7 +744,7 @@ class AgentApplyFacade {
|
|
|
374
744
|
}
|
|
375
745
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AgentApplyFacade, decorators: [{
|
|
376
746
|
type: Injectable
|
|
377
|
-
}] });
|
|
747
|
+
}], ctorParameters: () => [] });
|
|
378
748
|
|
|
379
749
|
const applyStepGuard = async (childRoute) => {
|
|
380
750
|
const facade = inject(AgentApplyFacade);
|
|
@@ -407,61 +777,65 @@ function buildFallbackSegments(route, fallbackStep) {
|
|
|
407
777
|
const AGENT_APPLY_ROUTES = [
|
|
408
778
|
{
|
|
409
779
|
path: '',
|
|
410
|
-
loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-start-page-
|
|
780
|
+
loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-start-page-BfqO2YWB.mjs').then((m) => m.AgentApplyStartPage),
|
|
411
781
|
},
|
|
412
782
|
{
|
|
413
783
|
path: ':applicationId',
|
|
414
784
|
canActivate: [applicationIdGuard],
|
|
415
785
|
canActivateChild: [applyStepGuard],
|
|
416
786
|
providers: [AgentApplyFacade],
|
|
417
|
-
loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-shell-page-
|
|
787
|
+
loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-shell-page-CaTvnFzk.mjs').then((m) => m.AgentApplyShellPage),
|
|
418
788
|
children: [
|
|
419
789
|
{
|
|
420
790
|
path: '',
|
|
421
791
|
pathMatch: 'full',
|
|
422
|
-
redirectTo: '
|
|
792
|
+
redirectTo: 'form',
|
|
423
793
|
},
|
|
424
794
|
{
|
|
425
|
-
path: '
|
|
795
|
+
path: 'form',
|
|
426
796
|
data: {
|
|
427
|
-
requiredStep: '
|
|
797
|
+
requiredStep: 'form',
|
|
428
798
|
},
|
|
429
|
-
loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-
|
|
799
|
+
loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-form-page-y02hYlN_.mjs').then((m) => m.AgentApplyFormPage),
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
path: 'profile',
|
|
803
|
+
pathMatch: 'full',
|
|
804
|
+
redirectTo: 'form',
|
|
430
805
|
},
|
|
431
806
|
{
|
|
432
807
|
path: 'qualification',
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
},
|
|
436
|
-
loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-qualification-page-CSwupuKt.mjs').then((m) => m.AgentApplyQualificationPage),
|
|
808
|
+
pathMatch: 'full',
|
|
809
|
+
redirectTo: 'form',
|
|
437
810
|
},
|
|
438
811
|
{
|
|
439
812
|
path: 'financial',
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
},
|
|
443
|
-
loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-financial-page-Ck3Rowke.mjs').then((m) => m.AgentApplyFinancialPage),
|
|
813
|
+
pathMatch: 'full',
|
|
814
|
+
redirectTo: 'form',
|
|
444
815
|
},
|
|
445
816
|
{
|
|
446
817
|
path: 'banking',
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
},
|
|
450
|
-
loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-banking-page-VYNfR4fy.mjs').then((m) => m.AgentApplyBankingPage),
|
|
818
|
+
pathMatch: 'full',
|
|
819
|
+
redirectTo: 'form',
|
|
451
820
|
},
|
|
452
821
|
{
|
|
453
822
|
path: 'review',
|
|
454
823
|
data: {
|
|
455
824
|
requiredStep: 'review',
|
|
456
825
|
},
|
|
457
|
-
loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-review-page-
|
|
826
|
+
loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-review-page-DG_D03YW.mjs').then((m) => m.AgentApplyReviewPage),
|
|
458
827
|
},
|
|
459
828
|
{
|
|
460
829
|
path: 'submitted',
|
|
830
|
+
pathMatch: 'full',
|
|
831
|
+
redirectTo: 'result',
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
path: 'result',
|
|
461
835
|
data: {
|
|
462
|
-
requiredStep: '
|
|
836
|
+
requiredStep: 'result',
|
|
463
837
|
},
|
|
464
|
-
loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-
|
|
838
|
+
loadComponent: () => import('./rolatech-angular-onboarding-agent-apply-result-page-CEL4nWb_.mjs').then((m) => m.AgentApplyResultPage),
|
|
465
839
|
},
|
|
466
840
|
],
|
|
467
841
|
},
|
|
@@ -520,7 +894,7 @@ class AgentOnboardingFacade {
|
|
|
520
894
|
method: 'PUT',
|
|
521
895
|
body: file,
|
|
522
896
|
headers: {
|
|
523
|
-
|
|
897
|
+
...uploadUrl.uploadHeaders,
|
|
524
898
|
},
|
|
525
899
|
});
|
|
526
900
|
if (!uploadResponse.ok) {
|
|
@@ -596,7 +970,7 @@ const AGENT_ONBOARDING_ROUTES = [
|
|
|
596
970
|
},
|
|
597
971
|
{
|
|
598
972
|
path: 'documents',
|
|
599
|
-
loadComponent: () => import('./rolatech-angular-onboarding-agent-onboarding-documents-page-
|
|
973
|
+
loadComponent: () => import('./rolatech-angular-onboarding-agent-onboarding-documents-page-BKDYZE0e.mjs').then((m) => m.AgentOnboardingDocumentsPage),
|
|
600
974
|
},
|
|
601
975
|
{
|
|
602
976
|
path: 'issues',
|
|
@@ -609,17 +983,17 @@ const AGENT_ONBOARDING_ROUTES = [
|
|
|
609
983
|
const ADMIN_ONBOARDING_ROUTES = [
|
|
610
984
|
{
|
|
611
985
|
path: '',
|
|
612
|
-
loadComponent: () => import('./rolatech-angular-onboarding-admin-onboarding-index-page-
|
|
986
|
+
loadComponent: () => import('./rolatech-angular-onboarding-admin-onboarding-index-page-DP7wffLd.mjs').then((m) => m.AdminOnboardingIndexPage),
|
|
613
987
|
},
|
|
614
988
|
{
|
|
615
|
-
path: ':applicationId',
|
|
989
|
+
path: ':applicationId/review',
|
|
616
990
|
canActivate: [applicationIdGuard],
|
|
617
|
-
loadComponent: () => import('./rolatech-angular-onboarding-admin-onboarding-
|
|
991
|
+
loadComponent: () => import('./rolatech-angular-onboarding-admin-onboarding-review-page-BERcLBeQ.mjs').then((m) => m.AdminOnboardingReviewPage),
|
|
618
992
|
},
|
|
619
993
|
{
|
|
620
|
-
path: ':applicationId
|
|
994
|
+
path: ':applicationId',
|
|
621
995
|
canActivate: [applicationIdGuard],
|
|
622
|
-
loadComponent: () => import('./rolatech-angular-onboarding-admin-onboarding-
|
|
996
|
+
loadComponent: () => import('./rolatech-angular-onboarding-admin-onboarding-detail-page-DREEMvoT.mjs').then((m) => m.AdminOnboardingDetailPage),
|
|
623
997
|
},
|
|
624
998
|
];
|
|
625
999
|
|
|
@@ -681,6 +1055,12 @@ class AdminOnboardingListFacade {
|
|
|
681
1055
|
}));
|
|
682
1056
|
}
|
|
683
1057
|
toErrorMessage(error) {
|
|
1058
|
+
if (error &&
|
|
1059
|
+
typeof error === 'object' &&
|
|
1060
|
+
'message' in error &&
|
|
1061
|
+
typeof error.message === 'string') {
|
|
1062
|
+
return error.message;
|
|
1063
|
+
}
|
|
684
1064
|
if (error instanceof Error && error.message) {
|
|
685
1065
|
return error.message;
|
|
686
1066
|
}
|
|
@@ -728,6 +1108,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
|
|
|
728
1108
|
type: Injectable
|
|
729
1109
|
}] });
|
|
730
1110
|
|
|
1111
|
+
const INDIVIDUAL_REQUIRED_DOCUMENTS = [
|
|
1112
|
+
{ type: 'PASSPORT', label: 'Applicant passport', helperText: 'Required identity document.' },
|
|
1113
|
+
];
|
|
1114
|
+
const COMPANY_BASE_REQUIRED_DOCUMENTS = [
|
|
1115
|
+
{ type: 'PASSPORT', label: 'Director passport', helperText: 'Required director identity document.' },
|
|
1116
|
+
{ type: 'CERTIFICATE_OF_INCORPORATION', label: 'Certificate of incorporation', helperText: 'Required for company verification.' },
|
|
1117
|
+
{ type: 'INDUSTRY_CERTIFICATE', label: 'Industry certificate', helperText: 'RICS, ARLA, NAEA, or Propertymark.' },
|
|
1118
|
+
];
|
|
1119
|
+
const UK_COMPANY_REQUIRED_DOCUMENTS = [
|
|
1120
|
+
...COMPANY_BASE_REQUIRED_DOCUMENTS,
|
|
1121
|
+
{ type: 'OTHER', label: 'Redress / AML evidence', helperText: 'Evidence bundle for UK company review.' },
|
|
1122
|
+
];
|
|
1123
|
+
const CHINA_COMPANY_REQUIRED_DOCUMENTS = [
|
|
1124
|
+
...COMPANY_BASE_REQUIRED_DOCUMENTS,
|
|
1125
|
+
{
|
|
1126
|
+
type: 'OTHER',
|
|
1127
|
+
label: 'Stamped business license',
|
|
1128
|
+
helperText: 'The original scanned business license must include the official company stamp.',
|
|
1129
|
+
},
|
|
1130
|
+
];
|
|
731
1131
|
class AdminOnboardingReviewFacade {
|
|
732
1132
|
onboardingAdminService = inject(OnboardingAdminService);
|
|
733
1133
|
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
@@ -735,7 +1135,49 @@ class AdminOnboardingReviewFacade {
|
|
|
735
1135
|
error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
736
1136
|
detail = signal(null, ...(ngDevMode ? [{ debugName: "detail" }] : []));
|
|
737
1137
|
reviewComment = signal('', ...(ngDevMode ? [{ debugName: "reviewComment" }] : []));
|
|
738
|
-
|
|
1138
|
+
fieldIssueDrafts = signal({}, ...(ngDevMode ? [{ debugName: "fieldIssueDrafts" }] : []));
|
|
1139
|
+
documentIssueDrafts = signal({}, ...(ngDevMode ? [{ debugName: "documentIssueDrafts" }] : []));
|
|
1140
|
+
passedFields = signal({}, ...(ngDevMode ? [{ debugName: "passedFields" }] : []));
|
|
1141
|
+
passedDocuments = signal({}, ...(ngDevMode ? [{ debugName: "passedDocuments" }] : []));
|
|
1142
|
+
previewLoading = signal(false, ...(ngDevMode ? [{ debugName: "previewLoading" }] : []));
|
|
1143
|
+
previewError = signal(null, ...(ngDevMode ? [{ debugName: "previewError" }] : []));
|
|
1144
|
+
previewUrl = signal(null, ...(ngDevMode ? [{ debugName: "previewUrl" }] : []));
|
|
1145
|
+
previewDocumentId = signal(null, ...(ngDevMode ? [{ debugName: "previewDocumentId" }] : []));
|
|
1146
|
+
requiredDocuments = computed(() => {
|
|
1147
|
+
const detail = this.detail();
|
|
1148
|
+
if (!detail || detail.applicantType !== 'COMPANY') {
|
|
1149
|
+
return INDIVIDUAL_REQUIRED_DOCUMENTS;
|
|
1150
|
+
}
|
|
1151
|
+
if (detail.companyCountry === 'CN') {
|
|
1152
|
+
return CHINA_COMPANY_REQUIRED_DOCUMENTS;
|
|
1153
|
+
}
|
|
1154
|
+
if (detail.companyCountry === 'UK') {
|
|
1155
|
+
return UK_COMPANY_REQUIRED_DOCUMENTS;
|
|
1156
|
+
}
|
|
1157
|
+
return COMPANY_BASE_REQUIRED_DOCUMENTS;
|
|
1158
|
+
}, ...(ngDevMode ? [{ debugName: "requiredDocuments" }] : []));
|
|
1159
|
+
existingFieldIssues = computed(() => {
|
|
1160
|
+
const result = {};
|
|
1161
|
+
for (const issue of this.detail()?.issues ?? []) {
|
|
1162
|
+
if (issue.resolved || !issue.fieldKey || result[issue.fieldKey]) {
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
result[issue.fieldKey] = issue.comment;
|
|
1166
|
+
}
|
|
1167
|
+
return result;
|
|
1168
|
+
}, ...(ngDevMode ? [{ debugName: "existingFieldIssues" }] : []));
|
|
1169
|
+
existingDocumentIssues = computed(() => {
|
|
1170
|
+
const result = {};
|
|
1171
|
+
for (const issue of this.detail()?.issues ?? []) {
|
|
1172
|
+
if (issue.resolved || !issue.documentType || result[issue.documentType]) {
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
result[issue.documentType] = issue.comment;
|
|
1176
|
+
}
|
|
1177
|
+
return result;
|
|
1178
|
+
}, ...(ngDevMode ? [{ debugName: "existingDocumentIssues" }] : []));
|
|
1179
|
+
issueDrafts = computed(() => this.buildIssueDrafts(), ...(ngDevMode ? [{ debugName: "issueDrafts" }] : []));
|
|
1180
|
+
hasPendingIssues = computed(() => this.issueDrafts().length > 0, ...(ngDevMode ? [{ debugName: "hasPendingIssues" }] : []));
|
|
739
1181
|
async load(applicationId) {
|
|
740
1182
|
if (!applicationId) {
|
|
741
1183
|
return;
|
|
@@ -745,6 +1187,8 @@ class AdminOnboardingReviewFacade {
|
|
|
745
1187
|
try {
|
|
746
1188
|
const detail = await firstValueFrom(this.onboardingAdminService.getApplication(applicationId));
|
|
747
1189
|
this.detail.set(detail);
|
|
1190
|
+
this.resetDrafts();
|
|
1191
|
+
this.clearDocumentPreview();
|
|
748
1192
|
}
|
|
749
1193
|
catch (error) {
|
|
750
1194
|
this.error.set(this.toErrorMessage(error));
|
|
@@ -754,108 +1198,296 @@ class AdminOnboardingReviewFacade {
|
|
|
754
1198
|
}
|
|
755
1199
|
}
|
|
756
1200
|
addFieldIssue(fieldKey, comment) {
|
|
757
|
-
this.
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
fieldKey,
|
|
762
|
-
comment,
|
|
763
|
-
},
|
|
764
|
-
]);
|
|
1201
|
+
this.setFieldIssueDraft(fieldKey, {
|
|
1202
|
+
optionKey: 'manual',
|
|
1203
|
+
comment,
|
|
1204
|
+
});
|
|
765
1205
|
}
|
|
766
1206
|
addDocumentIssue(documentType, comment) {
|
|
767
|
-
this.
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
documentType,
|
|
772
|
-
comment,
|
|
773
|
-
},
|
|
774
|
-
]);
|
|
1207
|
+
this.setDocumentIssueDraft(documentType, {
|
|
1208
|
+
optionKey: 'manual',
|
|
1209
|
+
comment,
|
|
1210
|
+
});
|
|
775
1211
|
}
|
|
776
1212
|
removeIssue(index) {
|
|
777
|
-
this.issueDrafts
|
|
1213
|
+
const target = this.issueDrafts()[index];
|
|
1214
|
+
if (!target) {
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
if (target.issueType === 'FIELD' && target.fieldKey) {
|
|
1218
|
+
this.clearFieldIssueDraft(target.fieldKey);
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
if (target.issueType === 'DOCUMENT' && target.documentType) {
|
|
1222
|
+
this.clearDocumentIssueDraft(target.documentType);
|
|
1223
|
+
}
|
|
778
1224
|
}
|
|
779
1225
|
setComment(value) {
|
|
780
1226
|
this.reviewComment.set(value);
|
|
781
1227
|
}
|
|
1228
|
+
setFieldIssueDraft(fieldKey, draft) {
|
|
1229
|
+
const normalized = this.normalizeDraft(draft);
|
|
1230
|
+
this.fieldIssueDrafts.update((current) => {
|
|
1231
|
+
const next = { ...current };
|
|
1232
|
+
if (!normalized) {
|
|
1233
|
+
delete next[fieldKey];
|
|
1234
|
+
}
|
|
1235
|
+
else {
|
|
1236
|
+
next[fieldKey] = normalized;
|
|
1237
|
+
}
|
|
1238
|
+
return next;
|
|
1239
|
+
});
|
|
1240
|
+
if (normalized) {
|
|
1241
|
+
this.passedFields.update((current) => ({
|
|
1242
|
+
...current,
|
|
1243
|
+
[fieldKey]: false,
|
|
1244
|
+
}));
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
setDocumentIssueDraft(documentType, draft) {
|
|
1248
|
+
const normalized = this.normalizeDraft(draft);
|
|
1249
|
+
this.documentIssueDrafts.update((current) => {
|
|
1250
|
+
const next = { ...current };
|
|
1251
|
+
if (!normalized) {
|
|
1252
|
+
delete next[documentType];
|
|
1253
|
+
}
|
|
1254
|
+
else {
|
|
1255
|
+
next[documentType] = normalized;
|
|
1256
|
+
}
|
|
1257
|
+
return next;
|
|
1258
|
+
});
|
|
1259
|
+
if (normalized) {
|
|
1260
|
+
this.passedDocuments.update((current) => ({
|
|
1261
|
+
...current,
|
|
1262
|
+
[documentType]: false,
|
|
1263
|
+
}));
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
toggleFieldPassed(fieldKey, passed) {
|
|
1267
|
+
this.passedFields.update((current) => ({
|
|
1268
|
+
...current,
|
|
1269
|
+
[fieldKey]: passed,
|
|
1270
|
+
}));
|
|
1271
|
+
if (passed) {
|
|
1272
|
+
this.clearFieldIssueDraft(fieldKey);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
toggleDocumentPassed(documentType, passed) {
|
|
1276
|
+
this.passedDocuments.update((current) => ({
|
|
1277
|
+
...current,
|
|
1278
|
+
[documentType]: passed,
|
|
1279
|
+
}));
|
|
1280
|
+
if (passed) {
|
|
1281
|
+
this.clearDocumentIssueDraft(documentType);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
fieldDraft(fieldKey) {
|
|
1285
|
+
return this.fieldIssueDrafts()[fieldKey] ?? null;
|
|
1286
|
+
}
|
|
1287
|
+
documentDraft(documentType) {
|
|
1288
|
+
return this.documentIssueDrafts()[documentType] ?? null;
|
|
1289
|
+
}
|
|
1290
|
+
fieldDraftComment(fieldKey) {
|
|
1291
|
+
return this.fieldDraft(fieldKey)?.comment ?? '';
|
|
1292
|
+
}
|
|
1293
|
+
documentDraftComment(documentType) {
|
|
1294
|
+
return this.documentDraft(documentType)?.comment ?? '';
|
|
1295
|
+
}
|
|
1296
|
+
fieldPassed(fieldKey) {
|
|
1297
|
+
return this.passedFields()[fieldKey] ?? false;
|
|
1298
|
+
}
|
|
1299
|
+
documentPassed(documentType) {
|
|
1300
|
+
return this.passedDocuments()[documentType] ?? false;
|
|
1301
|
+
}
|
|
782
1302
|
async startReview(applicationId) {
|
|
783
1303
|
return this.runAction(async () => {
|
|
784
|
-
|
|
785
|
-
this.
|
|
1304
|
+
await firstValueFrom(this.onboardingAdminService.startReview(applicationId));
|
|
1305
|
+
await this.load(applicationId);
|
|
786
1306
|
});
|
|
787
1307
|
}
|
|
788
1308
|
async requestMoreInfo(applicationId) {
|
|
1309
|
+
const issues = this.issueDrafts();
|
|
1310
|
+
if (issues.length === 0) {
|
|
1311
|
+
this.error.set('Add at least one field or document issue before requesting more information.');
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
789
1314
|
const payload = {
|
|
790
|
-
comment: this.reviewComment(),
|
|
791
|
-
issues
|
|
1315
|
+
comment: this.reviewComment().trim(),
|
|
1316
|
+
issues,
|
|
792
1317
|
};
|
|
793
1318
|
return this.runAction(async () => {
|
|
794
|
-
|
|
795
|
-
this.
|
|
1319
|
+
await firstValueFrom(this.onboardingAdminService.needMoreInfo(applicationId, payload));
|
|
1320
|
+
await this.load(applicationId);
|
|
796
1321
|
this.reviewComment.set('');
|
|
797
|
-
this.
|
|
1322
|
+
this.resetDrafts();
|
|
798
1323
|
});
|
|
799
1324
|
}
|
|
800
1325
|
async approve(applicationId) {
|
|
1326
|
+
if (this.hasPendingIssues()) {
|
|
1327
|
+
this.error.set('Clear draft issues before approving the application.');
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
801
1330
|
const payload = {
|
|
802
|
-
comment: this.reviewComment(),
|
|
1331
|
+
comment: this.reviewComment().trim(),
|
|
803
1332
|
};
|
|
804
1333
|
return this.runAction(async () => {
|
|
805
|
-
|
|
806
|
-
this.
|
|
1334
|
+
await firstValueFrom(this.onboardingAdminService.approve(applicationId, payload));
|
|
1335
|
+
await this.load(applicationId);
|
|
807
1336
|
this.reviewComment.set('');
|
|
1337
|
+
this.resetDrafts();
|
|
808
1338
|
});
|
|
809
1339
|
}
|
|
810
1340
|
async fail(applicationId) {
|
|
1341
|
+
if (this.hasPendingIssues()) {
|
|
1342
|
+
this.error.set('Clear draft issues before failing the application.');
|
|
1343
|
+
return false;
|
|
1344
|
+
}
|
|
811
1345
|
const payload = {
|
|
812
|
-
comment: this.reviewComment(),
|
|
1346
|
+
comment: this.reviewComment().trim(),
|
|
813
1347
|
};
|
|
814
1348
|
return this.runAction(async () => {
|
|
815
|
-
|
|
816
|
-
this.
|
|
1349
|
+
await firstValueFrom(this.onboardingAdminService.fail(applicationId, payload));
|
|
1350
|
+
await this.load(applicationId);
|
|
817
1351
|
this.reviewComment.set('');
|
|
1352
|
+
this.resetDrafts();
|
|
818
1353
|
});
|
|
819
1354
|
}
|
|
820
|
-
async
|
|
821
|
-
this.
|
|
822
|
-
|
|
1355
|
+
async openDocumentPreview(documentId) {
|
|
1356
|
+
const applicationId = this.detail()?.id;
|
|
1357
|
+
if (!applicationId || !documentId) {
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
this.previewLoading.set(true);
|
|
1361
|
+
this.previewError.set(null);
|
|
1362
|
+
this.previewUrl.set(null);
|
|
1363
|
+
this.previewDocumentId.set(documentId);
|
|
823
1364
|
try {
|
|
824
|
-
await
|
|
1365
|
+
const response = await firstValueFrom(this.onboardingAdminService.getDocumentPreviewUrl(applicationId, documentId));
|
|
1366
|
+
this.previewUrl.set(response.previewUrl);
|
|
1367
|
+
this.previewDocumentId.set(response.documentId);
|
|
825
1368
|
return true;
|
|
826
1369
|
}
|
|
827
1370
|
catch (error) {
|
|
828
|
-
this.
|
|
1371
|
+
this.previewError.set(this.toPreviewErrorMessage(error));
|
|
829
1372
|
return false;
|
|
830
1373
|
}
|
|
831
1374
|
finally {
|
|
832
|
-
this.
|
|
1375
|
+
this.previewLoading.set(false);
|
|
833
1376
|
}
|
|
834
1377
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1378
|
+
clearDocumentPreview() {
|
|
1379
|
+
this.previewLoading.set(false);
|
|
1380
|
+
this.previewError.set(null);
|
|
1381
|
+
this.previewUrl.set(null);
|
|
1382
|
+
this.previewDocumentId.set(null);
|
|
840
1383
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1384
|
+
clearFieldIssueDraft(fieldKey) {
|
|
1385
|
+
this.fieldIssueDrafts.update((current) => {
|
|
1386
|
+
const next = { ...current };
|
|
1387
|
+
delete next[fieldKey];
|
|
1388
|
+
return next;
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
clearDocumentIssueDraft(documentType) {
|
|
1392
|
+
this.documentIssueDrafts.update((current) => {
|
|
1393
|
+
const next = { ...current };
|
|
1394
|
+
delete next[documentType];
|
|
1395
|
+
return next;
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
buildIssueDrafts() {
|
|
1399
|
+
const fieldIssues = Object.entries(this.fieldIssueDrafts()).flatMap(([fieldKey, draft]) => draft.comment.trim()
|
|
1400
|
+
? [
|
|
1401
|
+
{
|
|
1402
|
+
issueType: 'FIELD',
|
|
1403
|
+
fieldKey,
|
|
1404
|
+
comment: draft.comment.trim(),
|
|
1405
|
+
},
|
|
1406
|
+
]
|
|
1407
|
+
: []);
|
|
1408
|
+
const documentIssues = Object.entries(this.documentIssueDrafts()).flatMap(([documentType, draft]) => draft?.comment.trim()
|
|
1409
|
+
? [
|
|
1410
|
+
{
|
|
1411
|
+
issueType: 'DOCUMENT',
|
|
1412
|
+
documentType: documentType,
|
|
1413
|
+
comment: draft.comment.trim(),
|
|
1414
|
+
},
|
|
1415
|
+
]
|
|
1416
|
+
: []);
|
|
1417
|
+
return [...fieldIssues, ...documentIssues];
|
|
1418
|
+
}
|
|
1419
|
+
resetDrafts() {
|
|
1420
|
+
this.fieldIssueDrafts.set({});
|
|
1421
|
+
this.documentIssueDrafts.set({});
|
|
1422
|
+
this.passedFields.set({});
|
|
1423
|
+
this.passedDocuments.set({});
|
|
1424
|
+
}
|
|
1425
|
+
normalizeDraft(draft) {
|
|
1426
|
+
if (typeof draft === 'string') {
|
|
1427
|
+
return draft.trim()
|
|
1428
|
+
? {
|
|
1429
|
+
optionKey: 'manual',
|
|
1430
|
+
comment: draft,
|
|
1431
|
+
}
|
|
1432
|
+
: null;
|
|
1433
|
+
}
|
|
1434
|
+
if (!draft) {
|
|
1435
|
+
return null;
|
|
1436
|
+
}
|
|
1437
|
+
if (!draft.optionKey?.trim()) {
|
|
1438
|
+
return null;
|
|
1439
|
+
}
|
|
1440
|
+
return {
|
|
1441
|
+
optionKey: draft.optionKey,
|
|
1442
|
+
comment: draft.comment ?? '',
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
async runAction(action) {
|
|
1446
|
+
this.saving.set(true);
|
|
1447
|
+
this.error.set(null);
|
|
1448
|
+
try {
|
|
1449
|
+
await action();
|
|
1450
|
+
return true;
|
|
1451
|
+
}
|
|
1452
|
+
catch (error) {
|
|
1453
|
+
this.error.set(this.toErrorMessage(error));
|
|
1454
|
+
return false;
|
|
1455
|
+
}
|
|
1456
|
+
finally {
|
|
1457
|
+
this.saving.set(false);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
toErrorMessage(error) {
|
|
1461
|
+
if (error instanceof Error && error.message) {
|
|
1462
|
+
return error.message;
|
|
1463
|
+
}
|
|
1464
|
+
return 'Unable to complete the onboarding review action.';
|
|
1465
|
+
}
|
|
1466
|
+
toPreviewErrorMessage(error) {
|
|
1467
|
+
if (error instanceof Error && error.message) {
|
|
1468
|
+
return error.message;
|
|
1469
|
+
}
|
|
1470
|
+
return 'Unable to load the document preview.';
|
|
1471
|
+
}
|
|
1472
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AdminOnboardingReviewFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1473
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AdminOnboardingReviewFacade });
|
|
1474
|
+
}
|
|
1475
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AdminOnboardingReviewFacade, decorators: [{
|
|
1476
|
+
type: Injectable
|
|
1477
|
+
}] });
|
|
858
1478
|
|
|
1479
|
+
class OnboardingPageShell {
|
|
1480
|
+
title = input.required(...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
1481
|
+
subtitle = input('', ...(ngDevMode ? [{ debugName: "subtitle" }] : []));
|
|
1482
|
+
kicker = input('', ...(ngDevMode ? [{ debugName: "kicker" }] : []));
|
|
1483
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingPageShell, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1484
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingPageShell, isStandalone: true, selector: "rolatech-onboarding-page-shell", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, kicker: { classPropertyName: "kicker", publicName: "kicker", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
1485
|
+
<section
|
|
1486
|
+
class="rt-onboarding-theme mx-auto w-full max-w-6xl space-y-6 bg-(--rt-base-background) px-4 py-6 text-(--rt-text-primary) sm:px-6 lg:px-8"
|
|
1487
|
+
>
|
|
1488
|
+
<header
|
|
1489
|
+
class="relative overflow-hidden rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm sm:p-8"
|
|
1490
|
+
>
|
|
859
1491
|
<div class="relative flex flex-wrap items-start justify-between gap-4">
|
|
860
1492
|
<div class="flex min-w-0 items-start gap-3">
|
|
861
1493
|
<div class="rt-onboarding-header-leading">
|
|
@@ -889,11 +1521,12 @@ class OnboardingPageShell {
|
|
|
889
1521
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingPageShell, decorators: [{
|
|
890
1522
|
type: Component,
|
|
891
1523
|
args: [{ selector: 'rolatech-onboarding-page-shell', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { class: 'block' }, template: `
|
|
892
|
-
<section
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1524
|
+
<section
|
|
1525
|
+
class="rt-onboarding-theme mx-auto w-full max-w-6xl space-y-6 bg-(--rt-base-background) px-4 py-6 text-(--rt-text-primary) sm:px-6 lg:px-8"
|
|
1526
|
+
>
|
|
1527
|
+
<header
|
|
1528
|
+
class="relative overflow-hidden rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm sm:p-8"
|
|
1529
|
+
>
|
|
897
1530
|
<div class="relative flex flex-wrap items-start justify-between gap-4">
|
|
898
1531
|
<div class="flex min-w-0 items-start gap-3">
|
|
899
1532
|
<div class="rt-onboarding-header-leading">
|
|
@@ -933,14 +1566,14 @@ class OnboardingStatusBadge {
|
|
|
933
1566
|
badgeClass = computed(() => {
|
|
934
1567
|
switch (this.status()) {
|
|
935
1568
|
case 'APPROVED':
|
|
936
|
-
return 'border-
|
|
1569
|
+
return 'border-(--rt-border-color) bg-(--rt-raised-background) text-emerald-700';
|
|
937
1570
|
case 'FAILED':
|
|
938
|
-
return 'border-
|
|
1571
|
+
return 'border-(--rt-border-color) bg-(--rt-raised-background) text-rose-700';
|
|
939
1572
|
case 'NEED_MORE_INFO':
|
|
940
|
-
return 'border-
|
|
1573
|
+
return 'border-(--rt-border-color) bg-(--rt-raised-background) text-amber-700';
|
|
941
1574
|
case 'SUBMITTED':
|
|
942
1575
|
case 'IN_REVIEW':
|
|
943
|
-
return 'border-
|
|
1576
|
+
return 'border-(--rt-border-color) bg-(--rt-raised-background) text-sky-700';
|
|
944
1577
|
case 'IN_PROGRESS':
|
|
945
1578
|
case 'DRAFT':
|
|
946
1579
|
return 'border-(--rt-border-color) bg-(--rt-raised-background) text-(--rt-text-secondary)';
|
|
@@ -982,7 +1615,7 @@ class OnboardingStepNav {
|
|
|
982
1615
|
activeStep = input('', ...(ngDevMode ? [{ debugName: "activeStep" }] : []));
|
|
983
1616
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingStepNav, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
984
1617
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingStepNav, isStandalone: true, selector: "rolatech-onboarding-step-nav", inputs: { steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: false, transformFunction: null }, activeStep: { classPropertyName: "activeStep", publicName: "activeStep", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
985
|
-
<nav class="grid gap-2 sm:grid-cols-2 lg:grid-cols-
|
|
1618
|
+
<nav class="grid gap-2 sm:grid-cols-2 lg:grid-cols-3" aria-label="Onboarding steps">
|
|
986
1619
|
@for (step of steps(); track step.step) {
|
|
987
1620
|
<a
|
|
988
1621
|
[routerLink]="step.accessible ? step.link : null"
|
|
@@ -1018,7 +1651,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
|
|
|
1018
1651
|
encapsulation: ViewEncapsulation.None,
|
|
1019
1652
|
host: { class: 'block' },
|
|
1020
1653
|
template: `
|
|
1021
|
-
<nav class="grid gap-2 sm:grid-cols-2 lg:grid-cols-
|
|
1654
|
+
<nav class="grid gap-2 sm:grid-cols-2 lg:grid-cols-3" aria-label="Onboarding steps">
|
|
1022
1655
|
@for (step of steps(); track step.step) {
|
|
1023
1656
|
<a
|
|
1024
1657
|
[routerLink]="step.accessible ? step.link : null"
|
|
@@ -1051,10 +1684,28 @@ class OnboardingDocumentUploader {
|
|
|
1051
1684
|
helperText = input('Upload a clear and readable file.', ...(ngDevMode ? [{ debugName: "helperText" }] : []));
|
|
1052
1685
|
documentType = input.required(...(ngDevMode ? [{ debugName: "documentType" }] : []));
|
|
1053
1686
|
document = input(null, ...(ngDevMode ? [{ debugName: "document" }] : []));
|
|
1687
|
+
warning = input(null, ...(ngDevMode ? [{ debugName: "warning" }] : []));
|
|
1054
1688
|
comment = input(null, ...(ngDevMode ? [{ debugName: "comment" }] : []));
|
|
1055
1689
|
busy = input(false, ...(ngDevMode ? [{ debugName: "busy" }] : []));
|
|
1690
|
+
locked = input(false, ...(ngDevMode ? [{ debugName: "locked" }] : []));
|
|
1691
|
+
uploading = input(false, ...(ngDevMode ? [{ debugName: "uploading" }] : []));
|
|
1692
|
+
progress = input(0, ...(ngDevMode ? [{ debugName: "progress" }] : []));
|
|
1693
|
+
pendingFilename = input(null, ...(ngDevMode ? [{ debugName: "pendingFilename" }] : []));
|
|
1694
|
+
uploadError = input(null, ...(ngDevMode ? [{ debugName: "uploadError" }] : []));
|
|
1056
1695
|
acceptedTypes = input('image/*,application/pdf', ...(ngDevMode ? [{ debugName: "acceptedTypes" }] : []));
|
|
1696
|
+
canPreview = input(false, ...(ngDevMode ? [{ debugName: "canPreview" }] : []));
|
|
1697
|
+
previewBusy = input(false, ...(ngDevMode ? [{ debugName: "previewBusy" }] : []));
|
|
1057
1698
|
fileSelected = output();
|
|
1699
|
+
previewRequested = output();
|
|
1700
|
+
buttonLabel = computed(() => {
|
|
1701
|
+
if (this.locked()) {
|
|
1702
|
+
return 'Locked';
|
|
1703
|
+
}
|
|
1704
|
+
if (this.uploading()) {
|
|
1705
|
+
return 'Uploading...';
|
|
1706
|
+
}
|
|
1707
|
+
return this.document() ? 'Replace' : 'Upload';
|
|
1708
|
+
}, ...(ngDevMode ? [{ debugName: "buttonLabel" }] : []));
|
|
1058
1709
|
onFileChange(event) {
|
|
1059
1710
|
const target = event.target;
|
|
1060
1711
|
const file = target.files?.[0];
|
|
@@ -1069,7 +1720,7 @@ class OnboardingDocumentUploader {
|
|
|
1069
1720
|
target.value = '';
|
|
1070
1721
|
}
|
|
1071
1722
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingDocumentUploader, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1072
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingDocumentUploader, isStandalone: true, selector: "rolatech-onboarding-document-uploader", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, documentType: { classPropertyName: "documentType", publicName: "documentType", isSignal: true, isRequired: true, transformFunction: null }, document: { classPropertyName: "document", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, comment: { classPropertyName: "comment", publicName: "comment", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null }, acceptedTypes: { classPropertyName: "acceptedTypes", publicName: "acceptedTypes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileSelected: "fileSelected" }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
1723
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingDocumentUploader, isStandalone: true, selector: "rolatech-onboarding-document-uploader", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, documentType: { classPropertyName: "documentType", publicName: "documentType", isSignal: true, isRequired: true, transformFunction: null }, document: { classPropertyName: "document", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, warning: { classPropertyName: "warning", publicName: "warning", isSignal: true, isRequired: false, transformFunction: null }, comment: { classPropertyName: "comment", publicName: "comment", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null }, locked: { classPropertyName: "locked", publicName: "locked", isSignal: true, isRequired: false, transformFunction: null }, uploading: { classPropertyName: "uploading", publicName: "uploading", isSignal: true, isRequired: false, transformFunction: null }, progress: { classPropertyName: "progress", publicName: "progress", isSignal: true, isRequired: false, transformFunction: null }, pendingFilename: { classPropertyName: "pendingFilename", publicName: "pendingFilename", isSignal: true, isRequired: false, transformFunction: null }, uploadError: { classPropertyName: "uploadError", publicName: "uploadError", isSignal: true, isRequired: false, transformFunction: null }, acceptedTypes: { classPropertyName: "acceptedTypes", publicName: "acceptedTypes", isSignal: true, isRequired: false, transformFunction: null }, canPreview: { classPropertyName: "canPreview", publicName: "canPreview", isSignal: true, isRequired: false, transformFunction: null }, previewBusy: { classPropertyName: "previewBusy", publicName: "previewBusy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileSelected: "fileSelected", previewRequested: "previewRequested" }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
1073
1724
|
<section class="rounded-2xl border border-(--rt-border-color) bg-(--rt-base-background) p-4 shadow-sm">
|
|
1074
1725
|
<div class="flex flex-wrap items-start justify-between gap-4">
|
|
1075
1726
|
<div>
|
|
@@ -1081,30 +1732,69 @@ class OnboardingDocumentUploader {
|
|
|
1081
1732
|
{{ existing.originalFilename }}
|
|
1082
1733
|
<span class="text-(--rt-text-secondary)">(v{{ existing.version }})</span>
|
|
1083
1734
|
</p>
|
|
1735
|
+
} @else if (pendingFilename()) {
|
|
1736
|
+
<p class="mt-2 text-sm text-(--rt-text-primary)">{{ pendingFilename() }}</p>
|
|
1084
1737
|
}
|
|
1085
1738
|
</div>
|
|
1086
1739
|
|
|
1087
|
-
<
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1740
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
1741
|
+
@if (document() && canPreview()) {
|
|
1742
|
+
<button
|
|
1743
|
+
type="button"
|
|
1744
|
+
class="rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm font-medium text-(--rt-text-primary) transition hover:bg-(--rt-base-background) disabled:cursor-not-allowed disabled:opacity-60"
|
|
1745
|
+
[disabled]="previewBusy()"
|
|
1746
|
+
(click)="previewRequested.emit()"
|
|
1747
|
+
>
|
|
1748
|
+
{{ previewBusy() ? 'Loading...' : 'Preview' }}
|
|
1749
|
+
</button>
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
<label
|
|
1753
|
+
class="inline-flex cursor-pointer items-center rounded-xl border border-(--rt-border-color) px-3 py-2 text-sm font-medium text-(--rt-text-primary) transition hover:bg-(--rt-raised-background)"
|
|
1754
|
+
[class.pointer-events-none]="busy() || locked()"
|
|
1755
|
+
[class.opacity-60]="busy() || locked()"
|
|
1756
|
+
>
|
|
1757
|
+
{{ buttonLabel() }}
|
|
1758
|
+
<input
|
|
1759
|
+
type="file"
|
|
1760
|
+
class="hidden"
|
|
1761
|
+
[accept]="acceptedTypes()"
|
|
1762
|
+
[disabled]="busy() || locked()"
|
|
1763
|
+
(change)="onFileChange($event)"
|
|
1764
|
+
/>
|
|
1765
|
+
</label>
|
|
1766
|
+
</div>
|
|
1101
1767
|
</div>
|
|
1102
1768
|
|
|
1769
|
+
@if (uploading()) {
|
|
1770
|
+
<div class="mt-3 space-y-2">
|
|
1771
|
+
<div class="flex items-center justify-between gap-3 text-xs text-(--rt-text-secondary)">
|
|
1772
|
+
<span>Uploading</span>
|
|
1773
|
+
<span>{{ progress() }}%</span>
|
|
1774
|
+
</div>
|
|
1775
|
+
<div class="h-2 overflow-hidden rounded-full bg-(--rt-raised-background)">
|
|
1776
|
+
<div class="h-full rounded-full bg-(--rt-brand-color) transition-all duration-200" [style.width.%]="progress()"></div>
|
|
1777
|
+
</div>
|
|
1778
|
+
</div>
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
@if (warning()) {
|
|
1782
|
+
<div class="mt-3 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-amber-800">
|
|
1783
|
+
{{ warning() }}
|
|
1784
|
+
</div>
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1103
1787
|
@if (comment()) {
|
|
1104
|
-
<div class="mt-3 rounded-xl border border-
|
|
1788
|
+
<div class="mt-3 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-amber-800">
|
|
1105
1789
|
{{ comment() }}
|
|
1106
1790
|
</div>
|
|
1107
1791
|
}
|
|
1792
|
+
|
|
1793
|
+
@if (uploadError()) {
|
|
1794
|
+
<div class="mt-3 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-rose-700">
|
|
1795
|
+
{{ uploadError() }}
|
|
1796
|
+
</div>
|
|
1797
|
+
}
|
|
1108
1798
|
</section>
|
|
1109
1799
|
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1110
1800
|
}
|
|
@@ -1128,34 +1818,243 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
|
|
|
1128
1818
|
{{ existing.originalFilename }}
|
|
1129
1819
|
<span class="text-(--rt-text-secondary)">(v{{ existing.version }})</span>
|
|
1130
1820
|
</p>
|
|
1821
|
+
} @else if (pendingFilename()) {
|
|
1822
|
+
<p class="mt-2 text-sm text-(--rt-text-primary)">{{ pendingFilename() }}</p>
|
|
1131
1823
|
}
|
|
1132
1824
|
</div>
|
|
1133
1825
|
|
|
1134
|
-
<
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1826
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
1827
|
+
@if (document() && canPreview()) {
|
|
1828
|
+
<button
|
|
1829
|
+
type="button"
|
|
1830
|
+
class="rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm font-medium text-(--rt-text-primary) transition hover:bg-(--rt-base-background) disabled:cursor-not-allowed disabled:opacity-60"
|
|
1831
|
+
[disabled]="previewBusy()"
|
|
1832
|
+
(click)="previewRequested.emit()"
|
|
1833
|
+
>
|
|
1834
|
+
{{ previewBusy() ? 'Loading...' : 'Preview' }}
|
|
1835
|
+
</button>
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
<label
|
|
1839
|
+
class="inline-flex cursor-pointer items-center rounded-xl border border-(--rt-border-color) px-3 py-2 text-sm font-medium text-(--rt-text-primary) transition hover:bg-(--rt-raised-background)"
|
|
1840
|
+
[class.pointer-events-none]="busy() || locked()"
|
|
1841
|
+
[class.opacity-60]="busy() || locked()"
|
|
1842
|
+
>
|
|
1843
|
+
{{ buttonLabel() }}
|
|
1844
|
+
<input
|
|
1845
|
+
type="file"
|
|
1846
|
+
class="hidden"
|
|
1847
|
+
[accept]="acceptedTypes()"
|
|
1848
|
+
[disabled]="busy() || locked()"
|
|
1849
|
+
(change)="onFileChange($event)"
|
|
1850
|
+
/>
|
|
1851
|
+
</label>
|
|
1852
|
+
</div>
|
|
1148
1853
|
</div>
|
|
1149
1854
|
|
|
1855
|
+
@if (uploading()) {
|
|
1856
|
+
<div class="mt-3 space-y-2">
|
|
1857
|
+
<div class="flex items-center justify-between gap-3 text-xs text-(--rt-text-secondary)">
|
|
1858
|
+
<span>Uploading</span>
|
|
1859
|
+
<span>{{ progress() }}%</span>
|
|
1860
|
+
</div>
|
|
1861
|
+
<div class="h-2 overflow-hidden rounded-full bg-(--rt-raised-background)">
|
|
1862
|
+
<div class="h-full rounded-full bg-(--rt-brand-color) transition-all duration-200" [style.width.%]="progress()"></div>
|
|
1863
|
+
</div>
|
|
1864
|
+
</div>
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
@if (warning()) {
|
|
1868
|
+
<div class="mt-3 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-amber-800">
|
|
1869
|
+
{{ warning() }}
|
|
1870
|
+
</div>
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1150
1873
|
@if (comment()) {
|
|
1151
|
-
<div class="mt-3 rounded-xl border border-
|
|
1874
|
+
<div class="mt-3 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-amber-800">
|
|
1152
1875
|
{{ comment() }}
|
|
1153
1876
|
</div>
|
|
1154
1877
|
}
|
|
1878
|
+
|
|
1879
|
+
@if (uploadError()) {
|
|
1880
|
+
<div class="mt-3 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-rose-700">
|
|
1881
|
+
{{ uploadError() }}
|
|
1882
|
+
</div>
|
|
1883
|
+
}
|
|
1155
1884
|
</section>
|
|
1156
1885
|
`,
|
|
1157
1886
|
}]
|
|
1158
|
-
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], documentType: [{ type: i0.Input, args: [{ isSignal: true, alias: "documentType", required: true }] }], document: [{ type: i0.Input, args: [{ isSignal: true, alias: "document", required: false }] }], comment: [{ type: i0.Input, args: [{ isSignal: true, alias: "comment", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], acceptedTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceptedTypes", required: false }] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }] } });
|
|
1887
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], documentType: [{ type: i0.Input, args: [{ isSignal: true, alias: "documentType", required: true }] }], document: [{ type: i0.Input, args: [{ isSignal: true, alias: "document", required: false }] }], warning: [{ type: i0.Input, args: [{ isSignal: true, alias: "warning", required: false }] }], comment: [{ type: i0.Input, args: [{ isSignal: true, alias: "comment", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], locked: [{ type: i0.Input, args: [{ isSignal: true, alias: "locked", required: false }] }], uploading: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploading", required: false }] }], progress: [{ type: i0.Input, args: [{ isSignal: true, alias: "progress", required: false }] }], pendingFilename: [{ type: i0.Input, args: [{ isSignal: true, alias: "pendingFilename", required: false }] }], uploadError: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadError", required: false }] }], acceptedTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceptedTypes", required: false }] }], canPreview: [{ type: i0.Input, args: [{ isSignal: true, alias: "canPreview", required: false }] }], previewBusy: [{ type: i0.Input, args: [{ isSignal: true, alias: "previewBusy", required: false }] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }], previewRequested: [{ type: i0.Output, args: ["previewRequested"] }] } });
|
|
1888
|
+
|
|
1889
|
+
class OnboardingDocumentPreviewDialog {
|
|
1890
|
+
open = input(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
|
|
1891
|
+
title = input(null, ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
1892
|
+
subtitle = input(null, ...(ngDevMode ? [{ debugName: "subtitle" }] : []));
|
|
1893
|
+
url = input(null, ...(ngDevMode ? [{ debugName: "url" }] : []));
|
|
1894
|
+
loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
1895
|
+
error = input(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
1896
|
+
closeRequested = output();
|
|
1897
|
+
sanitizer = inject(DomSanitizer);
|
|
1898
|
+
safeUrl = computed(() => {
|
|
1899
|
+
const url = this.url();
|
|
1900
|
+
return url ? this.sanitizer.bypassSecurityTrustResourceUrl(url) : null;
|
|
1901
|
+
}, ...(ngDevMode ? [{ debugName: "safeUrl" }] : []));
|
|
1902
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingDocumentPreviewDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1903
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingDocumentPreviewDialog, isStandalone: true, selector: "rolatech-onboarding-document-preview-dialog", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closeRequested: "closeRequested" }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
1904
|
+
@if (open()) {
|
|
1905
|
+
<div
|
|
1906
|
+
class="fixed inset-0 z-9200 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
|
|
1907
|
+
(click)="closeRequested.emit()"
|
|
1908
|
+
>
|
|
1909
|
+
<section
|
|
1910
|
+
class="flex max-h-[92vh] w-full max-w-6xl flex-col overflow-hidden rounded-xl border border-(--rt-border-color) bg-(--rt-base-background) shadow-2xl"
|
|
1911
|
+
(click)="$event.stopPropagation()"
|
|
1912
|
+
>
|
|
1913
|
+
<header
|
|
1914
|
+
class="flex flex-wrap items-start justify-between gap-4 border-b border-(--rt-border-color) bg-(--rt-raised-background) px-5 py-4"
|
|
1915
|
+
>
|
|
1916
|
+
<div class="min-w-0 space-y-1">
|
|
1917
|
+
<p class="text-[11px] font-semibold uppercase tracking-[0.24em] text-(--rt-text-secondary)">Document Preview</p>
|
|
1918
|
+
<h2 class="truncate text-lg font-semibold text-(--rt-text-primary)">{{ title() || 'Onboarding document' }}</h2>
|
|
1919
|
+
@if (subtitle()) {
|
|
1920
|
+
<p class="text-sm text-(--rt-text-secondary)">{{ subtitle() }}</p>
|
|
1921
|
+
}
|
|
1922
|
+
</div>
|
|
1923
|
+
|
|
1924
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
1925
|
+
@if (url()) {
|
|
1926
|
+
<a
|
|
1927
|
+
[href]="url()!"
|
|
1928
|
+
target="_blank"
|
|
1929
|
+
rel="noreferrer"
|
|
1930
|
+
class="rounded-full border border-(--rt-border-color) bg-(--rt-base-background) px-3 py-1.5 text-xs font-semibold text-(--rt-text-primary) transition hover:bg-(--rt-raised-background)"
|
|
1931
|
+
>
|
|
1932
|
+
Open in new tab
|
|
1933
|
+
</a>
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
<button
|
|
1937
|
+
type="button"
|
|
1938
|
+
class="rounded-full border border-(--rt-border-color) bg-(--rt-base-background) px-3 py-1.5 text-xs font-semibold text-(--rt-text-primary) transition hover:bg-(--rt-raised-background)"
|
|
1939
|
+
(click)="closeRequested.emit()"
|
|
1940
|
+
>
|
|
1941
|
+
Close
|
|
1942
|
+
</button>
|
|
1943
|
+
</div>
|
|
1944
|
+
</header>
|
|
1945
|
+
|
|
1946
|
+
<div class="min-h-[420px] flex-1 bg-(--rt-raised-background)">
|
|
1947
|
+
@if (loading()) {
|
|
1948
|
+
<div class="flex h-full items-center justify-center px-6 text-sm text-(--rt-text-secondary)">
|
|
1949
|
+
Loading preview...
|
|
1950
|
+
</div>
|
|
1951
|
+
} @else if (error()) {
|
|
1952
|
+
<div class="flex h-full items-center justify-center px-6">
|
|
1953
|
+
<div
|
|
1954
|
+
class="max-w-md rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) px-5 py-4 text-sm text-rose-700"
|
|
1955
|
+
>
|
|
1956
|
+
{{ error() }}
|
|
1957
|
+
</div>
|
|
1958
|
+
</div>
|
|
1959
|
+
} @else if (safeUrl()) {
|
|
1960
|
+
<iframe
|
|
1961
|
+
class="h-full min-h-[420px] w-full bg-(--rt-base-background)"
|
|
1962
|
+
[src]="safeUrl()"
|
|
1963
|
+
title="Onboarding document preview"
|
|
1964
|
+
></iframe>
|
|
1965
|
+
} @else {
|
|
1966
|
+
<div class="flex h-full items-center justify-center px-6 text-sm text-(--rt-text-secondary)">
|
|
1967
|
+
Preview unavailable for this document.
|
|
1968
|
+
</div>
|
|
1969
|
+
}
|
|
1970
|
+
</div>
|
|
1971
|
+
</section>
|
|
1972
|
+
</div>
|
|
1973
|
+
}
|
|
1974
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1975
|
+
}
|
|
1976
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingDocumentPreviewDialog, decorators: [{
|
|
1977
|
+
type: Component,
|
|
1978
|
+
args: [{
|
|
1979
|
+
selector: 'rolatech-onboarding-document-preview-dialog',
|
|
1980
|
+
standalone: true,
|
|
1981
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1982
|
+
encapsulation: ViewEncapsulation.None,
|
|
1983
|
+
host: { class: 'block' },
|
|
1984
|
+
template: `
|
|
1985
|
+
@if (open()) {
|
|
1986
|
+
<div
|
|
1987
|
+
class="fixed inset-0 z-9200 flex items-center justify-center bg-slate-950/70 p-4 backdrop-blur-sm"
|
|
1988
|
+
(click)="closeRequested.emit()"
|
|
1989
|
+
>
|
|
1990
|
+
<section
|
|
1991
|
+
class="flex max-h-[92vh] w-full max-w-6xl flex-col overflow-hidden rounded-xl border border-(--rt-border-color) bg-(--rt-base-background) shadow-2xl"
|
|
1992
|
+
(click)="$event.stopPropagation()"
|
|
1993
|
+
>
|
|
1994
|
+
<header
|
|
1995
|
+
class="flex flex-wrap items-start justify-between gap-4 border-b border-(--rt-border-color) bg-(--rt-raised-background) px-5 py-4"
|
|
1996
|
+
>
|
|
1997
|
+
<div class="min-w-0 space-y-1">
|
|
1998
|
+
<p class="text-[11px] font-semibold uppercase tracking-[0.24em] text-(--rt-text-secondary)">Document Preview</p>
|
|
1999
|
+
<h2 class="truncate text-lg font-semibold text-(--rt-text-primary)">{{ title() || 'Onboarding document' }}</h2>
|
|
2000
|
+
@if (subtitle()) {
|
|
2001
|
+
<p class="text-sm text-(--rt-text-secondary)">{{ subtitle() }}</p>
|
|
2002
|
+
}
|
|
2003
|
+
</div>
|
|
2004
|
+
|
|
2005
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
2006
|
+
@if (url()) {
|
|
2007
|
+
<a
|
|
2008
|
+
[href]="url()!"
|
|
2009
|
+
target="_blank"
|
|
2010
|
+
rel="noreferrer"
|
|
2011
|
+
class="rounded-full border border-(--rt-border-color) bg-(--rt-base-background) px-3 py-1.5 text-xs font-semibold text-(--rt-text-primary) transition hover:bg-(--rt-raised-background)"
|
|
2012
|
+
>
|
|
2013
|
+
Open in new tab
|
|
2014
|
+
</a>
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
<button
|
|
2018
|
+
type="button"
|
|
2019
|
+
class="rounded-full border border-(--rt-border-color) bg-(--rt-base-background) px-3 py-1.5 text-xs font-semibold text-(--rt-text-primary) transition hover:bg-(--rt-raised-background)"
|
|
2020
|
+
(click)="closeRequested.emit()"
|
|
2021
|
+
>
|
|
2022
|
+
Close
|
|
2023
|
+
</button>
|
|
2024
|
+
</div>
|
|
2025
|
+
</header>
|
|
2026
|
+
|
|
2027
|
+
<div class="min-h-[420px] flex-1 bg-(--rt-raised-background)">
|
|
2028
|
+
@if (loading()) {
|
|
2029
|
+
<div class="flex h-full items-center justify-center px-6 text-sm text-(--rt-text-secondary)">
|
|
2030
|
+
Loading preview...
|
|
2031
|
+
</div>
|
|
2032
|
+
} @else if (error()) {
|
|
2033
|
+
<div class="flex h-full items-center justify-center px-6">
|
|
2034
|
+
<div
|
|
2035
|
+
class="max-w-md rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) px-5 py-4 text-sm text-rose-700"
|
|
2036
|
+
>
|
|
2037
|
+
{{ error() }}
|
|
2038
|
+
</div>
|
|
2039
|
+
</div>
|
|
2040
|
+
} @else if (safeUrl()) {
|
|
2041
|
+
<iframe
|
|
2042
|
+
class="h-full min-h-[420px] w-full bg-(--rt-base-background)"
|
|
2043
|
+
[src]="safeUrl()"
|
|
2044
|
+
title="Onboarding document preview"
|
|
2045
|
+
></iframe>
|
|
2046
|
+
} @else {
|
|
2047
|
+
<div class="flex h-full items-center justify-center px-6 text-sm text-(--rt-text-secondary)">
|
|
2048
|
+
Preview unavailable for this document.
|
|
2049
|
+
</div>
|
|
2050
|
+
}
|
|
2051
|
+
</div>
|
|
2052
|
+
</section>
|
|
2053
|
+
</div>
|
|
2054
|
+
}
|
|
2055
|
+
`,
|
|
2056
|
+
}]
|
|
2057
|
+
}], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], url: [{ type: i0.Input, args: [{ isSignal: true, alias: "url", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }] } });
|
|
1159
2058
|
|
|
1160
2059
|
class OnboardingIssueList {
|
|
1161
2060
|
issues = input([], ...(ngDevMode ? [{ debugName: "issues" }] : []));
|
|
@@ -1176,11 +2075,13 @@ class OnboardingIssueList {
|
|
|
1176
2075
|
} @else {
|
|
1177
2076
|
<div class="space-y-3">
|
|
1178
2077
|
@for (issue of issues(); track issue.id) {
|
|
1179
|
-
<article class="rounded-xl border border-
|
|
2078
|
+
<article class="rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) p-3">
|
|
1180
2079
|
<div class="flex flex-wrap items-center justify-between gap-2">
|
|
1181
2080
|
<p class="text-xs font-semibold uppercase tracking-wide text-amber-800">{{ issueTypeLabel(issue.issueType) }}</p>
|
|
1182
2081
|
@if (issue.resolved) {
|
|
1183
|
-
<span
|
|
2082
|
+
<span
|
|
2083
|
+
class="rounded-full border border-(--rt-border-color) bg-(--rt-base-background) px-2 py-0.5 text-xs font-semibold text-emerald-700"
|
|
2084
|
+
>
|
|
1184
2085
|
Resolved
|
|
1185
2086
|
</span>
|
|
1186
2087
|
}
|
|
@@ -1224,11 +2125,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
|
|
|
1224
2125
|
} @else {
|
|
1225
2126
|
<div class="space-y-3">
|
|
1226
2127
|
@for (issue of issues(); track issue.id) {
|
|
1227
|
-
<article class="rounded-xl border border-
|
|
2128
|
+
<article class="rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) p-3">
|
|
1228
2129
|
<div class="flex flex-wrap items-center justify-between gap-2">
|
|
1229
2130
|
<p class="text-xs font-semibold uppercase tracking-wide text-amber-800">{{ issueTypeLabel(issue.issueType) }}</p>
|
|
1230
2131
|
@if (issue.resolved) {
|
|
1231
|
-
<span
|
|
2132
|
+
<span
|
|
2133
|
+
class="rounded-full border border-(--rt-border-color) bg-(--rt-base-background) px-2 py-0.5 text-xs font-semibold text-emerald-700"
|
|
2134
|
+
>
|
|
1232
2135
|
Resolved
|
|
1233
2136
|
</span>
|
|
1234
2137
|
}
|
|
@@ -1487,9 +2390,1124 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
|
|
|
1487
2390
|
}]
|
|
1488
2391
|
}], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }] } });
|
|
1489
2392
|
|
|
2393
|
+
class OnboardingReviewIssuesBanner {
|
|
2394
|
+
issues = input([], ...(ngDevMode ? [{ debugName: "issues" }] : []));
|
|
2395
|
+
status = input(null, ...(ngDevMode ? [{ debugName: "status" }] : []));
|
|
2396
|
+
unresolvedCount = computed(() => this.issues().filter((issue) => !issue.resolved).length, ...(ngDevMode ? [{ debugName: "unresolvedCount" }] : []));
|
|
2397
|
+
eyebrow = computed(() => (this.status() === 'NEED_MORE_INFO' ? 'More Information Required' : 'Review Status'), ...(ngDevMode ? [{ debugName: "eyebrow" }] : []));
|
|
2398
|
+
title = computed(() => this.status() === 'NEED_MORE_INFO'
|
|
2399
|
+
? 'Review feedback is blocking resubmission.'
|
|
2400
|
+
: 'The application is now waiting on reviewer action.', ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
2401
|
+
description = computed(() => this.status() === 'NEED_MORE_INFO'
|
|
2402
|
+
? 'Only the flagged fields and documents should be updated. Approved items stay read-only until review is complete.'
|
|
2403
|
+
: 'You can monitor status updates and any reviewer feedback from this flow.', ...(ngDevMode ? [{ debugName: "description" }] : []));
|
|
2404
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingReviewIssuesBanner, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2405
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.1", type: OnboardingReviewIssuesBanner, isStandalone: true, selector: "rolatech-onboarding-review-issues-banner", inputs: { issues: { classPropertyName: "issues", publicName: "issues", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
2406
|
+
<section
|
|
2407
|
+
class="rounded-2xl border border-(--rt-border-color) bg-(--rt-base-background) px-4 py-4 shadow-sm"
|
|
2408
|
+
[class]="status() === 'NEED_MORE_INFO' ? 'text-amber-950' : 'text-sky-950'"
|
|
2409
|
+
>
|
|
2410
|
+
<div class="flex flex-wrap items-start justify-between gap-3">
|
|
2411
|
+
<div class="space-y-1">
|
|
2412
|
+
<p class="text-xs font-semibold uppercase tracking-[0.2em]">{{ eyebrow() }}</p>
|
|
2413
|
+
<h3 class="text-base font-semibold">{{ title() }}</h3>
|
|
2414
|
+
<p class="text-sm opacity-80">{{ description() }}</p>
|
|
2415
|
+
</div>
|
|
2416
|
+
|
|
2417
|
+
<span class="rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-1 text-xs font-semibold">
|
|
2418
|
+
{{ unresolvedCount() }} open
|
|
2419
|
+
</span>
|
|
2420
|
+
</div>
|
|
2421
|
+
</section>
|
|
2422
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2423
|
+
}
|
|
2424
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingReviewIssuesBanner, decorators: [{
|
|
2425
|
+
type: Component,
|
|
2426
|
+
args: [{
|
|
2427
|
+
selector: 'rolatech-onboarding-review-issues-banner',
|
|
2428
|
+
standalone: true,
|
|
2429
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2430
|
+
encapsulation: ViewEncapsulation.None,
|
|
2431
|
+
host: { class: 'block' },
|
|
2432
|
+
template: `
|
|
2433
|
+
<section
|
|
2434
|
+
class="rounded-2xl border border-(--rt-border-color) bg-(--rt-base-background) px-4 py-4 shadow-sm"
|
|
2435
|
+
[class]="status() === 'NEED_MORE_INFO' ? 'text-amber-950' : 'text-sky-950'"
|
|
2436
|
+
>
|
|
2437
|
+
<div class="flex flex-wrap items-start justify-between gap-3">
|
|
2438
|
+
<div class="space-y-1">
|
|
2439
|
+
<p class="text-xs font-semibold uppercase tracking-[0.2em]">{{ eyebrow() }}</p>
|
|
2440
|
+
<h3 class="text-base font-semibold">{{ title() }}</h3>
|
|
2441
|
+
<p class="text-sm opacity-80">{{ description() }}</p>
|
|
2442
|
+
</div>
|
|
2443
|
+
|
|
2444
|
+
<span class="rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-1 text-xs font-semibold">
|
|
2445
|
+
{{ unresolvedCount() }} open
|
|
2446
|
+
</span>
|
|
2447
|
+
</div>
|
|
2448
|
+
</section>
|
|
2449
|
+
`,
|
|
2450
|
+
}]
|
|
2451
|
+
}], propDecorators: { issues: [{ type: i0.Input, args: [{ isSignal: true, alias: "issues", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }] } });
|
|
2452
|
+
|
|
2453
|
+
class OnboardingQualificationUploadSection {
|
|
2454
|
+
documentSpecs = input([], ...(ngDevMode ? [{ debugName: "documentSpecs" }] : []));
|
|
2455
|
+
documents = input([], ...(ngDevMode ? [{ debugName: "documents" }] : []));
|
|
2456
|
+
issueComments = input({}, ...(ngDevMode ? [{ debugName: "issueComments" }] : []));
|
|
2457
|
+
lockedDocuments = input([], ...(ngDevMode ? [{ debugName: "lockedDocuments" }] : []));
|
|
2458
|
+
uploadStates = input({}, ...(ngDevMode ? [{ debugName: "uploadStates" }] : []));
|
|
2459
|
+
busy = input(false, ...(ngDevMode ? [{ debugName: "busy" }] : []));
|
|
2460
|
+
fileSelected = output();
|
|
2461
|
+
uploadedRequiredCount = computed(() => {
|
|
2462
|
+
const uploadedTypes = new Set(this.documents().map((item) => item.documentType));
|
|
2463
|
+
return this.documentSpecs().filter((spec) => uploadedTypes.has(spec.type)).length;
|
|
2464
|
+
}, ...(ngDevMode ? [{ debugName: "uploadedRequiredCount" }] : []));
|
|
2465
|
+
documentFor(documentType) {
|
|
2466
|
+
return this.documents().find((item) => item.documentType === documentType) ?? null;
|
|
2467
|
+
}
|
|
2468
|
+
issueCommentFor(documentType) {
|
|
2469
|
+
return this.issueComments()[documentType] ?? null;
|
|
2470
|
+
}
|
|
2471
|
+
isLocked(documentType) {
|
|
2472
|
+
return this.lockedDocuments().includes(documentType);
|
|
2473
|
+
}
|
|
2474
|
+
uploadStateFor(documentType) {
|
|
2475
|
+
return (this.uploadStates()[documentType] ?? {
|
|
2476
|
+
filename: null,
|
|
2477
|
+
progress: 0,
|
|
2478
|
+
status: 'idle',
|
|
2479
|
+
error: null,
|
|
2480
|
+
});
|
|
2481
|
+
}
|
|
2482
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingQualificationUploadSection, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2483
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingQualificationUploadSection, isStandalone: true, selector: "rolatech-onboarding-qualification-upload-section", inputs: { documentSpecs: { classPropertyName: "documentSpecs", publicName: "documentSpecs", isSignal: true, isRequired: false, transformFunction: null }, documents: { classPropertyName: "documents", publicName: "documents", isSignal: true, isRequired: false, transformFunction: null }, issueComments: { classPropertyName: "issueComments", publicName: "issueComments", isSignal: true, isRequired: false, transformFunction: null }, lockedDocuments: { classPropertyName: "lockedDocuments", publicName: "lockedDocuments", isSignal: true, isRequired: false, transformFunction: null }, uploadStates: { classPropertyName: "uploadStates", publicName: "uploadStates", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileSelected: "fileSelected" }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
2484
|
+
<section class="space-y-4 rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm">
|
|
2485
|
+
<div class="flex flex-wrap items-start justify-between gap-4">
|
|
2486
|
+
<div class="space-y-2">
|
|
2487
|
+
<h2 class="text-lg font-semibold text-(--rt-text-primary)">Qualification Documents</h2>
|
|
2488
|
+
<p class="max-w-2xl text-sm text-(--rt-text-secondary)">
|
|
2489
|
+
Upload the documents needed for review. File types are limited to PDF, JPG, and PNG.
|
|
2490
|
+
</p>
|
|
2491
|
+
</div>
|
|
2492
|
+
|
|
2493
|
+
<span class="rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-1 text-xs font-semibold text-(--rt-text-secondary)">
|
|
2494
|
+
{{ uploadedRequiredCount() }}/{{ documentSpecs().length }} ready
|
|
2495
|
+
</span>
|
|
2496
|
+
</div>
|
|
2497
|
+
|
|
2498
|
+
@for (documentSpec of documentSpecs(); track documentSpec.type) {
|
|
2499
|
+
<rolatech-onboarding-document-uploader
|
|
2500
|
+
[label]="documentSpec.label"
|
|
2501
|
+
[helperText]="documentSpec.helperText ?? 'Upload a clear and readable file.'"
|
|
2502
|
+
[documentType]="documentSpec.type"
|
|
2503
|
+
[document]="documentFor(documentSpec.type)"
|
|
2504
|
+
[warning]="documentSpec.warning ?? null"
|
|
2505
|
+
[comment]="issueCommentFor(documentSpec.type)"
|
|
2506
|
+
[busy]="busy()"
|
|
2507
|
+
[locked]="isLocked(documentSpec.type)"
|
|
2508
|
+
[uploading]="uploadStateFor(documentSpec.type).status === 'uploading'"
|
|
2509
|
+
[progress]="uploadStateFor(documentSpec.type).progress"
|
|
2510
|
+
[pendingFilename]="uploadStateFor(documentSpec.type).filename"
|
|
2511
|
+
[uploadError]="uploadStateFor(documentSpec.type).error"
|
|
2512
|
+
(fileSelected)="fileSelected.emit($event)"
|
|
2513
|
+
/>
|
|
2514
|
+
}
|
|
2515
|
+
</section>
|
|
2516
|
+
`, isInline: true, dependencies: [{ kind: "component", type: OnboardingDocumentUploader, selector: "rolatech-onboarding-document-uploader", inputs: ["label", "helperText", "documentType", "document", "warning", "comment", "busy", "locked", "uploading", "progress", "pendingFilename", "uploadError", "acceptedTypes", "canPreview", "previewBusy"], outputs: ["fileSelected", "previewRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2517
|
+
}
|
|
2518
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingQualificationUploadSection, decorators: [{
|
|
2519
|
+
type: Component,
|
|
2520
|
+
args: [{
|
|
2521
|
+
selector: 'rolatech-onboarding-qualification-upload-section',
|
|
2522
|
+
standalone: true,
|
|
2523
|
+
imports: [OnboardingDocumentUploader],
|
|
2524
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2525
|
+
encapsulation: ViewEncapsulation.None,
|
|
2526
|
+
host: { class: 'block' },
|
|
2527
|
+
template: `
|
|
2528
|
+
<section class="space-y-4 rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm">
|
|
2529
|
+
<div class="flex flex-wrap items-start justify-between gap-4">
|
|
2530
|
+
<div class="space-y-2">
|
|
2531
|
+
<h2 class="text-lg font-semibold text-(--rt-text-primary)">Qualification Documents</h2>
|
|
2532
|
+
<p class="max-w-2xl text-sm text-(--rt-text-secondary)">
|
|
2533
|
+
Upload the documents needed for review. File types are limited to PDF, JPG, and PNG.
|
|
2534
|
+
</p>
|
|
2535
|
+
</div>
|
|
2536
|
+
|
|
2537
|
+
<span class="rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-1 text-xs font-semibold text-(--rt-text-secondary)">
|
|
2538
|
+
{{ uploadedRequiredCount() }}/{{ documentSpecs().length }} ready
|
|
2539
|
+
</span>
|
|
2540
|
+
</div>
|
|
2541
|
+
|
|
2542
|
+
@for (documentSpec of documentSpecs(); track documentSpec.type) {
|
|
2543
|
+
<rolatech-onboarding-document-uploader
|
|
2544
|
+
[label]="documentSpec.label"
|
|
2545
|
+
[helperText]="documentSpec.helperText ?? 'Upload a clear and readable file.'"
|
|
2546
|
+
[documentType]="documentSpec.type"
|
|
2547
|
+
[document]="documentFor(documentSpec.type)"
|
|
2548
|
+
[warning]="documentSpec.warning ?? null"
|
|
2549
|
+
[comment]="issueCommentFor(documentSpec.type)"
|
|
2550
|
+
[busy]="busy()"
|
|
2551
|
+
[locked]="isLocked(documentSpec.type)"
|
|
2552
|
+
[uploading]="uploadStateFor(documentSpec.type).status === 'uploading'"
|
|
2553
|
+
[progress]="uploadStateFor(documentSpec.type).progress"
|
|
2554
|
+
[pendingFilename]="uploadStateFor(documentSpec.type).filename"
|
|
2555
|
+
[uploadError]="uploadStateFor(documentSpec.type).error"
|
|
2556
|
+
(fileSelected)="fileSelected.emit($event)"
|
|
2557
|
+
/>
|
|
2558
|
+
}
|
|
2559
|
+
</section>
|
|
2560
|
+
`,
|
|
2561
|
+
}]
|
|
2562
|
+
}], propDecorators: { documentSpecs: [{ type: i0.Input, args: [{ isSignal: true, alias: "documentSpecs", required: false }] }], documents: [{ type: i0.Input, args: [{ isSignal: true, alias: "documents", required: false }] }], issueComments: [{ type: i0.Input, args: [{ isSignal: true, alias: "issueComments", required: false }] }], lockedDocuments: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedDocuments", required: false }] }], uploadStates: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadStates", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }] } });
|
|
2563
|
+
|
|
2564
|
+
class OnboardingVatSection {
|
|
2565
|
+
form = input.required(...(ngDevMode ? [{ debugName: "form" }] : []));
|
|
2566
|
+
applicantType = input.required(...(ngDevMode ? [{ debugName: "applicantType" }] : []));
|
|
2567
|
+
companyCountry = input.required(...(ngDevMode ? [{ debugName: "companyCountry" }] : []));
|
|
2568
|
+
fieldLocks = input({}, ...(ngDevMode ? [{ debugName: "fieldLocks" }] : []));
|
|
2569
|
+
vatRateLabel = input('20%', ...(ngDevMode ? [{ debugName: "vatRateLabel" }] : []));
|
|
2570
|
+
issueComments = input({}, ...(ngDevMode ? [{ debugName: "issueComments" }] : []));
|
|
2571
|
+
isIndividual = computed(() => this.applicantType() === 'INDIVIDUAL', ...(ngDevMode ? [{ debugName: "isIndividual" }] : []));
|
|
2572
|
+
currentVatMode = computed(() => {
|
|
2573
|
+
const value = this.form().get('vatMode')?.value;
|
|
2574
|
+
return (typeof value === 'string' ? value : 'NON_VAT_REGISTERED');
|
|
2575
|
+
}, ...(ngDevMode ? [{ debugName: "currentVatMode" }] : []));
|
|
2576
|
+
vatNumberValue = computed(() => String(this.form().get('vatNumber')?.value ?? '').trim(), ...(ngDevMode ? [{ debugName: "vatNumberValue" }] : []));
|
|
2577
|
+
sectionReadOnly = computed(() => this.isFieldLocked('vatMode') && this.isFieldLocked('vatNumber'), ...(ngDevMode ? [{ debugName: "sectionReadOnly" }] : []));
|
|
2578
|
+
vatModeLabel = onboardingVatModeLabel;
|
|
2579
|
+
fieldComment(fieldKey) {
|
|
2580
|
+
return this.issueComments()[fieldKey] ?? null;
|
|
2581
|
+
}
|
|
2582
|
+
isFieldLocked(fieldKey) {
|
|
2583
|
+
return this.fieldLocks()[fieldKey] ?? false;
|
|
2584
|
+
}
|
|
2585
|
+
hasError(controlName, errorCode) {
|
|
2586
|
+
const control = this.form().get(controlName);
|
|
2587
|
+
return Boolean(control?.touched && control.hasError(errorCode));
|
|
2588
|
+
}
|
|
2589
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingVatSection, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2590
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingVatSection, isStandalone: true, selector: "rolatech-onboarding-vat-section", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, applicantType: { classPropertyName: "applicantType", publicName: "applicantType", isSignal: true, isRequired: true, transformFunction: null }, companyCountry: { classPropertyName: "companyCountry", publicName: "companyCountry", isSignal: true, isRequired: true, transformFunction: null }, fieldLocks: { classPropertyName: "fieldLocks", publicName: "fieldLocks", isSignal: true, isRequired: false, transformFunction: null }, vatRateLabel: { classPropertyName: "vatRateLabel", publicName: "vatRateLabel", isSignal: true, isRequired: false, transformFunction: null }, issueComments: { classPropertyName: "issueComments", publicName: "issueComments", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
2591
|
+
<section class="space-y-4 rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm">
|
|
2592
|
+
<div class="space-y-2">
|
|
2593
|
+
<h2 class="text-lg font-semibold text-(--rt-text-primary)">VAT and Settlement Terms</h2>
|
|
2594
|
+
<p class="max-w-2xl text-sm text-(--rt-text-secondary)">
|
|
2595
|
+
We calculate final payment as commission base x (1 + VAT rate). The current VAT rate for this application is {{ vatRateLabel() }}.
|
|
2596
|
+
</p>
|
|
2597
|
+
</div>
|
|
2598
|
+
|
|
2599
|
+
@if (isIndividual()) {
|
|
2600
|
+
<div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-4 text-sm text-emerald-900">
|
|
2601
|
+
Individual applicants are treated as non-VAT registered. VAT is fixed at 0% and no VAT number is required.
|
|
2602
|
+
</div>
|
|
2603
|
+
} @else {
|
|
2604
|
+
@if (sectionReadOnly()) {
|
|
2605
|
+
<div class="grid gap-4 md:grid-cols-2">
|
|
2606
|
+
<article class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) p-4">
|
|
2607
|
+
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">VAT mode</p>
|
|
2608
|
+
<p class="mt-2 text-sm font-medium text-(--rt-text-primary)">{{ vatModeLabel(currentVatMode()) }}</p>
|
|
2609
|
+
</article>
|
|
2610
|
+
|
|
2611
|
+
<article class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) p-4">
|
|
2612
|
+
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">VAT number</p>
|
|
2613
|
+
<p class="mt-2 text-sm font-medium text-(--rt-text-primary)">{{ vatNumberValue() || 'Pending manual verification' }}</p>
|
|
2614
|
+
</article>
|
|
2615
|
+
</div>
|
|
2616
|
+
} @else {
|
|
2617
|
+
<form [formGroup]="form()" class="grid gap-4 md:grid-cols-2">
|
|
2618
|
+
@if (isFieldLocked('vatMode')) {
|
|
2619
|
+
<article class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) p-4">
|
|
2620
|
+
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">VAT mode</p>
|
|
2621
|
+
<p class="mt-2 text-sm font-medium text-(--rt-text-primary)">{{ vatModeLabel(currentVatMode()) }}</p>
|
|
2622
|
+
</article>
|
|
2623
|
+
} @else {
|
|
2624
|
+
<label class="grid gap-2">
|
|
2625
|
+
<span class="text-sm font-medium text-(--rt-text-secondary)">VAT mode</span>
|
|
2626
|
+
<select
|
|
2627
|
+
formControlName="vatMode"
|
|
2628
|
+
class="h-11 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 text-(--rt-text-primary)"
|
|
2629
|
+
>
|
|
2630
|
+
<option value="VAT_REGISTERED">VAT registered</option>
|
|
2631
|
+
<option value="MANUAL_REVIEW_REQUIRED">Manual review required</option>
|
|
2632
|
+
</select>
|
|
2633
|
+
</label>
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
@if (currentVatMode() === 'VAT_REGISTERED') {
|
|
2637
|
+
<label class="grid gap-2">
|
|
2638
|
+
<span class="text-sm font-medium text-(--rt-text-secondary)">VAT number</span>
|
|
2639
|
+
<input formControlName="vatNumber" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('vatNumber')" />
|
|
2640
|
+
</label>
|
|
2641
|
+
}
|
|
2642
|
+
</form>
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
@if (fieldComment('vatMode')) {
|
|
2646
|
+
<div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-3 text-sm text-amber-800">
|
|
2647
|
+
{{ fieldComment('vatMode') }}
|
|
2648
|
+
</div>
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
@if (fieldComment('vatNumber')) {
|
|
2652
|
+
<div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-3 text-sm text-amber-800">
|
|
2653
|
+
{{ fieldComment('vatNumber') }}
|
|
2654
|
+
</div>
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
@if (currentVatMode() === 'MANUAL_REVIEW_REQUIRED') {
|
|
2658
|
+
<div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-3 text-sm text-amber-900">
|
|
2659
|
+
Manual verification is required because a VAT number is unavailable. This keeps the application inside the company review flow.
|
|
2660
|
+
</div>
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
@if (companyCountry() === 'CN') {
|
|
2664
|
+
<p class="text-sm text-(--rt-text-secondary)">
|
|
2665
|
+
China companies still follow company VAT rules. If a VAT number is unavailable, choose manual review instead of leaving the section incomplete.
|
|
2666
|
+
</p>
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
@if (hasError('vatNumber', 'required')) {
|
|
2670
|
+
<p class="text-sm text-rose-700">VAT number is required when VAT mode is set to VAT registered.</p>
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
</section>
|
|
2674
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2675
|
+
}
|
|
2676
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingVatSection, decorators: [{
|
|
2677
|
+
type: Component,
|
|
2678
|
+
args: [{
|
|
2679
|
+
selector: 'rolatech-onboarding-vat-section',
|
|
2680
|
+
standalone: true,
|
|
2681
|
+
imports: [ReactiveFormsModule],
|
|
2682
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2683
|
+
encapsulation: ViewEncapsulation.None,
|
|
2684
|
+
host: { class: 'block' },
|
|
2685
|
+
template: `
|
|
2686
|
+
<section class="space-y-4 rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm">
|
|
2687
|
+
<div class="space-y-2">
|
|
2688
|
+
<h2 class="text-lg font-semibold text-(--rt-text-primary)">VAT and Settlement Terms</h2>
|
|
2689
|
+
<p class="max-w-2xl text-sm text-(--rt-text-secondary)">
|
|
2690
|
+
We calculate final payment as commission base x (1 + VAT rate). The current VAT rate for this application is {{ vatRateLabel() }}.
|
|
2691
|
+
</p>
|
|
2692
|
+
</div>
|
|
2693
|
+
|
|
2694
|
+
@if (isIndividual()) {
|
|
2695
|
+
<div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-4 text-sm text-emerald-900">
|
|
2696
|
+
Individual applicants are treated as non-VAT registered. VAT is fixed at 0% and no VAT number is required.
|
|
2697
|
+
</div>
|
|
2698
|
+
} @else {
|
|
2699
|
+
@if (sectionReadOnly()) {
|
|
2700
|
+
<div class="grid gap-4 md:grid-cols-2">
|
|
2701
|
+
<article class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) p-4">
|
|
2702
|
+
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">VAT mode</p>
|
|
2703
|
+
<p class="mt-2 text-sm font-medium text-(--rt-text-primary)">{{ vatModeLabel(currentVatMode()) }}</p>
|
|
2704
|
+
</article>
|
|
2705
|
+
|
|
2706
|
+
<article class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) p-4">
|
|
2707
|
+
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">VAT number</p>
|
|
2708
|
+
<p class="mt-2 text-sm font-medium text-(--rt-text-primary)">{{ vatNumberValue() || 'Pending manual verification' }}</p>
|
|
2709
|
+
</article>
|
|
2710
|
+
</div>
|
|
2711
|
+
} @else {
|
|
2712
|
+
<form [formGroup]="form()" class="grid gap-4 md:grid-cols-2">
|
|
2713
|
+
@if (isFieldLocked('vatMode')) {
|
|
2714
|
+
<article class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) p-4">
|
|
2715
|
+
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">VAT mode</p>
|
|
2716
|
+
<p class="mt-2 text-sm font-medium text-(--rt-text-primary)">{{ vatModeLabel(currentVatMode()) }}</p>
|
|
2717
|
+
</article>
|
|
2718
|
+
} @else {
|
|
2719
|
+
<label class="grid gap-2">
|
|
2720
|
+
<span class="text-sm font-medium text-(--rt-text-secondary)">VAT mode</span>
|
|
2721
|
+
<select
|
|
2722
|
+
formControlName="vatMode"
|
|
2723
|
+
class="h-11 rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 text-(--rt-text-primary)"
|
|
2724
|
+
>
|
|
2725
|
+
<option value="VAT_REGISTERED">VAT registered</option>
|
|
2726
|
+
<option value="MANUAL_REVIEW_REQUIRED">Manual review required</option>
|
|
2727
|
+
</select>
|
|
2728
|
+
</label>
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
@if (currentVatMode() === 'VAT_REGISTERED') {
|
|
2732
|
+
<label class="grid gap-2">
|
|
2733
|
+
<span class="text-sm font-medium text-(--rt-text-secondary)">VAT number</span>
|
|
2734
|
+
<input formControlName="vatNumber" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('vatNumber')" />
|
|
2735
|
+
</label>
|
|
2736
|
+
}
|
|
2737
|
+
</form>
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
@if (fieldComment('vatMode')) {
|
|
2741
|
+
<div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-3 text-sm text-amber-800">
|
|
2742
|
+
{{ fieldComment('vatMode') }}
|
|
2743
|
+
</div>
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
@if (fieldComment('vatNumber')) {
|
|
2747
|
+
<div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-3 text-sm text-amber-800">
|
|
2748
|
+
{{ fieldComment('vatNumber') }}
|
|
2749
|
+
</div>
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
@if (currentVatMode() === 'MANUAL_REVIEW_REQUIRED') {
|
|
2753
|
+
<div class="rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-4 py-3 text-sm text-amber-900">
|
|
2754
|
+
Manual verification is required because a VAT number is unavailable. This keeps the application inside the company review flow.
|
|
2755
|
+
</div>
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
@if (companyCountry() === 'CN') {
|
|
2759
|
+
<p class="text-sm text-(--rt-text-secondary)">
|
|
2760
|
+
China companies still follow company VAT rules. If a VAT number is unavailable, choose manual review instead of leaving the section incomplete.
|
|
2761
|
+
</p>
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
@if (hasError('vatNumber', 'required')) {
|
|
2765
|
+
<p class="text-sm text-rose-700">VAT number is required when VAT mode is set to VAT registered.</p>
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
</section>
|
|
2769
|
+
`,
|
|
2770
|
+
}]
|
|
2771
|
+
}], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], applicantType: [{ type: i0.Input, args: [{ isSignal: true, alias: "applicantType", required: true }] }], companyCountry: [{ type: i0.Input, args: [{ isSignal: true, alias: "companyCountry", required: true }] }], fieldLocks: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldLocks", required: false }] }], vatRateLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "vatRateLabel", required: false }] }], issueComments: [{ type: i0.Input, args: [{ isSignal: true, alias: "issueComments", required: false }] }] } });
|
|
2772
|
+
|
|
2773
|
+
class OnboardingBankingSection {
|
|
2774
|
+
form = input.required(...(ngDevMode ? [{ debugName: "form" }] : []));
|
|
2775
|
+
fieldLocks = input({}, ...(ngDevMode ? [{ debugName: "fieldLocks" }] : []));
|
|
2776
|
+
expectedAccountHolderName = input('', ...(ngDevMode ? [{ debugName: "expectedAccountHolderName" }] : []));
|
|
2777
|
+
issueComments = input({}, ...(ngDevMode ? [{ debugName: "issueComments" }] : []));
|
|
2778
|
+
fieldComment(fieldKey) {
|
|
2779
|
+
return this.issueComments()[fieldKey] ?? null;
|
|
2780
|
+
}
|
|
2781
|
+
isFieldLocked(fieldKey) {
|
|
2782
|
+
return this.fieldLocks()[fieldKey] ?? false;
|
|
2783
|
+
}
|
|
2784
|
+
hasError(controlName, errorCode) {
|
|
2785
|
+
const control = this.form().get(controlName);
|
|
2786
|
+
return Boolean(control?.touched && control.hasError(errorCode));
|
|
2787
|
+
}
|
|
2788
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingBankingSection, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2789
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingBankingSection, isStandalone: true, selector: "rolatech-onboarding-banking-section", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, fieldLocks: { classPropertyName: "fieldLocks", publicName: "fieldLocks", isSignal: true, isRequired: false, transformFunction: null }, expectedAccountHolderName: { classPropertyName: "expectedAccountHolderName", publicName: "expectedAccountHolderName", isSignal: true, isRequired: false, transformFunction: null }, issueComments: { classPropertyName: "issueComments", publicName: "issueComments", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
2790
|
+
<section class="space-y-4 rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm">
|
|
2791
|
+
<div class="space-y-2">
|
|
2792
|
+
<h2 class="text-lg font-semibold text-(--rt-text-primary)">UK Settlement Bank Account</h2>
|
|
2793
|
+
<p class="max-w-2xl text-sm text-(--rt-text-secondary)">
|
|
2794
|
+
Settlement is restricted to a UK local bank account. The account holder name must match the legal applicant name.
|
|
2795
|
+
</p>
|
|
2796
|
+
</div>
|
|
2797
|
+
|
|
2798
|
+
<form [formGroup]="form()" class="grid gap-4 md:grid-cols-2">
|
|
2799
|
+
<label class="grid gap-2">
|
|
2800
|
+
<span class="text-sm font-medium text-(--rt-text-secondary)">Bank name</span>
|
|
2801
|
+
<input formControlName="bankName" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('bankName')" />
|
|
2802
|
+
@if (fieldComment('bankName')) {
|
|
2803
|
+
<span class="text-xs text-amber-800">{{ fieldComment('bankName') }}</span>
|
|
2804
|
+
}
|
|
2805
|
+
@if (hasError('bankName', 'required')) {
|
|
2806
|
+
<span class="text-xs text-rose-700">Bank name is required.</span>
|
|
2807
|
+
}
|
|
2808
|
+
</label>
|
|
2809
|
+
|
|
2810
|
+
<label class="grid gap-2">
|
|
2811
|
+
<span class="text-sm font-medium text-(--rt-text-secondary)">Account holder name</span>
|
|
2812
|
+
<input
|
|
2813
|
+
formControlName="accountHolderName"
|
|
2814
|
+
class="h-11 rounded-xl border border-(--rt-border-color) px-3"
|
|
2815
|
+
[readonly]="isFieldLocked('accountHolderName')"
|
|
2816
|
+
/>
|
|
2817
|
+
<span class="text-xs text-(--rt-text-secondary)">Expected name: {{ expectedAccountHolderName() || 'Complete the profile section first' }}</span>
|
|
2818
|
+
@if (fieldComment('accountHolderName')) {
|
|
2819
|
+
<span class="text-xs text-amber-800">{{ fieldComment('accountHolderName') }}</span>
|
|
2820
|
+
}
|
|
2821
|
+
@if (hasError('accountHolderName', 'required')) {
|
|
2822
|
+
<span class="text-xs text-rose-700">Account holder name is required.</span>
|
|
2823
|
+
}
|
|
2824
|
+
@if (hasError('accountHolderName', 'accountHolderMismatch')) {
|
|
2825
|
+
<span class="text-xs text-rose-700">Account holder name must match the applicant or company legal name.</span>
|
|
2826
|
+
}
|
|
2827
|
+
</label>
|
|
2828
|
+
|
|
2829
|
+
<label class="grid gap-2">
|
|
2830
|
+
<span class="text-sm font-medium text-(--rt-text-secondary)">Sort code</span>
|
|
2831
|
+
<input formControlName="sortCode" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('sortCode')" />
|
|
2832
|
+
<span class="text-xs text-(--rt-text-secondary)">Use 6 digits. Example: 01-02-03.</span>
|
|
2833
|
+
@if (fieldComment('sortCode')) {
|
|
2834
|
+
<span class="text-xs text-amber-800">{{ fieldComment('sortCode') }}</span>
|
|
2835
|
+
}
|
|
2836
|
+
@if (hasError('sortCode', 'required')) {
|
|
2837
|
+
<span class="text-xs text-rose-700">Sort code is required.</span>
|
|
2838
|
+
}
|
|
2839
|
+
@if (hasError('sortCode', 'pattern')) {
|
|
2840
|
+
<span class="text-xs text-rose-700">Sort code must contain exactly 6 digits.</span>
|
|
2841
|
+
}
|
|
2842
|
+
</label>
|
|
2843
|
+
|
|
2844
|
+
<label class="grid gap-2">
|
|
2845
|
+
<span class="text-sm font-medium text-(--rt-text-secondary)">Account number</span>
|
|
2846
|
+
<input formControlName="accountNumber" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('accountNumber')" />
|
|
2847
|
+
<span class="text-xs text-(--rt-text-secondary)">Account number must contain exactly 8 digits.</span>
|
|
2848
|
+
@if (fieldComment('accountNumber')) {
|
|
2849
|
+
<span class="text-xs text-amber-800">{{ fieldComment('accountNumber') }}</span>
|
|
2850
|
+
}
|
|
2851
|
+
@if (hasError('accountNumber', 'required')) {
|
|
2852
|
+
<span class="text-xs text-rose-700">Account number is required.</span>
|
|
2853
|
+
}
|
|
2854
|
+
@if (hasError('accountNumber', 'pattern')) {
|
|
2855
|
+
<span class="text-xs text-rose-700">Account number must contain exactly 8 digits.</span>
|
|
2856
|
+
}
|
|
2857
|
+
</label>
|
|
2858
|
+
</form>
|
|
2859
|
+
</section>
|
|
2860
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2861
|
+
}
|
|
2862
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingBankingSection, decorators: [{
|
|
2863
|
+
type: Component,
|
|
2864
|
+
args: [{
|
|
2865
|
+
selector: 'rolatech-onboarding-banking-section',
|
|
2866
|
+
standalone: true,
|
|
2867
|
+
imports: [ReactiveFormsModule],
|
|
2868
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2869
|
+
encapsulation: ViewEncapsulation.None,
|
|
2870
|
+
host: { class: 'block' },
|
|
2871
|
+
template: `
|
|
2872
|
+
<section class="space-y-4 rounded-3xl border border-(--rt-border-color) bg-(--rt-base-background) p-6 shadow-sm">
|
|
2873
|
+
<div class="space-y-2">
|
|
2874
|
+
<h2 class="text-lg font-semibold text-(--rt-text-primary)">UK Settlement Bank Account</h2>
|
|
2875
|
+
<p class="max-w-2xl text-sm text-(--rt-text-secondary)">
|
|
2876
|
+
Settlement is restricted to a UK local bank account. The account holder name must match the legal applicant name.
|
|
2877
|
+
</p>
|
|
2878
|
+
</div>
|
|
2879
|
+
|
|
2880
|
+
<form [formGroup]="form()" class="grid gap-4 md:grid-cols-2">
|
|
2881
|
+
<label class="grid gap-2">
|
|
2882
|
+
<span class="text-sm font-medium text-(--rt-text-secondary)">Bank name</span>
|
|
2883
|
+
<input formControlName="bankName" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('bankName')" />
|
|
2884
|
+
@if (fieldComment('bankName')) {
|
|
2885
|
+
<span class="text-xs text-amber-800">{{ fieldComment('bankName') }}</span>
|
|
2886
|
+
}
|
|
2887
|
+
@if (hasError('bankName', 'required')) {
|
|
2888
|
+
<span class="text-xs text-rose-700">Bank name is required.</span>
|
|
2889
|
+
}
|
|
2890
|
+
</label>
|
|
2891
|
+
|
|
2892
|
+
<label class="grid gap-2">
|
|
2893
|
+
<span class="text-sm font-medium text-(--rt-text-secondary)">Account holder name</span>
|
|
2894
|
+
<input
|
|
2895
|
+
formControlName="accountHolderName"
|
|
2896
|
+
class="h-11 rounded-xl border border-(--rt-border-color) px-3"
|
|
2897
|
+
[readonly]="isFieldLocked('accountHolderName')"
|
|
2898
|
+
/>
|
|
2899
|
+
<span class="text-xs text-(--rt-text-secondary)">Expected name: {{ expectedAccountHolderName() || 'Complete the profile section first' }}</span>
|
|
2900
|
+
@if (fieldComment('accountHolderName')) {
|
|
2901
|
+
<span class="text-xs text-amber-800">{{ fieldComment('accountHolderName') }}</span>
|
|
2902
|
+
}
|
|
2903
|
+
@if (hasError('accountHolderName', 'required')) {
|
|
2904
|
+
<span class="text-xs text-rose-700">Account holder name is required.</span>
|
|
2905
|
+
}
|
|
2906
|
+
@if (hasError('accountHolderName', 'accountHolderMismatch')) {
|
|
2907
|
+
<span class="text-xs text-rose-700">Account holder name must match the applicant or company legal name.</span>
|
|
2908
|
+
}
|
|
2909
|
+
</label>
|
|
2910
|
+
|
|
2911
|
+
<label class="grid gap-2">
|
|
2912
|
+
<span class="text-sm font-medium text-(--rt-text-secondary)">Sort code</span>
|
|
2913
|
+
<input formControlName="sortCode" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('sortCode')" />
|
|
2914
|
+
<span class="text-xs text-(--rt-text-secondary)">Use 6 digits. Example: 01-02-03.</span>
|
|
2915
|
+
@if (fieldComment('sortCode')) {
|
|
2916
|
+
<span class="text-xs text-amber-800">{{ fieldComment('sortCode') }}</span>
|
|
2917
|
+
}
|
|
2918
|
+
@if (hasError('sortCode', 'required')) {
|
|
2919
|
+
<span class="text-xs text-rose-700">Sort code is required.</span>
|
|
2920
|
+
}
|
|
2921
|
+
@if (hasError('sortCode', 'pattern')) {
|
|
2922
|
+
<span class="text-xs text-rose-700">Sort code must contain exactly 6 digits.</span>
|
|
2923
|
+
}
|
|
2924
|
+
</label>
|
|
2925
|
+
|
|
2926
|
+
<label class="grid gap-2">
|
|
2927
|
+
<span class="text-sm font-medium text-(--rt-text-secondary)">Account number</span>
|
|
2928
|
+
<input formControlName="accountNumber" class="h-11 rounded-xl border border-(--rt-border-color) px-3" [readonly]="isFieldLocked('accountNumber')" />
|
|
2929
|
+
<span class="text-xs text-(--rt-text-secondary)">Account number must contain exactly 8 digits.</span>
|
|
2930
|
+
@if (fieldComment('accountNumber')) {
|
|
2931
|
+
<span class="text-xs text-amber-800">{{ fieldComment('accountNumber') }}</span>
|
|
2932
|
+
}
|
|
2933
|
+
@if (hasError('accountNumber', 'required')) {
|
|
2934
|
+
<span class="text-xs text-rose-700">Account number is required.</span>
|
|
2935
|
+
}
|
|
2936
|
+
@if (hasError('accountNumber', 'pattern')) {
|
|
2937
|
+
<span class="text-xs text-rose-700">Account number must contain exactly 8 digits.</span>
|
|
2938
|
+
}
|
|
2939
|
+
</label>
|
|
2940
|
+
</form>
|
|
2941
|
+
</section>
|
|
2942
|
+
`,
|
|
2943
|
+
}]
|
|
2944
|
+
}], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], fieldLocks: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldLocks", required: false }] }], expectedAccountHolderName: [{ type: i0.Input, args: [{ isSignal: true, alias: "expectedAccountHolderName", required: false }] }], issueComments: [{ type: i0.Input, args: [{ isSignal: true, alias: "issueComments", required: false }] }] } });
|
|
2945
|
+
|
|
2946
|
+
class OnboardingReviewRow {
|
|
2947
|
+
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
2948
|
+
value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
2949
|
+
helper = input('', ...(ngDevMode ? [{ debugName: "helper" }] : []));
|
|
2950
|
+
existingIssue = input(null, ...(ngDevMode ? [{ debugName: "existingIssue" }] : []));
|
|
2951
|
+
issueOptions = input([], ...(ngDevMode ? [{ debugName: "issueOptions" }] : []));
|
|
2952
|
+
draftIssue = input(null, ...(ngDevMode ? [{ debugName: "draftIssue" }] : []));
|
|
2953
|
+
passed = input(false, ...(ngDevMode ? [{ debugName: "passed" }] : []));
|
|
2954
|
+
busy = input(false, ...(ngDevMode ? [{ debugName: "busy" }] : []));
|
|
2955
|
+
actionLabel = input(null, ...(ngDevMode ? [{ debugName: "actionLabel" }] : []));
|
|
2956
|
+
actionDisabled = input(false, ...(ngDevMode ? [{ debugName: "actionDisabled" }] : []));
|
|
2957
|
+
selectedIssueKey = computed(() => this.draftIssue()?.optionKey ?? '', ...(ngDevMode ? [{ debugName: "selectedIssueKey" }] : []));
|
|
2958
|
+
activeIssueOption = computed(() => this.issueOptions().find((option) => option.key === this.selectedIssueKey()) ?? null, ...(ngDevMode ? [{ debugName: "activeIssueOption" }] : []));
|
|
2959
|
+
showCustomComment = computed(() => this.activeIssueOption()?.requiresComment ?? false, ...(ngDevMode ? [{ debugName: "showCustomComment" }] : []));
|
|
2960
|
+
selectedPresetComment = computed(() => {
|
|
2961
|
+
const option = this.activeIssueOption();
|
|
2962
|
+
if (!option || option.requiresComment) {
|
|
2963
|
+
return '';
|
|
2964
|
+
}
|
|
2965
|
+
return option.comment;
|
|
2966
|
+
}, ...(ngDevMode ? [{ debugName: "selectedPresetComment" }] : []));
|
|
2967
|
+
customCommentPlaceholder = computed(() => this.activeIssueOption()?.placeholder ?? 'Add a short reviewer note for this issue.', ...(ngDevMode ? [{ debugName: "customCommentPlaceholder" }] : []));
|
|
2968
|
+
passButtonClass = computed(() => this.passed()
|
|
2969
|
+
? 'border-(--rt-border-color) bg-(--rt-raised-background) text-emerald-800'
|
|
2970
|
+
: 'border-(--rt-border-color) bg-(--rt-base-background) text-(--rt-text-secondary)', ...(ngDevMode ? [{ debugName: "passButtonClass" }] : []));
|
|
2971
|
+
passedChange = output();
|
|
2972
|
+
draftIssueChange = output();
|
|
2973
|
+
actionTriggered = output();
|
|
2974
|
+
togglePassed() {
|
|
2975
|
+
this.passedChange.emit(!this.passed());
|
|
2976
|
+
}
|
|
2977
|
+
onIssueSelectionChange(event) {
|
|
2978
|
+
const target = event.target;
|
|
2979
|
+
const optionKey = target.value;
|
|
2980
|
+
if (!optionKey) {
|
|
2981
|
+
this.draftIssueChange.emit(null);
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
const option = this.issueOptions().find((item) => item.key === optionKey);
|
|
2985
|
+
if (!option) {
|
|
2986
|
+
this.draftIssueChange.emit(null);
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
this.draftIssueChange.emit({
|
|
2990
|
+
optionKey,
|
|
2991
|
+
comment: option.requiresComment && this.draftIssue()?.optionKey === optionKey
|
|
2992
|
+
? (this.draftIssue()?.comment ?? '')
|
|
2993
|
+
: option.comment,
|
|
2994
|
+
});
|
|
2995
|
+
}
|
|
2996
|
+
onCustomCommentChange(event) {
|
|
2997
|
+
const option = this.activeIssueOption();
|
|
2998
|
+
if (!option) {
|
|
2999
|
+
this.draftIssueChange.emit(null);
|
|
3000
|
+
return;
|
|
3001
|
+
}
|
|
3002
|
+
const target = event.target;
|
|
3003
|
+
this.draftIssueChange.emit({
|
|
3004
|
+
optionKey: option.key,
|
|
3005
|
+
comment: target.value,
|
|
3006
|
+
});
|
|
3007
|
+
}
|
|
3008
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingReviewRow, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3009
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: OnboardingReviewRow, isStandalone: true, selector: "rolatech-onboarding-review-row", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, helper: { classPropertyName: "helper", publicName: "helper", isSignal: true, isRequired: false, transformFunction: null }, existingIssue: { classPropertyName: "existingIssue", publicName: "existingIssue", isSignal: true, isRequired: false, transformFunction: null }, issueOptions: { classPropertyName: "issueOptions", publicName: "issueOptions", isSignal: true, isRequired: false, transformFunction: null }, draftIssue: { classPropertyName: "draftIssue", publicName: "draftIssue", isSignal: true, isRequired: false, transformFunction: null }, passed: { classPropertyName: "passed", publicName: "passed", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, actionDisabled: { classPropertyName: "actionDisabled", publicName: "actionDisabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { passedChange: "passedChange", draftIssueChange: "draftIssueChange", actionTriggered: "actionTriggered" }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
3010
|
+
<article
|
|
3011
|
+
class="overflow-hidden rounded-[1.75rem] border border-(--rt-border-color) bg-(--rt-base-background) p-4 shadow-sm"
|
|
3012
|
+
>
|
|
3013
|
+
<div class="flex flex-wrap items-start justify-between gap-3">
|
|
3014
|
+
<div class="min-w-0 space-y-1">
|
|
3015
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
3016
|
+
<p class="text-[11px] font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">{{ label() }}</p>
|
|
3017
|
+
@if (existingIssue()) {
|
|
3018
|
+
<span
|
|
3019
|
+
class="rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.16em] text-amber-800"
|
|
3020
|
+
>
|
|
3021
|
+
Open issue
|
|
3022
|
+
</span>
|
|
3023
|
+
}
|
|
3024
|
+
</div>
|
|
3025
|
+
|
|
3026
|
+
<p class="wrap-break-words text-sm font-semibold text-(--rt-text-primary)">{{ value() || 'Missing' }}</p>
|
|
3027
|
+
@if (helper()) {
|
|
3028
|
+
<p class="text-xs text-(--rt-text-secondary)">{{ helper() }}</p>
|
|
3029
|
+
}
|
|
3030
|
+
</div>
|
|
3031
|
+
|
|
3032
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
3033
|
+
@if (actionLabel()) {
|
|
3034
|
+
<button
|
|
3035
|
+
type="button"
|
|
3036
|
+
class="cursor-pointer rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-1.5 text-xs font-semibold text-(--rt-text-primary) transition hover:bg-(--rt-base-background) disabled:cursor-not-allowed disabled:opacity-60"
|
|
3037
|
+
[disabled]="busy() || actionDisabled()"
|
|
3038
|
+
(click)="actionTriggered.emit()"
|
|
3039
|
+
>
|
|
3040
|
+
{{ actionLabel() }}
|
|
3041
|
+
</button>
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
<button
|
|
3045
|
+
type="button"
|
|
3046
|
+
class="cursor-pointer rounded-full border px-3 py-1.5 text-xs font-semibold transition disabled:cursor-not-allowed disabled:opacity-60"
|
|
3047
|
+
[class]="passButtonClass()"
|
|
3048
|
+
[disabled]="busy()"
|
|
3049
|
+
(click)="togglePassed()"
|
|
3050
|
+
>
|
|
3051
|
+
{{ passed() ? 'Passed' : 'Mark pass' }}
|
|
3052
|
+
</button>
|
|
3053
|
+
</div>
|
|
3054
|
+
</div>
|
|
3055
|
+
|
|
3056
|
+
@if (existingIssue()) {
|
|
3057
|
+
<div
|
|
3058
|
+
class="mt-3 rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-amber-900"
|
|
3059
|
+
>
|
|
3060
|
+
Current issue: {{ existingIssue() }}
|
|
3061
|
+
</div>
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
<div class="mt-3 grid gap-2">
|
|
3065
|
+
<label class="grid gap-2">
|
|
3066
|
+
<span class="text-[11px] font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">Issue to raise</span>
|
|
3067
|
+
<select
|
|
3068
|
+
class="rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-(--rt-text-primary) disabled:cursor-not-allowed disabled:bg-(--rt-raised-background)"
|
|
3069
|
+
[value]="selectedIssueKey()"
|
|
3070
|
+
[disabled]="busy() || passed()"
|
|
3071
|
+
(change)="onIssueSelectionChange($event)"
|
|
3072
|
+
>
|
|
3073
|
+
<option value="">No issue</option>
|
|
3074
|
+
|
|
3075
|
+
@for (option of issueOptions(); track option.key) {
|
|
3076
|
+
<option [value]="option.key">{{ option.label }}</option>
|
|
3077
|
+
}
|
|
3078
|
+
</select>
|
|
3079
|
+
</label>
|
|
3080
|
+
|
|
3081
|
+
@if (selectedPresetComment()) {
|
|
3082
|
+
<p class="text-xs text-(--rt-text-secondary)">Review note: {{ selectedPresetComment() }}</p>
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
@if (showCustomComment()) {
|
|
3086
|
+
<label class="grid gap-2">
|
|
3087
|
+
<span class="text-[11px] font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)"
|
|
3088
|
+
>Custom review note</span
|
|
3089
|
+
>
|
|
3090
|
+
<textarea
|
|
3091
|
+
class="min-h-24 rounded-xl border border-(--rt-border-color) px-3 py-2 text-sm"
|
|
3092
|
+
[value]="draftIssue()?.comment ?? ''"
|
|
3093
|
+
[disabled]="busy() || passed()"
|
|
3094
|
+
(input)="onCustomCommentChange($event)"
|
|
3095
|
+
[placeholder]="customCommentPlaceholder()"
|
|
3096
|
+
></textarea>
|
|
3097
|
+
</label>
|
|
3098
|
+
}
|
|
3099
|
+
</div>
|
|
3100
|
+
</article>
|
|
3101
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
3102
|
+
}
|
|
3103
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: OnboardingReviewRow, decorators: [{
|
|
3104
|
+
type: Component,
|
|
3105
|
+
args: [{
|
|
3106
|
+
selector: 'rolatech-onboarding-review-row',
|
|
3107
|
+
standalone: true,
|
|
3108
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3109
|
+
encapsulation: ViewEncapsulation.None,
|
|
3110
|
+
host: { class: 'block' },
|
|
3111
|
+
template: `
|
|
3112
|
+
<article
|
|
3113
|
+
class="overflow-hidden rounded-[1.75rem] border border-(--rt-border-color) bg-(--rt-base-background) p-4 shadow-sm"
|
|
3114
|
+
>
|
|
3115
|
+
<div class="flex flex-wrap items-start justify-between gap-3">
|
|
3116
|
+
<div class="min-w-0 space-y-1">
|
|
3117
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
3118
|
+
<p class="text-[11px] font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">{{ label() }}</p>
|
|
3119
|
+
@if (existingIssue()) {
|
|
3120
|
+
<span
|
|
3121
|
+
class="rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.16em] text-amber-800"
|
|
3122
|
+
>
|
|
3123
|
+
Open issue
|
|
3124
|
+
</span>
|
|
3125
|
+
}
|
|
3126
|
+
</div>
|
|
3127
|
+
|
|
3128
|
+
<p class="wrap-break-words text-sm font-semibold text-(--rt-text-primary)">{{ value() || 'Missing' }}</p>
|
|
3129
|
+
@if (helper()) {
|
|
3130
|
+
<p class="text-xs text-(--rt-text-secondary)">{{ helper() }}</p>
|
|
3131
|
+
}
|
|
3132
|
+
</div>
|
|
3133
|
+
|
|
3134
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
3135
|
+
@if (actionLabel()) {
|
|
3136
|
+
<button
|
|
3137
|
+
type="button"
|
|
3138
|
+
class="cursor-pointer rounded-full border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-1.5 text-xs font-semibold text-(--rt-text-primary) transition hover:bg-(--rt-base-background) disabled:cursor-not-allowed disabled:opacity-60"
|
|
3139
|
+
[disabled]="busy() || actionDisabled()"
|
|
3140
|
+
(click)="actionTriggered.emit()"
|
|
3141
|
+
>
|
|
3142
|
+
{{ actionLabel() }}
|
|
3143
|
+
</button>
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
<button
|
|
3147
|
+
type="button"
|
|
3148
|
+
class="cursor-pointer rounded-full border px-3 py-1.5 text-xs font-semibold transition disabled:cursor-not-allowed disabled:opacity-60"
|
|
3149
|
+
[class]="passButtonClass()"
|
|
3150
|
+
[disabled]="busy()"
|
|
3151
|
+
(click)="togglePassed()"
|
|
3152
|
+
>
|
|
3153
|
+
{{ passed() ? 'Passed' : 'Mark pass' }}
|
|
3154
|
+
</button>
|
|
3155
|
+
</div>
|
|
3156
|
+
</div>
|
|
3157
|
+
|
|
3158
|
+
@if (existingIssue()) {
|
|
3159
|
+
<div
|
|
3160
|
+
class="mt-3 rounded-2xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-amber-900"
|
|
3161
|
+
>
|
|
3162
|
+
Current issue: {{ existingIssue() }}
|
|
3163
|
+
</div>
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
<div class="mt-3 grid gap-2">
|
|
3167
|
+
<label class="grid gap-2">
|
|
3168
|
+
<span class="text-[11px] font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)">Issue to raise</span>
|
|
3169
|
+
<select
|
|
3170
|
+
class="rounded-xl border border-(--rt-border-color) bg-(--rt-raised-background) px-3 py-2 text-sm text-(--rt-text-primary) disabled:cursor-not-allowed disabled:bg-(--rt-raised-background)"
|
|
3171
|
+
[value]="selectedIssueKey()"
|
|
3172
|
+
[disabled]="busy() || passed()"
|
|
3173
|
+
(change)="onIssueSelectionChange($event)"
|
|
3174
|
+
>
|
|
3175
|
+
<option value="">No issue</option>
|
|
3176
|
+
|
|
3177
|
+
@for (option of issueOptions(); track option.key) {
|
|
3178
|
+
<option [value]="option.key">{{ option.label }}</option>
|
|
3179
|
+
}
|
|
3180
|
+
</select>
|
|
3181
|
+
</label>
|
|
3182
|
+
|
|
3183
|
+
@if (selectedPresetComment()) {
|
|
3184
|
+
<p class="text-xs text-(--rt-text-secondary)">Review note: {{ selectedPresetComment() }}</p>
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
@if (showCustomComment()) {
|
|
3188
|
+
<label class="grid gap-2">
|
|
3189
|
+
<span class="text-[11px] font-semibold uppercase tracking-[0.2em] text-(--rt-text-secondary)"
|
|
3190
|
+
>Custom review note</span
|
|
3191
|
+
>
|
|
3192
|
+
<textarea
|
|
3193
|
+
class="min-h-24 rounded-xl border border-(--rt-border-color) px-3 py-2 text-sm"
|
|
3194
|
+
[value]="draftIssue()?.comment ?? ''"
|
|
3195
|
+
[disabled]="busy() || passed()"
|
|
3196
|
+
(input)="onCustomCommentChange($event)"
|
|
3197
|
+
[placeholder]="customCommentPlaceholder()"
|
|
3198
|
+
></textarea>
|
|
3199
|
+
</label>
|
|
3200
|
+
}
|
|
3201
|
+
</div>
|
|
3202
|
+
</article>
|
|
3203
|
+
`,
|
|
3204
|
+
}]
|
|
3205
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], helper: [{ type: i0.Input, args: [{ isSignal: true, alias: "helper", required: false }] }], existingIssue: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingIssue", required: false }] }], issueOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "issueOptions", required: false }] }], draftIssue: [{ type: i0.Input, args: [{ isSignal: true, alias: "draftIssue", required: false }] }], passed: [{ type: i0.Input, args: [{ isSignal: true, alias: "passed", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], actionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionLabel", required: false }] }], actionDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionDisabled", required: false }] }], passedChange: [{ type: i0.Output, args: ["passedChange"] }], draftIssueChange: [{ type: i0.Output, args: ["draftIssueChange"] }], actionTriggered: [{ type: i0.Output, args: ["actionTriggered"] }] } });
|
|
3206
|
+
|
|
3207
|
+
const OTHER_ISSUE_OPTION = {
|
|
3208
|
+
key: 'OTHER',
|
|
3209
|
+
label: 'Other issue',
|
|
3210
|
+
comment: '',
|
|
3211
|
+
requiresComment: true,
|
|
3212
|
+
placeholder: 'Add a short reviewer note for this issue.',
|
|
3213
|
+
};
|
|
3214
|
+
const DEFAULT_FIELD_ISSUE_OPTIONS = [
|
|
3215
|
+
{
|
|
3216
|
+
key: 'missing',
|
|
3217
|
+
label: 'Missing or incomplete',
|
|
3218
|
+
comment: 'This field is missing or incomplete and needs correction.',
|
|
3219
|
+
},
|
|
3220
|
+
{
|
|
3221
|
+
key: 'format',
|
|
3222
|
+
label: 'Incorrect format',
|
|
3223
|
+
comment: 'This field format is incorrect and needs correction.',
|
|
3224
|
+
},
|
|
3225
|
+
{
|
|
3226
|
+
key: 'mismatch',
|
|
3227
|
+
label: 'Does not match documents',
|
|
3228
|
+
comment: 'This field does not match the supporting documents.',
|
|
3229
|
+
},
|
|
3230
|
+
{
|
|
3231
|
+
key: 'clarify',
|
|
3232
|
+
label: 'Needs clarification',
|
|
3233
|
+
comment: 'Please clarify or correct this field before review can continue.',
|
|
3234
|
+
},
|
|
3235
|
+
OTHER_ISSUE_OPTION,
|
|
3236
|
+
];
|
|
3237
|
+
const NAME_FIELD_ISSUE_OPTIONS = [
|
|
3238
|
+
{
|
|
3239
|
+
key: 'missing',
|
|
3240
|
+
label: 'Missing or incomplete',
|
|
3241
|
+
comment: 'The name is missing or incomplete and needs correction.',
|
|
3242
|
+
},
|
|
3243
|
+
{
|
|
3244
|
+
key: 'mismatch',
|
|
3245
|
+
label: 'Does not match documents',
|
|
3246
|
+
comment: 'The name does not match the supporting documents.',
|
|
3247
|
+
},
|
|
3248
|
+
{
|
|
3249
|
+
key: 'legal',
|
|
3250
|
+
label: 'Legal name unclear',
|
|
3251
|
+
comment: 'Please provide the full legal name exactly as shown on the supporting documents.',
|
|
3252
|
+
},
|
|
3253
|
+
OTHER_ISSUE_OPTION,
|
|
3254
|
+
];
|
|
3255
|
+
const EMAIL_FIELD_ISSUE_OPTIONS = [
|
|
3256
|
+
{
|
|
3257
|
+
key: 'missing',
|
|
3258
|
+
label: 'Missing email',
|
|
3259
|
+
comment: 'The primary email address is missing.',
|
|
3260
|
+
},
|
|
3261
|
+
{
|
|
3262
|
+
key: 'invalid',
|
|
3263
|
+
label: 'Invalid format',
|
|
3264
|
+
comment: 'The primary email address format is invalid.',
|
|
3265
|
+
},
|
|
3266
|
+
{
|
|
3267
|
+
key: 'verify',
|
|
3268
|
+
label: 'Needs verification',
|
|
3269
|
+
comment: 'Please confirm the primary email address before review can continue.',
|
|
3270
|
+
},
|
|
3271
|
+
OTHER_ISSUE_OPTION,
|
|
3272
|
+
];
|
|
3273
|
+
const PHONE_FIELD_ISSUE_OPTIONS = [
|
|
3274
|
+
{
|
|
3275
|
+
key: 'missing',
|
|
3276
|
+
label: 'Missing number',
|
|
3277
|
+
comment: 'The phone number is missing or incomplete.',
|
|
3278
|
+
},
|
|
3279
|
+
{
|
|
3280
|
+
key: 'invalid',
|
|
3281
|
+
label: 'Invalid format',
|
|
3282
|
+
comment: 'The phone number format is invalid.',
|
|
3283
|
+
},
|
|
3284
|
+
{
|
|
3285
|
+
key: 'clarify',
|
|
3286
|
+
label: 'Needs clarification',
|
|
3287
|
+
comment: 'Please confirm the correct phone number before review can continue.',
|
|
3288
|
+
},
|
|
3289
|
+
OTHER_ISSUE_OPTION,
|
|
3290
|
+
];
|
|
3291
|
+
const COUNTRY_FIELD_ISSUE_OPTIONS = [
|
|
3292
|
+
{
|
|
3293
|
+
key: 'missing',
|
|
3294
|
+
label: 'Missing selection',
|
|
3295
|
+
comment: 'The country selection is missing.',
|
|
3296
|
+
},
|
|
3297
|
+
{
|
|
3298
|
+
key: 'mismatch',
|
|
3299
|
+
label: 'Does not match documents',
|
|
3300
|
+
comment: 'The selected country does not match the supporting documents.',
|
|
3301
|
+
},
|
|
3302
|
+
{
|
|
3303
|
+
key: 'clarify',
|
|
3304
|
+
label: 'Needs clarification',
|
|
3305
|
+
comment: 'Please confirm the correct country selection before review can continue.',
|
|
3306
|
+
},
|
|
3307
|
+
OTHER_ISSUE_OPTION,
|
|
3308
|
+
];
|
|
3309
|
+
const VAT_MODE_ISSUE_OPTIONS = [
|
|
3310
|
+
{
|
|
3311
|
+
key: 'mismatch',
|
|
3312
|
+
label: 'Does not match documents',
|
|
3313
|
+
comment: 'The VAT setup does not match the supporting documents.',
|
|
3314
|
+
},
|
|
3315
|
+
{
|
|
3316
|
+
key: 'clarify',
|
|
3317
|
+
label: 'Needs clarification',
|
|
3318
|
+
comment: 'Please clarify the VAT registration status before review can continue.',
|
|
3319
|
+
},
|
|
3320
|
+
OTHER_ISSUE_OPTION,
|
|
3321
|
+
];
|
|
3322
|
+
const VAT_NUMBER_ISSUE_OPTIONS = [
|
|
3323
|
+
{
|
|
3324
|
+
key: 'missing',
|
|
3325
|
+
label: 'Missing VAT number',
|
|
3326
|
+
comment: 'The VAT number is missing.',
|
|
3327
|
+
},
|
|
3328
|
+
{
|
|
3329
|
+
key: 'invalid',
|
|
3330
|
+
label: 'Invalid format',
|
|
3331
|
+
comment: 'The VAT number format appears invalid.',
|
|
3332
|
+
},
|
|
3333
|
+
{
|
|
3334
|
+
key: 'mismatch',
|
|
3335
|
+
label: 'Does not match documents',
|
|
3336
|
+
comment: 'The VAT number does not match the supporting documents.',
|
|
3337
|
+
},
|
|
3338
|
+
OTHER_ISSUE_OPTION,
|
|
3339
|
+
];
|
|
3340
|
+
const BANK_DETAIL_ISSUE_OPTIONS = [
|
|
3341
|
+
{
|
|
3342
|
+
key: 'missing',
|
|
3343
|
+
label: 'Missing or incomplete',
|
|
3344
|
+
comment: 'The banking detail is missing or incomplete.',
|
|
3345
|
+
},
|
|
3346
|
+
{
|
|
3347
|
+
key: 'mismatch',
|
|
3348
|
+
label: 'Does not match evidence',
|
|
3349
|
+
comment: 'The banking detail does not match the supporting bank evidence.',
|
|
3350
|
+
},
|
|
3351
|
+
{
|
|
3352
|
+
key: 'clarify',
|
|
3353
|
+
label: 'Needs clarification',
|
|
3354
|
+
comment: 'Please confirm the banking detail before review can continue.',
|
|
3355
|
+
},
|
|
3356
|
+
OTHER_ISSUE_OPTION,
|
|
3357
|
+
];
|
|
3358
|
+
const BANK_ACCOUNT_ISSUE_OPTIONS = [
|
|
3359
|
+
{
|
|
3360
|
+
key: 'missing',
|
|
3361
|
+
label: 'Missing or incomplete',
|
|
3362
|
+
comment: 'The account detail is missing or incomplete.',
|
|
3363
|
+
},
|
|
3364
|
+
{
|
|
3365
|
+
key: 'invalid',
|
|
3366
|
+
label: 'Invalid format',
|
|
3367
|
+
comment: 'The account detail format appears invalid.',
|
|
3368
|
+
},
|
|
3369
|
+
{
|
|
3370
|
+
key: 'mismatch',
|
|
3371
|
+
label: 'Does not match evidence',
|
|
3372
|
+
comment: 'The account detail does not match the supporting bank evidence.',
|
|
3373
|
+
},
|
|
3374
|
+
OTHER_ISSUE_OPTION,
|
|
3375
|
+
];
|
|
3376
|
+
const DEFAULT_DOCUMENT_ISSUE_OPTIONS = [
|
|
3377
|
+
{
|
|
3378
|
+
key: 'missing',
|
|
3379
|
+
label: 'Missing document',
|
|
3380
|
+
comment: 'The required document is missing.',
|
|
3381
|
+
},
|
|
3382
|
+
{
|
|
3383
|
+
key: 'unreadable',
|
|
3384
|
+
label: 'Unreadable file',
|
|
3385
|
+
comment: 'The uploaded document is unreadable or incomplete.',
|
|
3386
|
+
},
|
|
3387
|
+
{
|
|
3388
|
+
key: 'wrong-document',
|
|
3389
|
+
label: 'Wrong document',
|
|
3390
|
+
comment: 'The uploaded file does not match the requested document type.',
|
|
3391
|
+
},
|
|
3392
|
+
{
|
|
3393
|
+
key: 'mismatch',
|
|
3394
|
+
label: 'Details do not match',
|
|
3395
|
+
comment: 'The uploaded document details do not match the application information.',
|
|
3396
|
+
},
|
|
3397
|
+
OTHER_ISSUE_OPTION,
|
|
3398
|
+
];
|
|
3399
|
+
const PASSPORT_DOCUMENT_ISSUE_OPTIONS = [
|
|
3400
|
+
{
|
|
3401
|
+
key: 'missing',
|
|
3402
|
+
label: 'Missing passport',
|
|
3403
|
+
comment: 'The passport document is missing.',
|
|
3404
|
+
},
|
|
3405
|
+
{
|
|
3406
|
+
key: 'unreadable',
|
|
3407
|
+
label: 'Unreadable photo page',
|
|
3408
|
+
comment: 'The passport photo page is unreadable or incomplete.',
|
|
3409
|
+
},
|
|
3410
|
+
{
|
|
3411
|
+
key: 'expired',
|
|
3412
|
+
label: 'Expired or invalid',
|
|
3413
|
+
comment: 'The passport appears expired or otherwise invalid for verification.',
|
|
3414
|
+
},
|
|
3415
|
+
{
|
|
3416
|
+
key: 'mismatch',
|
|
3417
|
+
label: 'Details do not match',
|
|
3418
|
+
comment: 'The passport details do not match the application information.',
|
|
3419
|
+
},
|
|
3420
|
+
OTHER_ISSUE_OPTION,
|
|
3421
|
+
];
|
|
3422
|
+
const CERTIFICATE_DOCUMENT_ISSUE_OPTIONS = [
|
|
3423
|
+
{
|
|
3424
|
+
key: 'missing',
|
|
3425
|
+
label: 'Missing certificate',
|
|
3426
|
+
comment: 'The certificate is missing.',
|
|
3427
|
+
},
|
|
3428
|
+
{
|
|
3429
|
+
key: 'unreadable',
|
|
3430
|
+
label: 'Unreadable file',
|
|
3431
|
+
comment: 'The certificate is unreadable or incomplete.',
|
|
3432
|
+
},
|
|
3433
|
+
{
|
|
3434
|
+
key: 'invalid',
|
|
3435
|
+
label: 'Invalid or outdated',
|
|
3436
|
+
comment: 'The certificate appears invalid or outdated for review.',
|
|
3437
|
+
},
|
|
3438
|
+
{
|
|
3439
|
+
key: 'mismatch',
|
|
3440
|
+
label: 'Details do not match',
|
|
3441
|
+
comment: 'The certificate details do not match the application information.',
|
|
3442
|
+
},
|
|
3443
|
+
OTHER_ISSUE_OPTION,
|
|
3444
|
+
];
|
|
3445
|
+
const OTHER_DOCUMENT_ISSUE_OPTIONS = [
|
|
3446
|
+
{
|
|
3447
|
+
key: 'missing',
|
|
3448
|
+
label: 'Missing supporting file',
|
|
3449
|
+
comment: 'The requested supporting evidence is missing.',
|
|
3450
|
+
},
|
|
3451
|
+
{
|
|
3452
|
+
key: 'unreadable',
|
|
3453
|
+
label: 'Unreadable file',
|
|
3454
|
+
comment: 'The supporting evidence is unreadable or incomplete.',
|
|
3455
|
+
},
|
|
3456
|
+
{
|
|
3457
|
+
key: 'insufficient',
|
|
3458
|
+
label: 'Insufficient evidence',
|
|
3459
|
+
comment: 'The supporting evidence is insufficient for review.',
|
|
3460
|
+
},
|
|
3461
|
+
{
|
|
3462
|
+
key: 'wrong-document',
|
|
3463
|
+
label: 'Unrelated document',
|
|
3464
|
+
comment: 'The uploaded file does not provide the requested supporting evidence.',
|
|
3465
|
+
},
|
|
3466
|
+
OTHER_ISSUE_OPTION,
|
|
3467
|
+
];
|
|
3468
|
+
function onboardingFieldIssueOptions(fieldKey) {
|
|
3469
|
+
switch (fieldKey) {
|
|
3470
|
+
case 'organizationName':
|
|
3471
|
+
case 'contactName':
|
|
3472
|
+
return NAME_FIELD_ISSUE_OPTIONS;
|
|
3473
|
+
case 'contactEmail':
|
|
3474
|
+
return EMAIL_FIELD_ISSUE_OPTIONS;
|
|
3475
|
+
case 'contactPhone':
|
|
3476
|
+
return PHONE_FIELD_ISSUE_OPTIONS;
|
|
3477
|
+
case 'countryCode':
|
|
3478
|
+
case 'companyCountry':
|
|
3479
|
+
return COUNTRY_FIELD_ISSUE_OPTIONS;
|
|
3480
|
+
case 'vatMode':
|
|
3481
|
+
return VAT_MODE_ISSUE_OPTIONS;
|
|
3482
|
+
case 'vatNumber':
|
|
3483
|
+
return VAT_NUMBER_ISSUE_OPTIONS;
|
|
3484
|
+
case 'bankName':
|
|
3485
|
+
case 'accountHolderName':
|
|
3486
|
+
return BANK_DETAIL_ISSUE_OPTIONS;
|
|
3487
|
+
case 'sortCode':
|
|
3488
|
+
case 'accountNumber':
|
|
3489
|
+
return BANK_ACCOUNT_ISSUE_OPTIONS;
|
|
3490
|
+
default:
|
|
3491
|
+
return DEFAULT_FIELD_ISSUE_OPTIONS;
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
function onboardingDocumentIssueOptions(documentType) {
|
|
3495
|
+
switch (documentType) {
|
|
3496
|
+
case 'PASSPORT':
|
|
3497
|
+
return PASSPORT_DOCUMENT_ISSUE_OPTIONS;
|
|
3498
|
+
case 'CERTIFICATE_OF_INCORPORATION':
|
|
3499
|
+
case 'INDUSTRY_CERTIFICATE':
|
|
3500
|
+
return CERTIFICATE_DOCUMENT_ISSUE_OPTIONS;
|
|
3501
|
+
case 'OTHER':
|
|
3502
|
+
return OTHER_DOCUMENT_ISSUE_OPTIONS;
|
|
3503
|
+
default:
|
|
3504
|
+
return DEFAULT_DOCUMENT_ISSUE_OPTIONS;
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
|
|
1490
3508
|
/**
|
|
1491
3509
|
* Generated bundle index. Do not edit.
|
|
1492
3510
|
*/
|
|
1493
3511
|
|
|
1494
|
-
export { ADMIN_ONBOARDING_ROUTES, AGENT_APPLY_ROUTES, AGENT_ONBOARDING_ROUTES, AdminOnboardingDetailFacade, AdminOnboardingListFacade, AdminOnboardingReviewFacade, AgentApplyFacade, AgentOnboardingFacade, OnboardingDocumentUploader, OnboardingIssueList, OnboardingPageShell, OnboardingStatusBadge, OnboardingStepNav, OnboardingSummaryCard, OnboardingTimeline, OnboardingTimelineDrawer, applicationIdGuard, applyStepGuard };
|
|
3512
|
+
export { ADMIN_ONBOARDING_ROUTES, AGENT_APPLY_ROUTES, AGENT_ONBOARDING_ROUTES, AdminOnboardingDetailFacade, AdminOnboardingListFacade, AdminOnboardingReviewFacade, AgentApplyFacade, AgentOnboardingFacade, OnboardingBankingSection, OnboardingDocumentPreviewDialog, OnboardingDocumentUploader, OnboardingIssueList, OnboardingPageShell, OnboardingQualificationUploadSection, OnboardingReviewIssuesBanner, OnboardingReviewRow, OnboardingStatusBadge, OnboardingStepNav, OnboardingSummaryCard, OnboardingTimeline, OnboardingTimelineDrawer, OnboardingVatSection, applicationIdGuard, applyStepGuard, onboardingDocumentIssueOptions, onboardingFieldIssueOptions };
|
|
1495
3513
|
//# sourceMappingURL=rolatech-angular-onboarding.mjs.map
|