@dalmore/api-contracts 1.0.0 → 1.0.2
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/common/helpers/index.ts +205 -0
- package/common/types/account-manager.types.ts +10 -0
- package/common/types/account.types.ts +59 -2
- package/common/types/activity.types.ts +38 -0
- package/common/types/asset.types.ts +3 -0
- package/common/types/auth.types.ts +37 -10
- package/common/types/batch-jobs.types.ts +0 -1
- package/common/types/bonus-tier.types.ts +32 -0
- package/common/types/common.types.ts +20 -1
- package/common/types/contract-helpers.ts +205 -0
- package/common/types/investor-account.types.ts +137 -66
- package/common/types/page.types.ts +1 -1
- package/common/types/phone.type.ts +1 -1
- package/common/types/queue.types.ts +0 -1
- package/common/types/secondary-trade.types.ts +8 -0
- package/common/types/trade.types.ts +83 -1
- package/common/types/user.types.ts +2 -0
- package/common/types/zip.type.ts +1 -1
- package/contracts/clients/accounts/index.ts +3 -0
- package/contracts/clients/auth/index.ts +18 -1
- package/contracts/clients/index.ts +2 -0
- package/contracts/clients/offerings/index.ts +4 -3
- package/contracts/clients/sites/index.ts +56 -0
- package/contracts/compliance/account-managers/index.ts +2 -0
- package/contracts/compliance/accounts/index.ts +4 -2
- package/contracts/compliance/bonus-tiers/index.ts +55 -0
- package/contracts/compliance/index.ts +2 -0
- package/contracts/compliance/investor-accounts/index.ts +4 -0
- package/contracts/issuers/accounts/index.ts +5 -0
- package/contracts/issuers/bonus-tiers/index.ts +18 -0
- package/contracts/issuers/investor-accounts/index.ts +22 -0
- package/package.json +12 -2
- package/common/types/reminder-config.types.ts +0 -40
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract helpers - minimal subset for API contracts package
|
|
3
|
+
* This file is copied directly to @dalmore/api-contracts
|
|
4
|
+
*/
|
|
5
|
+
import { HttpStatus } from '@nestjs/common';
|
|
6
|
+
import parsePhoneNumberFromString, {
|
|
7
|
+
CountryCode,
|
|
8
|
+
PhoneNumber,
|
|
9
|
+
} from 'libphonenumber-js';
|
|
10
|
+
import { err, ok, Result } from 'neverthrow';
|
|
11
|
+
import { ErrorResult } from './common.types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validates a US zip code format (12345 or 12345-6789)
|
|
15
|
+
*/
|
|
16
|
+
export const validateUSZipCode = (zipCode: string): boolean => {
|
|
17
|
+
const regex = /^[0-9]{5}(?:-[0-9]{4})?$/;
|
|
18
|
+
return regex.test(zipCode);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validates a Canadian postal code format (A1A 1A1)
|
|
23
|
+
*/
|
|
24
|
+
export const validateCanadaZipCode = (zipCode: string): boolean => {
|
|
25
|
+
const regex = /^[A-Za-z]\d[A-Za-z] \d[A-Za-z]\d$/;
|
|
26
|
+
return regex.test(zipCode);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Normalizes a short date string (M/D/YY or M/D/YYYY) to MM/DD/YYYY format
|
|
31
|
+
*/
|
|
32
|
+
export function normalizeShortDate(input: string): string {
|
|
33
|
+
const [month, day, year] = input.split('/');
|
|
34
|
+
|
|
35
|
+
if (!month || !day || !year) {
|
|
36
|
+
throw new Error('Invalid date format');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const paddedMonth = month.padStart(2, '0');
|
|
40
|
+
const paddedDay = day.padStart(2, '0');
|
|
41
|
+
const fullYear = year.length === 2 ? `20${year}` : year;
|
|
42
|
+
|
|
43
|
+
return `${paddedMonth}/${paddedDay}/${fullYear}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Converts a string into a URL-safe slug
|
|
48
|
+
*/
|
|
49
|
+
export function slugify(input: string, exclude: boolean = false): string {
|
|
50
|
+
if (exclude) {
|
|
51
|
+
const excludeWords = ['llc', 'inc', 'corp', 'co'];
|
|
52
|
+
const negativeWordsList = [
|
|
53
|
+
'www',
|
|
54
|
+
'api',
|
|
55
|
+
'admin',
|
|
56
|
+
'dashboard',
|
|
57
|
+
'auth',
|
|
58
|
+
'cdn',
|
|
59
|
+
'static',
|
|
60
|
+
'blog',
|
|
61
|
+
'support',
|
|
62
|
+
'status',
|
|
63
|
+
'mail',
|
|
64
|
+
'smtp',
|
|
65
|
+
'ftp',
|
|
66
|
+
'ssh',
|
|
67
|
+
'login',
|
|
68
|
+
'register',
|
|
69
|
+
'signup',
|
|
70
|
+
'account',
|
|
71
|
+
'accounts',
|
|
72
|
+
'user',
|
|
73
|
+
'users',
|
|
74
|
+
'profile',
|
|
75
|
+
'settings',
|
|
76
|
+
'help',
|
|
77
|
+
'docs',
|
|
78
|
+
'documentation',
|
|
79
|
+
'developer',
|
|
80
|
+
'developers',
|
|
81
|
+
'app',
|
|
82
|
+
'apps',
|
|
83
|
+
'test',
|
|
84
|
+
'demo',
|
|
85
|
+
'staging',
|
|
86
|
+
'dev',
|
|
87
|
+
'prod',
|
|
88
|
+
'production',
|
|
89
|
+
'beta',
|
|
90
|
+
'alpha',
|
|
91
|
+
'portal',
|
|
92
|
+
'portals',
|
|
93
|
+
'client',
|
|
94
|
+
'clients',
|
|
95
|
+
'investor',
|
|
96
|
+
'investors',
|
|
97
|
+
'issuer',
|
|
98
|
+
'issuers',
|
|
99
|
+
'compliance',
|
|
100
|
+
'offering',
|
|
101
|
+
'offerings',
|
|
102
|
+
'trade',
|
|
103
|
+
'trades',
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
let slug = input
|
|
107
|
+
.toLowerCase()
|
|
108
|
+
.replace(/[^\w\s-]/g, '')
|
|
109
|
+
.replace(/[\s_-]+/g, '-')
|
|
110
|
+
.replace(/^-+|-+$/g, '');
|
|
111
|
+
|
|
112
|
+
excludeWords.forEach((word) => {
|
|
113
|
+
slug = slug.replace(new RegExp(`-${word}$`, 'i'), '');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (negativeWordsList.includes(slug)) {
|
|
117
|
+
return 'new-account';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return slug;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return input
|
|
124
|
+
.toLowerCase()
|
|
125
|
+
.replace(/[^\w\s-]/g, '')
|
|
126
|
+
.replace(/[\s_-]+/g, '-')
|
|
127
|
+
.replace(/^-+|-+$/g, '');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
type CurlOptions = {
|
|
131
|
+
query?: Record<string, string>;
|
|
132
|
+
headers?: Record<string, string>;
|
|
133
|
+
body?: Record<string, unknown>;
|
|
134
|
+
formData?: Record<string, string>;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const authHeader: CurlOptions['headers'] = {
|
|
138
|
+
Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9',
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const generateCurlExample = (
|
|
142
|
+
method: string,
|
|
143
|
+
path: string,
|
|
144
|
+
options: CurlOptions,
|
|
145
|
+
) => {
|
|
146
|
+
const baseUrl = 'https://dalmore-client-portal-api-prod.onrender.com/api/v1';
|
|
147
|
+
let curl = `curl -X ${method.toUpperCase()} '${baseUrl}${path}`;
|
|
148
|
+
if (options.query) {
|
|
149
|
+
const queryString = new URLSearchParams(options.query).toString();
|
|
150
|
+
curl += `?${queryString}`;
|
|
151
|
+
}
|
|
152
|
+
curl += "'";
|
|
153
|
+
if (options.headers) {
|
|
154
|
+
Object.entries(options.headers).forEach(([key, value]) => {
|
|
155
|
+
curl += ` \\\n --header '${key}: ${value}'`;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (options.body) {
|
|
159
|
+
curl += ` \\\n --header 'Content-Type: application/json'`;
|
|
160
|
+
curl += ` \\\n --data '${JSON.stringify(options.body)}'`;
|
|
161
|
+
}
|
|
162
|
+
if (options.formData) {
|
|
163
|
+
Object.entries(options.formData).forEach(([key, value]) => {
|
|
164
|
+
curl += ` \\\n --form '${key}=${value}'`;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return curl;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const generateApiDescription = (
|
|
171
|
+
summary: string,
|
|
172
|
+
method: string,
|
|
173
|
+
path: string,
|
|
174
|
+
options: CurlOptions,
|
|
175
|
+
) => {
|
|
176
|
+
const curlExample = generateCurlExample(method, path, options);
|
|
177
|
+
return `${summary}
|
|
178
|
+
|
|
179
|
+
### Example curl request
|
|
180
|
+
|
|
181
|
+
\`\`\`bash
|
|
182
|
+
${curlExample}
|
|
183
|
+
\`\`\``;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Normalizes a phone number to E.164 format
|
|
188
|
+
*/
|
|
189
|
+
export function normalizePhoneNumber(
|
|
190
|
+
phoneNumber: string,
|
|
191
|
+
countryCode: CountryCode,
|
|
192
|
+
): Result<PhoneNumber, ErrorResult> {
|
|
193
|
+
const parsedPhoneNumber = parsePhoneNumberFromString(
|
|
194
|
+
phoneNumber,
|
|
195
|
+
countryCode,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (!parsedPhoneNumber || !parsedPhoneNumber.isValid()) {
|
|
199
|
+
return err({
|
|
200
|
+
status: HttpStatus.BAD_REQUEST,
|
|
201
|
+
message: 'Invalid phone number. Only numbers',
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return ok(parsedPhoneNumber);
|
|
205
|
+
}
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
} from './common.types';
|
|
28
28
|
import { accountIdSchema } from './account.types';
|
|
29
29
|
import { SaStatus, tradeIdSchema, TradeZod } from './trade.types';
|
|
30
|
-
import { LegalEntityZod } from './legal-entity.types';
|
|
30
|
+
import { legalEntityIdSchema, LegalEntityZod } from './legal-entity.types';
|
|
31
31
|
import { extendZodWithOpenApi } from '@anatine/zod-openapi';
|
|
32
32
|
import { offeringIdSchema } from './offering.types';
|
|
33
33
|
import { userIdSchema, UserZod } from './user.types';
|
|
@@ -97,6 +97,7 @@ export const IInvestorAccountSummary = z.object({
|
|
|
97
97
|
totalPending: z.number(),
|
|
98
98
|
concentrationLiquidNetWorth: z.number(),
|
|
99
99
|
concentrationTotalNetWorth: z.number(),
|
|
100
|
+
postTradeConcentration: z.number().optional(),
|
|
100
101
|
});
|
|
101
102
|
|
|
102
103
|
export type IInvestorAccountSummary = z.infer<typeof IInvestorAccountSummary>;
|
|
@@ -159,24 +160,33 @@ export const InvestorAccountsFiltersZod = z.object({
|
|
|
159
160
|
amlStatus: z.nativeEnum(AMLStatus).optional(),
|
|
160
161
|
aicStatus: z.nativeEnum(AicStatus).optional(),
|
|
161
162
|
offeringId: z.lazy(() => offeringIdSchema).optional(),
|
|
162
|
-
regAQualified: z
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
163
|
+
regAQualified: z.preprocess(
|
|
164
|
+
(val) =>
|
|
165
|
+
val === 'true' || val === '1'
|
|
166
|
+
? true
|
|
167
|
+
: val === 'false' || val === '0'
|
|
168
|
+
? false
|
|
169
|
+
: val,
|
|
170
|
+
z.boolean().optional(),
|
|
171
|
+
),
|
|
172
|
+
regCfQualified: z.preprocess(
|
|
173
|
+
(val) =>
|
|
174
|
+
val === 'true' || val === '1'
|
|
175
|
+
? true
|
|
176
|
+
: val === 'false' || val === '0'
|
|
177
|
+
? false
|
|
178
|
+
: val,
|
|
179
|
+
z.boolean().optional(),
|
|
180
|
+
),
|
|
181
|
+
regDQualified: z.preprocess(
|
|
182
|
+
(val) =>
|
|
183
|
+
val === 'true' || val === '1'
|
|
184
|
+
? true
|
|
185
|
+
: val === 'false' || val === '0'
|
|
186
|
+
? false
|
|
187
|
+
: val,
|
|
188
|
+
z.boolean().optional(),
|
|
189
|
+
),
|
|
180
190
|
email: z.string().optional(),
|
|
181
191
|
accountId: accountIdSchema.optional(),
|
|
182
192
|
userType: z.nativeEnum(UserType).optional(),
|
|
@@ -222,24 +232,33 @@ export const IssuerInvestorAccountsFiltersZod = z.object({
|
|
|
222
232
|
amlStatus: z.nativeEnum(AMLStatus).optional(),
|
|
223
233
|
aicStatus: z.nativeEnum(AicStatus).optional(),
|
|
224
234
|
offeringId: z.lazy(() => offeringIdSchema).optional(),
|
|
225
|
-
regAQualified: z
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
235
|
+
regAQualified: z.preprocess(
|
|
236
|
+
(val) =>
|
|
237
|
+
val === 'true' || val === '1'
|
|
238
|
+
? true
|
|
239
|
+
: val === 'false' || val === '0'
|
|
240
|
+
? false
|
|
241
|
+
: val,
|
|
242
|
+
z.boolean().optional(),
|
|
243
|
+
),
|
|
244
|
+
regCfQualified: z.preprocess(
|
|
245
|
+
(val) =>
|
|
246
|
+
val === 'true' || val === '1'
|
|
247
|
+
? true
|
|
248
|
+
: val === 'false' || val === '0'
|
|
249
|
+
? false
|
|
250
|
+
: val,
|
|
251
|
+
z.boolean().optional(),
|
|
252
|
+
),
|
|
253
|
+
regDQualified: z.preprocess(
|
|
254
|
+
(val) =>
|
|
255
|
+
val === 'true' || val === '1'
|
|
256
|
+
? true
|
|
257
|
+
: val === 'false' || val === '0'
|
|
258
|
+
? false
|
|
259
|
+
: val,
|
|
260
|
+
z.boolean().optional(),
|
|
261
|
+
),
|
|
243
262
|
email: z.string().optional(),
|
|
244
263
|
startDate: dateSchema.optional(),
|
|
245
264
|
endDate: dateSchema.optional(),
|
|
@@ -248,37 +267,42 @@ export const IssuerInvestorAccountsFiltersZod = z.object({
|
|
|
248
267
|
|
|
249
268
|
export const InvestorAccountsFilters = z.object({
|
|
250
269
|
investorAccountType: z.nativeEnum(InvestorAccountType).optional(),
|
|
251
|
-
regAQualified: z
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
.refine((v) => !v || v === 'true' || v === 'false', {
|
|
255
|
-
message: 'regAQualified must be a boolean string',
|
|
256
|
-
}),
|
|
257
|
-
regCfQualified: z
|
|
258
|
-
.string()
|
|
259
|
-
.optional()
|
|
260
|
-
.refine((v) => !v || v === 'true' || v === 'false', {
|
|
261
|
-
message: 'regCfQualified must be a boolean string',
|
|
262
|
-
}),
|
|
263
|
-
regDQualified: z
|
|
264
|
-
.string()
|
|
265
|
-
.optional()
|
|
266
|
-
.refine((v) => !v || v === 'true' || v === 'false', {
|
|
267
|
-
message: 'regDQualified must be a boolean string',
|
|
268
|
-
}),
|
|
269
|
-
withPortfolio: z
|
|
270
|
-
.string()
|
|
271
|
-
.optional()
|
|
272
|
-
.refine((v) => !v || v === 'true' || v === 'false', {
|
|
273
|
-
message: 'withPortfolio should be boolean',
|
|
274
|
-
})
|
|
275
|
-
.transform((withPortfolio) =>
|
|
276
|
-
withPortfolio === 'true'
|
|
270
|
+
regAQualified: z.preprocess(
|
|
271
|
+
(val) =>
|
|
272
|
+
val === 'true' || val === '1'
|
|
277
273
|
? true
|
|
278
|
-
:
|
|
274
|
+
: val === 'false' || val === '0'
|
|
279
275
|
? false
|
|
280
|
-
:
|
|
281
|
-
),
|
|
276
|
+
: val,
|
|
277
|
+
z.boolean().optional(),
|
|
278
|
+
),
|
|
279
|
+
regCfQualified: z.preprocess(
|
|
280
|
+
(val) =>
|
|
281
|
+
val === 'true' || val === '1'
|
|
282
|
+
? true
|
|
283
|
+
: val === 'false' || val === '0'
|
|
284
|
+
? false
|
|
285
|
+
: val,
|
|
286
|
+
z.boolean().optional(),
|
|
287
|
+
),
|
|
288
|
+
regDQualified: z.preprocess(
|
|
289
|
+
(val) =>
|
|
290
|
+
val === 'true' || val === '1'
|
|
291
|
+
? true
|
|
292
|
+
: val === 'false' || val === '0'
|
|
293
|
+
? false
|
|
294
|
+
: val,
|
|
295
|
+
z.boolean().optional(),
|
|
296
|
+
),
|
|
297
|
+
withPortfolio: z.preprocess(
|
|
298
|
+
(val) =>
|
|
299
|
+
val === 'true' || val === '1'
|
|
300
|
+
? true
|
|
301
|
+
: val === 'false' || val === '0'
|
|
302
|
+
? false
|
|
303
|
+
: val,
|
|
304
|
+
z.boolean().optional(),
|
|
305
|
+
),
|
|
282
306
|
});
|
|
283
307
|
|
|
284
308
|
export const InvestorAccountExportFilters =
|
|
@@ -373,6 +397,42 @@ export type IPaginatedRegisteredInvestorUserZod = z.infer<
|
|
|
373
397
|
typeof IPaginatedRegisteredInvestorUserZod
|
|
374
398
|
>;
|
|
375
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Schema for raw query result from queryRegisteredInvestorUsers
|
|
402
|
+
* Matches the structure returned by getRawMany() with JSON aggregations.
|
|
403
|
+
* PostgreSQL/TypeORM automatically parses JSON fields, so they come as arrays.
|
|
404
|
+
*/
|
|
405
|
+
const IndividualAggregationZod = z.object({
|
|
406
|
+
id: z.lazy(() => individualIdSchema),
|
|
407
|
+
phone: z.lazy(() => PhoneZodSchema),
|
|
408
|
+
firstName: z.string().nullable(),
|
|
409
|
+
lastName: z.string().nullable(),
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const LegalEntityAggregationZod = z.object({
|
|
413
|
+
id: z.lazy(() => legalEntityIdSchema),
|
|
414
|
+
phone: z.lazy(() => PhoneZodSchema),
|
|
415
|
+
name: z.string().nullable(),
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
export const RegisteredInvestorUserRawZod = z.object({
|
|
419
|
+
id: z.lazy(() => userIdSchema),
|
|
420
|
+
name: z.string(),
|
|
421
|
+
email: z.string().email(),
|
|
422
|
+
investorAccountType: z.nativeEnum(InvestorAccountType).nullable(),
|
|
423
|
+
numberOfTrades: z.coerce.number().default(0),
|
|
424
|
+
invested: z.coerce.number().default(0),
|
|
425
|
+
createdAt: z.coerce.date(),
|
|
426
|
+
type: z.nativeEnum(UserType).nullable(),
|
|
427
|
+
investorAccountId: z.string().nullable(),
|
|
428
|
+
individuals: z.array(IndividualAggregationZod),
|
|
429
|
+
legalEntities: z.array(LegalEntityAggregationZod),
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
export type RegisteredInvestorUserRawZod = z.infer<
|
|
433
|
+
typeof RegisteredInvestorUserRawZod
|
|
434
|
+
>;
|
|
435
|
+
|
|
376
436
|
export const RegisteredInvestorUserFiltersZod = z.object({
|
|
377
437
|
search: z.string().optional().openapi({ example: 'John Doe' }),
|
|
378
438
|
startDate: dateSchema.optional().openapi({ example: 'MM/DD/YYYY' }),
|
|
@@ -1166,3 +1226,14 @@ export const INVESTOR_ACCOUNT_FIELDS_MAP = {
|
|
|
1166
1226
|
],
|
|
1167
1227
|
},
|
|
1168
1228
|
};
|
|
1229
|
+
|
|
1230
|
+
export const ExportRegisteredInvestorUsersFiltersZod = z.object({
|
|
1231
|
+
search: z.string().optional(),
|
|
1232
|
+
startDate: dateSchema.optional(),
|
|
1233
|
+
endDate: dateSchema.optional(),
|
|
1234
|
+
userType: z.nativeEnum(UserType).optional(),
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
export type ExportRegisteredInvestorUsersFiltersZod = z.infer<
|
|
1238
|
+
typeof ExportRegisteredInvestorUsersFiltersZod
|
|
1239
|
+
>;
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
UrlSchema,
|
|
15
15
|
} from './common.types';
|
|
16
16
|
import { accountIdSchema } from './account.types';
|
|
17
|
-
import { slugify } from '
|
|
17
|
+
import { slugify } from './contract-helpers';
|
|
18
18
|
import { fileIdSchema } from './file.types';
|
|
19
19
|
|
|
20
20
|
extendZodWithOpenApi(z);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { normalizePhoneNumber } from '
|
|
2
|
+
import { normalizePhoneNumber } from './contract-helpers';
|
|
3
3
|
import { CountryCodes } from './countries.types';
|
|
4
4
|
import { CountryCode } from 'libphonenumber-js';
|
|
5
5
|
import { ok } from 'neverthrow';
|
|
@@ -56,6 +56,7 @@ export const SecondaryTradeFiltersZod = z.object({
|
|
|
56
56
|
complianceReview: z.nativeEnum(ComplianceReview).optional(),
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
+
export type SecondaryTradeFiltersZod = z.infer<typeof SecondaryTradeFiltersZod>;
|
|
59
60
|
export const ExportSecondaryTradesResponse = z.object({
|
|
60
61
|
message: z.string(),
|
|
61
62
|
});
|
|
@@ -85,3 +86,10 @@ export const IPaginatedSecondaryTradeSummaryResponse = z.object({
|
|
|
85
86
|
export type IPaginatedSecondaryTradeSummaryResponse = z.infer<
|
|
86
87
|
typeof IPaginatedSecondaryTradeSummaryResponse
|
|
87
88
|
>;
|
|
89
|
+
|
|
90
|
+
export const CountResultZod = z.array(
|
|
91
|
+
z.object({
|
|
92
|
+
count: z.string(),
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
export type CountResultZod = z.infer<typeof CountResultZod>;
|
|
@@ -30,7 +30,12 @@ import {
|
|
|
30
30
|
paymentMethodIdSchema,
|
|
31
31
|
PaymentMethodType,
|
|
32
32
|
} from './payment-methods.types';
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
investorAccountIdSchema,
|
|
35
|
+
InvestorAccountExportFilters,
|
|
36
|
+
ComplianceInvestorAccountExportFilters,
|
|
37
|
+
RegisteredInvestorUserRawZod,
|
|
38
|
+
} from './investor-account.types';
|
|
34
39
|
import {
|
|
35
40
|
tradeLineItemIdSchema,
|
|
36
41
|
TradeLineItemZod,
|
|
@@ -40,6 +45,18 @@ import { TransactionZod } from './transaction.types';
|
|
|
40
45
|
import { accountIdSchema } from './account.types';
|
|
41
46
|
import { userIdSchema } from './user.types';
|
|
42
47
|
import { issuerIdSchema } from './issuer.types';
|
|
48
|
+
import {
|
|
49
|
+
GetSecondaryCustomerFiltersZod,
|
|
50
|
+
SecondaryCustomer,
|
|
51
|
+
} from './secondary-customer.types';
|
|
52
|
+
import {
|
|
53
|
+
SecondaryTrade,
|
|
54
|
+
SecondaryTradeFiltersZod,
|
|
55
|
+
} from './secondary-trade.types';
|
|
56
|
+
|
|
57
|
+
// Stub types for backend entities (not included in contracts package)
|
|
58
|
+
type InvestorAccount = Record<string, unknown>;
|
|
59
|
+
type Trade = Record<string, unknown>;
|
|
43
60
|
|
|
44
61
|
extendZodWithOpenApi(z);
|
|
45
62
|
export const CheckResultsSchema = z.object({
|
|
@@ -107,6 +124,12 @@ export enum TradeSystemLogType {
|
|
|
107
124
|
KYC_CHECK = 'KYC Check',
|
|
108
125
|
AML_CHECK = 'AML Check',
|
|
109
126
|
AIC_CHECK = 'AIC Check',
|
|
127
|
+
USER_AGE_CHECK = 'User Age Check',
|
|
128
|
+
KYC_NAME_CHECK = 'KYC Name Check',
|
|
129
|
+
AML_NAME_CHECK = 'AML Name Check',
|
|
130
|
+
AIC_NAME_CHECK = 'AIC Name Check',
|
|
131
|
+
KYC_DOB_CHECK = 'KYC DOB Check',
|
|
132
|
+
AIC_SSN_CHECK = 'AIC SSN Check',
|
|
110
133
|
// Data Integrity Checks For Imported Trades
|
|
111
134
|
SHARES_CHECK = 'Shares Check',
|
|
112
135
|
PRICE_PER_UNIT_CHECK = 'Price Per Unit Check',
|
|
@@ -447,6 +470,7 @@ export const ExportTradesQuery = PaginationOptionsZod.merge(TradeFiltersZod)
|
|
|
447
470
|
.optional()
|
|
448
471
|
.default(TradeExportType.PAGE),
|
|
449
472
|
});
|
|
473
|
+
export type ExportTradesQuery = z.infer<typeof ExportTradesQuery>;
|
|
450
474
|
|
|
451
475
|
const tradesColumn = z.enum([
|
|
452
476
|
'id',
|
|
@@ -847,3 +871,61 @@ export const NON_CANCELABLE_COMPLIANCE_REVIEW_STATUSES = [
|
|
|
847
871
|
ComplianceReview.REJECTED,
|
|
848
872
|
ComplianceReview.CANCELLED,
|
|
849
873
|
];
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Filter type for Investor Account exports in workers
|
|
877
|
+
* Extends the base InvestorAccountExportFilters with additional optional fields needed for bulk exports
|
|
878
|
+
*/
|
|
879
|
+
export type WorkerInvestorAccountExportFilters = Partial<
|
|
880
|
+
(InvestorAccountExportFilters | ComplianceInvestorAccountExportFilters) & {
|
|
881
|
+
isRegisteredUsersExport: boolean | null;
|
|
882
|
+
search: string | null;
|
|
883
|
+
startDate: Date | null;
|
|
884
|
+
endDate: Date | null;
|
|
885
|
+
userType: string | null;
|
|
886
|
+
accountId: string | null;
|
|
887
|
+
}
|
|
888
|
+
>;
|
|
889
|
+
/**
|
|
890
|
+
* Filter type for Trade exports
|
|
891
|
+
* Extends IssuersTradeFiltersZod with accountId
|
|
892
|
+
*/
|
|
893
|
+
export type TradeExportFilters = Partial<
|
|
894
|
+
IssuersTradeFiltersZod & {
|
|
895
|
+
accountId: string | null;
|
|
896
|
+
}
|
|
897
|
+
>;
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Filter type for Compliance Trade exports
|
|
901
|
+
*/
|
|
902
|
+
export type ComplianceTradeExportFilters = Partial<ExportTradesQuery>;
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Filter type for Secondary Customer exports
|
|
906
|
+
*/
|
|
907
|
+
export type SecondaryCustomerExportFilters =
|
|
908
|
+
Partial<GetSecondaryCustomerFiltersZod>;
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Filter type for Secondary Trade exports
|
|
912
|
+
*/
|
|
913
|
+
export type SecondaryTradeExportFilters = Partial<SecondaryTradeFiltersZod>;
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* Combined filter type for all bulk export types
|
|
917
|
+
* Used in the ExportBulkDataDto
|
|
918
|
+
*/
|
|
919
|
+
export type BulkExportFilters =
|
|
920
|
+
| WorkerInvestorAccountExportFilters
|
|
921
|
+
| TradeExportFilters
|
|
922
|
+
| ComplianceTradeExportFilters
|
|
923
|
+
| SecondaryCustomerExportFilters
|
|
924
|
+
| SecondaryTradeExportFilters;
|
|
925
|
+
|
|
926
|
+
export type BulkExportResponse =
|
|
927
|
+
| InvestorAccount[]
|
|
928
|
+
| RegisteredInvestorUserRawZod[]
|
|
929
|
+
| Trade[]
|
|
930
|
+
| SecondaryCustomer[]
|
|
931
|
+
| SecondaryTrade[];
|
|
@@ -157,6 +157,7 @@ export const UserZod = IBaseEntity.extend({
|
|
|
157
157
|
onboarding: z.string().nullable(),
|
|
158
158
|
account: AccountWithoutUsersZod.optional(),
|
|
159
159
|
active: z.boolean(),
|
|
160
|
+
status: z.nativeEnum(UserStatus),
|
|
160
161
|
userLogin: IBaseEntity.extend({
|
|
161
162
|
firstName: z.string(),
|
|
162
163
|
lastName: z.string(),
|
|
@@ -284,6 +285,7 @@ export const UsersSummaryFilterZod = z.object({
|
|
|
284
285
|
portalType: z.nativeEnum(PortalType).optional(),
|
|
285
286
|
search: z.string().trim().max(50).optional(),
|
|
286
287
|
selectRole: z.nativeEnum(UserRole).optional(),
|
|
288
|
+
status: z.nativeEnum(UserStatus).optional(),
|
|
287
289
|
});
|
|
288
290
|
export type UsersSummaryFilterZod = z.infer<typeof UsersSummaryFilterZod>;
|
|
289
291
|
|
package/common/types/zip.type.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { validateCanadaZipCode, validateUSZipCode } from '
|
|
2
|
+
import { validateCanadaZipCode, validateUSZipCode } from './contract-helpers';
|
|
3
3
|
import { CountryCode, CountryEnumSchema } from './countries.types';
|
|
4
4
|
|
|
5
5
|
export function refineAddressZip(
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
AccountsIncludeQuery,
|
|
10
10
|
AccountZod,
|
|
11
11
|
accountIdSchema,
|
|
12
|
+
InternalError,
|
|
12
13
|
} from '../../../common/types';
|
|
13
14
|
import { z } from 'zod';
|
|
14
15
|
|
|
@@ -32,6 +33,7 @@ export const accountsContract = c.router(
|
|
|
32
33
|
401: UnauthorizedError,
|
|
33
34
|
403: ForbiddenError,
|
|
34
35
|
404: NotFoundError,
|
|
36
|
+
500: InternalError,
|
|
35
37
|
},
|
|
36
38
|
},
|
|
37
39
|
getAccounts: {
|
|
@@ -49,6 +51,7 @@ export const accountsContract = c.router(
|
|
|
49
51
|
200: IPaginatedAccount,
|
|
50
52
|
401: UnauthorizedError,
|
|
51
53
|
403: ForbiddenError,
|
|
54
|
+
500: InternalError,
|
|
52
55
|
},
|
|
53
56
|
},
|
|
54
57
|
},
|