@dalmore/api-contracts 0.0.0-dev.785b227 → 0.0.0-dev.801385

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.
Files changed (60) hide show
  1. package/common/types/account-contact.types.ts +2 -1
  2. package/common/types/account-manager.types.ts +3 -7
  3. package/common/types/account-setting.types.ts +65 -0
  4. package/common/types/account.types.ts +1 -0
  5. package/common/types/activity.types.ts +1 -1
  6. package/common/types/auth.types.ts +7 -18
  7. package/common/types/bonus-tier.types.ts +33 -0
  8. package/common/types/cart.types.ts +4 -1
  9. package/common/types/common.types.ts +38 -6
  10. package/common/types/contact-us.types.ts +6 -2
  11. package/common/types/covered-person.types.ts +2 -1
  12. package/common/types/dashboard.types.ts +2 -9
  13. package/common/types/disbursements.types.ts +119 -3
  14. package/common/types/escrow-account.types.ts +3 -3
  15. package/common/types/file.types.ts +20 -4
  16. package/common/types/i-will-do-it-later.types.ts +68 -0
  17. package/common/types/index.ts +2 -0
  18. package/common/types/individuals.types.ts +5 -17
  19. package/common/types/investor-account.types.ts +2 -1
  20. package/common/types/invite.types.ts +27 -1
  21. package/common/types/issuer-offering.types.ts +13 -17
  22. package/common/types/issuer-payment-method.types.ts +41 -0
  23. package/common/types/issuer.types.ts +9 -0
  24. package/common/types/legal-entity.types.ts +3 -2
  25. package/common/types/note.types.ts +1 -1
  26. package/common/types/notification.types.ts +515 -29
  27. package/common/types/offering.types.ts +4 -9
  28. package/common/types/site-settings.types.ts +2 -1
  29. package/common/types/site.types.ts +2 -9
  30. package/common/types/{trade-line-item.type.ts → trade-line-item.types.ts} +2 -9
  31. package/common/types/trade.types.ts +48 -2
  32. package/common/types/transaction.types.ts +12 -1
  33. package/common/types/trusted-contact.types.ts +7 -7
  34. package/common/types/user.types.ts +17 -33
  35. package/contracts/clients/cart/index.ts +21 -1
  36. package/contracts/clients/index.ts +6 -0
  37. package/contracts/clients/issuer-payment-methods/index.ts +39 -0
  38. package/contracts/clients/payment-methods/index.ts +85 -0
  39. package/contracts/clients/trade-line-items/index.ts +1 -1
  40. package/contracts/clients/trades/index.ts +1 -1
  41. package/contracts/clients/transactions/index.ts +37 -0
  42. package/contracts/compliance/account-settings/index.ts +59 -0
  43. package/contracts/compliance/auth/index.ts +4 -3
  44. package/contracts/compliance/bonus-tiers/index.ts +21 -2
  45. package/contracts/compliance/index.ts +4 -0
  46. package/contracts/compliance/invites/index.ts +4 -12
  47. package/contracts/compliance/notification-channels/index.ts +251 -0
  48. package/contracts/compliance/trade-line-items/index.ts +1 -1
  49. package/contracts/compliance/trades/index.ts +19 -0
  50. package/contracts/compliance/users/index.ts +21 -0
  51. package/contracts/investors/bonus-tiers/index.ts +18 -0
  52. package/contracts/investors/individuals/index.ts +22 -0
  53. package/contracts/investors/trade-line-items/index.ts +1 -1
  54. package/contracts/issuers/account-settings/index.ts +36 -0
  55. package/contracts/issuers/auth/index.ts +4 -3
  56. package/contracts/issuers/bonus-tiers/index.ts +18 -0
  57. package/contracts/issuers/disbursements/index.ts +48 -12
  58. package/contracts/issuers/index.ts +4 -0
  59. package/contracts/issuers/notification-channels/index.ts +251 -0
  60. package/package.json +1 -1
@@ -6,6 +6,7 @@ import { PhoneZodSchema } from './phone.type';
6
6
  import {
7
7
  AccountContactType,
8
8
  AccountZod,
9
+ EmailSchema,
9
10
  IPaginationMeta,
10
11
  } from './common.types';
11
12
  import { IBaseEntity } from './entity.types';
@@ -60,7 +61,7 @@ export const PatchAccountContactZod = z.object({
60
61
  .openapi({ example: 'Mr' }),
61
62
  firstName: z.string().min(1).max(150).openapi({ example: 'John' }),
62
63
  lastName: z.string().min(1).max(150).openapi({ example: 'Doe' }),
63
- email: z.string().email().openapi({ example: 'john.doe@example.com' }),
64
+ email: EmailSchema.openapi({ example: 'john.doe@example.com' }),
64
65
  phone: PhoneZodSchema.nullable()
65
66
  .optional()
66
67
  .openapi({ example: '+12124567890' }),
@@ -4,7 +4,7 @@ import { TypeID } from 'typeid-js';
4
4
  import { IBaseEntity } from './entity.types';
5
5
  import { PhoneNumberData } from './sms.types';
6
6
  import { userIdSchema, UserZod } from './user.types';
7
- import { IPaginationMeta } from './common.types';
7
+ import { EmailSchema, IPaginationMeta } from './common.types';
8
8
  import { PhoneZodSchema } from './phone.type';
9
9
 
10
10
  extendZodWithOpenApi(z);
@@ -68,7 +68,7 @@ export type IAccountManagerZod = z.infer<typeof IAccountManagerZod>;
68
68
  export const PostAccountManagerZod = z.object({
69
69
  firstName: z.string().min(1).max(255).openapi({ example: 'John' }),
70
70
  lastName: z.string().min(1).max(255).openapi({ example: 'Doe' }),
71
- email: z.string().email().openapi({ example: 'john.doe@example.com' }),
71
+ email: EmailSchema,
72
72
  phone: PhoneZodSchema.nullable()
73
73
  .optional()
74
74
  .openapi({ example: '+12124567890' }),
@@ -80,11 +80,7 @@ export type PostAccountManagerZod = z.infer<typeof PostAccountManagerZod>;
80
80
  export const PatchAccountManagerZod = z.object({
81
81
  firstName: z.string().min(1).max(255).optional().openapi({ example: 'John' }),
82
82
  lastName: z.string().min(1).max(255).optional().openapi({ example: 'Doe' }),
83
- email: z
84
- .string()
85
- .email()
86
- .optional()
87
- .openapi({ example: 'john.doe@example.com' }),
83
+ email: EmailSchema.optional(),
88
84
  phone: PhoneZodSchema.nullable()
89
85
  .optional()
90
86
  .openapi({ example: '+12124567890' }),
@@ -0,0 +1,65 @@
1
+ import { extendZodWithOpenApi } from '@anatine/zod-openapi';
2
+ import { z } from 'zod';
3
+ import { TypeID } from 'typeid-js';
4
+ import { IBaseEntity } from './entity.types';
5
+ import { IPaginationMeta } from './common.types';
6
+ import { accountIdSchema } from './account.types';
7
+
8
+ extendZodWithOpenApi(z);
9
+
10
+ export const accountSettingIdSchema = z.string().refine(
11
+ (value) => {
12
+ try {
13
+ const tid = TypeID.fromString(value);
14
+ return tid.getType() === 'account_setting';
15
+ } catch {
16
+ return false;
17
+ }
18
+ },
19
+ {
20
+ message:
21
+ 'Invalid account setting ID format. Must be a valid TypeID with "account_setting" prefix.',
22
+ },
23
+ );
24
+
25
+ export const IAccountSettingZod = IBaseEntity.extend({
26
+ id: accountSettingIdSchema.openapi({
27
+ example: 'account_setting_01j5y5ghx8fvc83dmx3pznq7hv',
28
+ }),
29
+ accountId: z.string().openapi({
30
+ example: 'account_01j5y5ghx8fvc83dmx3pznq7hv',
31
+ }),
32
+ });
33
+ export type IAccountSettingZod = z.infer<typeof IAccountSettingZod>;
34
+
35
+ export const IPaginatedAccountSetting = z.object({
36
+ items: z.array(IAccountSettingZod),
37
+ meta: IPaginationMeta,
38
+ });
39
+ export type IPaginatedAccountSetting = z.infer<typeof IPaginatedAccountSetting>;
40
+
41
+ export const AccountSettingsFilters = z.object({
42
+ accountId: accountIdSchema.optional(),
43
+ });
44
+ export type AccountSettingsFilters = z.infer<typeof AccountSettingsFilters>;
45
+
46
+ export const accountSettingsInclude = z.enum(['notificationChannels']);
47
+
48
+ export const AccountSettingsIncludeQuery = z.object({
49
+ include: z
50
+ .string()
51
+ .optional()
52
+ .transform((str) => (str ? str.split(',') : []))
53
+ .refine(
54
+ (includes) =>
55
+ includes.every((include) =>
56
+ accountSettingsInclude.options.includes(include as any),
57
+ ),
58
+ {
59
+ message: `Invalid include value. Valid values are: ${accountSettingsInclude.options.join(', ')}`,
60
+ },
61
+ ),
62
+ });
63
+ export type AccountSettingsIncludeQuery = z.infer<
64
+ typeof AccountSettingsIncludeQuery
65
+ >;
@@ -91,6 +91,7 @@ export const AccountFiltersZod = z.object({
91
91
  const accountsInclude = z.enum([
92
92
  'accountManager',
93
93
  'accountIntegrations',
94
+ 'accountSettings',
94
95
  'apiKeys',
95
96
  'assets',
96
97
  'dataRooms',
@@ -199,7 +199,7 @@ export const ActivityZod = IBaseEntity.extend({
199
199
  userId: z.string().nullable(),
200
200
  activityTypeId: z.string(),
201
201
  accountId: z.string().nullable(),
202
- user: UserForActivityZod,
202
+ user: UserForActivityZod.nullable(),
203
203
  activityType: ActivityTypeZod,
204
204
  targetObject: z.string().nullable().optional(),
205
205
  __entity: z.string().optional(),
@@ -8,6 +8,7 @@ import {
8
8
  ManagedByType,
9
9
  UserRole,
10
10
  createUrlSchema,
11
+ EmailSchema,
11
12
  } from './common.types';
12
13
  import { TypeID } from 'typeid-js';
13
14
  import { TwoFactorMethod } from './sms.types';
@@ -60,13 +61,7 @@ const BaseAuthBody = z.object({
60
61
  .openapi({
61
62
  example: 'Armstrong',
62
63
  }),
63
- email: z
64
- .string()
65
- .email()
66
- .transform((val) => val.toLowerCase())
67
- .openapi({
68
- example: 'neil@dalmoregroup.com',
69
- }),
64
+ email: EmailSchema,
70
65
  password: PasswordSchema,
71
66
  });
72
67
 
@@ -99,13 +94,7 @@ export const IChangePasswordBodyZod = z
99
94
  });
100
95
 
101
96
  export const LoginBody = z.object({
102
- email: z
103
- .string()
104
- .email()
105
- .transform((val) => val.toLowerCase())
106
- .openapi({
107
- example: 'neil@dalmoregroup.com',
108
- }),
97
+ email: EmailSchema,
109
98
  password: z.string().openapi({
110
99
  example: 'Secretpassword6#',
111
100
  }),
@@ -297,7 +286,7 @@ export const RegisterBodyInvestors = BaseAuthBody.extend({
297
286
  });
298
287
 
299
288
  export const ResetPasswordVerifyBody = z.object({
300
- email: z.string().email(),
289
+ email: EmailSchema,
301
290
  code: z.string().length(6, 'code is not 6 characters').openapi({
302
291
  example: '123456',
303
292
  }),
@@ -315,7 +304,7 @@ export type ResetPasswordVerifyResponseType = z.infer<
315
304
  >;
316
305
 
317
306
  export const ResetPasswordBody = z.object({
318
- email: z.string().email(),
307
+ email: EmailSchema,
319
308
  code: z.string().length(6, 'code is not 6 characters').openapi({
320
309
  example: '123456',
321
310
  }),
@@ -337,7 +326,7 @@ export type ClientAuthSuccessResponse = z.infer<
337
326
  >;
338
327
 
339
328
  export const AdminForgotPasswordRequestZod = z.object({
340
- email: z.string().email(),
329
+ email: EmailSchema,
341
330
  portal: z.nativeEnum(PortalType),
342
331
  });
343
332
  export type AdminForgotPasswordRequestZod = z.infer<
@@ -345,7 +334,7 @@ export type AdminForgotPasswordRequestZod = z.infer<
345
334
  >;
346
335
 
347
336
  export const InvestorForgotPasswordRequestZod = z.object({
348
- email: z.string().email(),
337
+ email: EmailSchema,
349
338
  url: createUrlSchema({ strict: true }),
350
339
  });
351
340
  export type InvestorForgotPasswordRequestZod = z.infer<
@@ -98,6 +98,15 @@ export type EstimateBonusTierCalculationZod = z.infer<
98
98
  typeof EstimateBonusTierCalculationZod
99
99
  >;
100
100
 
101
+ export const ComplianceEstimateBonusTierCalculationZod =
102
+ EstimateBonusTierCalculationZod.and(
103
+ z.object({
104
+ accountId: accountIdSchema,
105
+ }),
106
+ );
107
+ export type ComplianceEstimateBonusTierCalculationZod = z.infer<
108
+ typeof ComplianceEstimateBonusTierCalculationZod
109
+ >;
101
110
  export const EstimateBonusTierCalculationResponseZod = z.object({
102
111
  bonusTierId: bonusTierIdSchema
103
112
  .nullable()
@@ -145,3 +154,27 @@ export const CompliancePostBonusTierZod = PostBonusTierZod.extend({
145
154
  export type CompliancePostBonusTierZod = z.infer<
146
155
  typeof CompliancePostBonusTierZod
147
156
  >;
157
+ export const PurchaseCalculationZod = z.object({
158
+ assetId: assetIdSchema,
159
+ totalAmount: z.number().positive().openapi({ example: 1000 }),
160
+ purchasedShares: z.number().positive().int().openapi({ example: 100 }),
161
+ });
162
+ export type PurchaseCalculationZod = z.infer<typeof PurchaseCalculationZod>;
163
+
164
+ export const PurchaseCalculationResponseZod = z.object({
165
+ bonusTierId: bonusTierIdSchema
166
+ .nullable()
167
+ .openapi({ example: 'bonus_tier_01j5y5ghx5fg68d663j1fvy2x7' }),
168
+ assetId: assetIdSchema.openapi({
169
+ example: 'asset_00041061050r3gg28a1c60t3gf',
170
+ }),
171
+ type: z
172
+ .nativeEnum(BonusType)
173
+ .nullable()
174
+ .openapi({ example: BonusType.PERCENTAGE }),
175
+ bonusShares: z.number().int().openapi({ example: 100 }),
176
+ });
177
+
178
+ export type PurchaseCalculationResponseZod = z.infer<
179
+ typeof PurchaseCalculationResponseZod
180
+ >;
@@ -3,6 +3,7 @@ import { extendZodWithOpenApi } from '@anatine/zod-openapi';
3
3
  import { TradeStatus } from './common.types';
4
4
  import { investorAccountIdSchema } from './investor-account.types';
5
5
  import { paymentMethodIdSchema } from './payment-methods.types';
6
+ import { userIdSchema } from './user.types';
6
7
 
7
8
  extendZodWithOpenApi(z);
8
9
 
@@ -10,7 +11,9 @@ export const PlaceTradeResponse = z.object({
10
11
  tradeStatus: z.nativeEnum(TradeStatus).optional(),
11
12
  });
12
13
  export const PlaceTradeBody = z.object({});
13
-
14
+ export const ClientPlacetradeBody = z.object({
15
+ userId: userIdSchema,
16
+ });
14
17
  export const PatchCartBody = z.object({
15
18
  investorAccountId: investorAccountIdSchema.optional(),
16
19
  paymentMethodId: paymentMethodIdSchema.optional(),
@@ -69,6 +69,10 @@ export enum BaseStatus {
69
69
  VOIDED = 'VOIDED',
70
70
  JOIN = 'JOIN',
71
71
  RESTORE = 'RESTORE',
72
+ ACTIVE = 'ACTIVE',
73
+ LOCKED = 'LOCKED',
74
+ ENABLED = 'ENABLED',
75
+ DISABLED = 'DISABLED',
72
76
  }
73
77
 
74
78
  export const UserWithoutPasswordAccountZod = IBaseEntity.extend({
@@ -111,6 +115,10 @@ export enum HttpMethod {
111
115
  OPTIONS = 'OPTIONS',
112
116
  }
113
117
 
118
+ export enum UserStatus {
119
+ ACTIVE = 'ACTIVE',
120
+ LOCKED = 'LOCKED',
121
+ }
114
122
  export const SENSITIVE_PATTERNS = [
115
123
  /password/i,
116
124
  /credit.*card/i,
@@ -200,11 +208,6 @@ export enum UserType {
200
208
  DEMO = 'DEMO',
201
209
  }
202
210
 
203
- export enum UserStatus {
204
- ACTIVE = 'ACTIVE',
205
- LOCKED = 'LOCKED',
206
- }
207
-
208
211
  export enum UserRole {
209
212
  API_KEY = 'API_KEY',
210
213
  IMPORT = 'IMPORT',
@@ -306,7 +309,7 @@ export const AuthUserReq = BaseAuthReq.extend({
306
309
  lastName: z.string(),
307
310
  email: z.string().email(),
308
311
  provider: z.string(),
309
- status: z.lazy(() => z.nativeEnum(UserStatus)),
312
+ locked: z.boolean(),
310
313
  lastLoginAt: z.date().nullable(),
311
314
  loginCount: z.number(),
312
315
  requiresTwoFactorSetup: z.boolean().optional(),
@@ -809,6 +812,8 @@ export enum EventField {
809
812
  SA_STATUS = 'saStatus',
810
813
  ONBOARDING_STATUS = 'onboardingStatus',
811
814
  TRANSACTION_STATUS = 'transactionStatus',
815
+ ACTIVE = 'active',
816
+ LOCKED = 'locked',
812
817
  }
813
818
 
814
819
  export enum EventName {
@@ -1518,3 +1523,30 @@ export const SUBJECT_TYPE_MAP: Record<BulkExportType, string> = {
1518
1523
  [BulkExportType.SECONDARY_CUSTOMERS]: 'Secondary Customers',
1519
1524
  [BulkExportType.SECONDARY_TRADES]: 'Secondary Trades',
1520
1525
  };
1526
+
1527
+ export const StringToBooleanSchema = z.preprocess(
1528
+ (val) =>
1529
+ val === 'true' || val === '1'
1530
+ ? true
1531
+ : val === 'false' || val === '0'
1532
+ ? false
1533
+ : val,
1534
+ z.boolean(),
1535
+ );
1536
+ export type StringToBooleanSchema = z.infer<typeof StringToBooleanSchema>;
1537
+
1538
+ /**
1539
+ * Reusable email schema that validates email format, transforms to lowercase,
1540
+ * and includes OpenAPI metadata.
1541
+ *
1542
+ * @example
1543
+ * EmailSchema.parse('John.Doe@EXAMPLE.COM'); // returns 'john.doe@example.com'
1544
+ * EmailSchema.optional().parse(undefined); // returns undefined
1545
+ */
1546
+ export const EmailSchema = z
1547
+ .string()
1548
+ .email()
1549
+ .transform((val) => val.toLowerCase())
1550
+ .openapi({
1551
+ example: 'neil@dalmoregroup.com',
1552
+ });
@@ -1,7 +1,11 @@
1
1
  import { z } from 'zod';
2
2
  import { extendZodWithOpenApi } from '@anatine/zod-openapi';
3
3
  import { TypeID } from 'typeid-js';
4
- import { BaseContactUsOptions, createUrlSchema } from './common.types';
4
+ import {
5
+ BaseContactUsOptions,
6
+ createUrlSchema,
7
+ EmailSchema,
8
+ } from './common.types';
5
9
  extendZodWithOpenApi(z);
6
10
 
7
11
  export const contactUsIdSchema = z.string().refine(
@@ -55,7 +59,7 @@ export const InvestorPostContactUsZod = PostContactUsZod.extend({
55
59
  export type InvestorPostContactUsZod = z.infer<typeof InvestorPostContactUsZod>;
56
60
 
57
61
  export const PublicInvestorContactUsZod = PostContactUsZod.extend({
58
- email: z.string().email().openapi({ example: 'email@example.com' }),
62
+ email: EmailSchema,
59
63
  firstName: z.string().min(1).max(100).openapi({ example: 'John' }),
60
64
  lastName: z.string().min(1).max(100).openapi({ example: 'Doe' }),
61
65
  contactOption: z.nativeEnum(InvestorContactOptions).openapi({
@@ -6,6 +6,7 @@ import {
6
6
  ComplianceReview,
7
7
  CoveredPersonsRoleType,
8
8
  CoveredPersonsStatus,
9
+ EmailSchema,
9
10
  IDType,
10
11
  IPaginationMeta,
11
12
  } from './common.types';
@@ -137,7 +138,7 @@ export const PatchCoveredPersonsZod = z
137
138
  .openapi({ example: CoveredPersonsRoleType.ACCOUNTING }),
138
139
 
139
140
  ownership: z.number().min(0).max(100).openapi({ example: 15 }).optional(),
140
- email: z.string().nullable().optional(),
141
+ email: EmailSchema.nullable().optional(),
141
142
  idType: z
142
143
  .nativeEnum(IDType)
143
144
  .optional()
@@ -7,6 +7,7 @@ import {
7
7
  FileLabels,
8
8
  IPaginationMeta,
9
9
  OfferingType,
10
+ StringToBooleanSchema,
10
11
  } from './common.types';
11
12
  import { accountIdSchema } from './account.types';
12
13
 
@@ -330,15 +331,7 @@ export const GetInvestmentDashboardQueryZod = z.object({
330
331
  offerings: OfferingsArrayQueryZod.optional(),
331
332
  offeringTypes: OfferingTypesQueryZod.optional(),
332
333
  timeFilteringType: z.nativeEnum(DashboardTimeFilteringType),
333
- debug: z.preprocess(
334
- (val) =>
335
- val === 'true' || val === '1'
336
- ? true
337
- : val === 'false' || val === '0'
338
- ? false
339
- : val,
340
- z.boolean().optional(),
341
- ),
334
+ debug: StringToBooleanSchema.optional(),
342
335
  });
343
336
  export type GetInvestmentDashboardQueryZod = z.infer<
344
337
  typeof GetInvestmentDashboardQueryZod
@@ -11,14 +11,17 @@ import {
11
11
  import { tradeIdSchema } from './trade.types';
12
12
  import { accountIdSchema } from './account.types';
13
13
  import { TypeID } from 'typeid-js';
14
- import { issuerIdSchema } from './issuer.types';
15
- import { issuerBankAccountIdSchema } from './issuer-bank-account.types';
14
+ import { IIssuer, issuerIdSchema } from './issuer.types';
15
+ import {
16
+ IIssuerBankAccount,
17
+ issuerBankAccountIdSchema,
18
+ } from './issuer-bank-account.types';
16
19
  import { userIdSchema, UserZod } from './user.types';
17
20
  import {
18
21
  PostDisbursementAdjustmentZod,
19
22
  DisbursementAdjustmentType,
20
23
  } from './disbursement-adjustment.types';
21
- import { escrowAccountIdSchema } from './escrow-account.types';
24
+ import { IEscrowAccount, escrowAccountIdSchema } from './escrow-account.types';
22
25
 
23
26
  export const disbursementIdSchema = z.string().refine(
24
27
  (value) => {
@@ -308,3 +311,116 @@ export const DisbursementSummaryZod = z.object({
308
311
  amountToBeTransferred: z.number(),
309
312
  });
310
313
  export type DisbursementSummaryZod = z.infer<typeof DisbursementSummaryZod>;
314
+
315
+ // GET /disbursements/preview query parameters
316
+ export const GetDisbursementPreviewQueryZod = z.object({
317
+ offeringId: offeringIdSchema,
318
+ selectedTrades: z
319
+ .string()
320
+ .optional()
321
+ .transform((str) => (str ? str.split(',') : []))
322
+ .refine(
323
+ (ids) =>
324
+ ids.every((id) => {
325
+ try {
326
+ const tid = TypeID.fromString(id);
327
+ return tid.getType() === 'trade';
328
+ } catch {
329
+ return false;
330
+ }
331
+ }),
332
+ {
333
+ message: 'All selected trades must be valid trade IDs',
334
+ },
335
+ )
336
+ .openapi({
337
+ example: 'trade_01abc123,trade_01def456',
338
+ }),
339
+ });
340
+ export type GetDisbursementPreviewQueryZod = z.infer<
341
+ typeof GetDisbursementPreviewQueryZod
342
+ >;
343
+
344
+ // Context fields for disbursement preview
345
+ export const DisbursementPreviewContextZod = z.object({
346
+ issuer: z.lazy(() => IIssuer).nullable(),
347
+ offering: z.lazy(() => IOffering).nullable(),
348
+ escrowAccount: z.lazy(() => IEscrowAccount).nullable(),
349
+ destinationBank: z.lazy(() => IIssuerBankAccount).nullable(),
350
+ });
351
+ export type DisbursementPreviewContextZod = z.infer<
352
+ typeof DisbursementPreviewContextZod
353
+ >;
354
+
355
+ // Balance values for disbursement preview
356
+ export const DisbursementPreviewBalanceZod = z.object({
357
+ totalRaised: z.number(),
358
+ totalDisbursed: z.number(),
359
+ pending: z.number(),
360
+ refundPool: z.number(),
361
+ availableForDisbursement: z.number(),
362
+ });
363
+ export type DisbursementPreviewBalanceZod = z.infer<
364
+ typeof DisbursementPreviewBalanceZod
365
+ >;
366
+
367
+ // Trade summary for disbursement preview
368
+ export const DisbursementPreviewTradeSummaryZod = z.object({
369
+ selectedTradesCount: z.number(),
370
+ selectedTradesTotalAmount: z.number(),
371
+ });
372
+ export type DisbursementPreviewTradeSummaryZod = z.infer<
373
+ typeof DisbursementPreviewTradeSummaryZod
374
+ >;
375
+
376
+ // Contingency validation result
377
+ export const DisbursementPreviewContingencyZod = z.object({
378
+ contingencyPassed: z.boolean(),
379
+ contingencyMessage: z.string().nullable(),
380
+ contingencyAmount: z.number(),
381
+ });
382
+ export type DisbursementPreviewContingencyZod = z.infer<
383
+ typeof DisbursementPreviewContingencyZod
384
+ >;
385
+
386
+ // Amount defaults and constraints
387
+ export const DisbursementPreviewAmountZod = z.object({
388
+ defaultAmount: z.number(),
389
+ minAmount: z.number(),
390
+ maxAmount: z.number(),
391
+ });
392
+ export type DisbursementPreviewAmountZod = z.infer<
393
+ typeof DisbursementPreviewAmountZod
394
+ >;
395
+
396
+ // Complete response for GET /disbursements/preview
397
+ export const DisbursementPreviewZod = z.object({
398
+ context: DisbursementPreviewContextZod,
399
+ balance: DisbursementPreviewBalanceZod,
400
+ tradeSummary: DisbursementPreviewTradeSummaryZod,
401
+ contingency: DisbursementPreviewContingencyZod,
402
+ amount: DisbursementPreviewAmountZod,
403
+ });
404
+ export type DisbursementPreviewZod = z.infer<typeof DisbursementPreviewZod>;
405
+
406
+ export const EligibleOfferingZod = z.object({
407
+ offeringId: offeringIdSchema,
408
+ offeringName: z.string(),
409
+ availableAmount: z.number(),
410
+ });
411
+ export type EligibleOfferingZod = z.infer<typeof EligibleOfferingZod>;
412
+
413
+ export const IPaginatedEligibleOffering = z.object({
414
+ items: z.array(EligibleOfferingZod),
415
+ meta: IPaginationMeta,
416
+ });
417
+ export type IPaginatedEligibleOffering = z.infer<
418
+ typeof IPaginatedEligibleOffering
419
+ >;
420
+
421
+ export const EligibleOfferingsFiltersZod = z.object({
422
+ search: z.string().optional(),
423
+ });
424
+ export type EligibleOfferingsFiltersZod = z.infer<
425
+ typeof EligibleOfferingsFiltersZod
426
+ >;
@@ -1,6 +1,6 @@
1
1
  import { extendZodWithOpenApi } from '@anatine/zod-openapi';
2
2
  import { z } from 'zod';
3
- import { AccountZod, IPaginationMeta } from './common.types';
3
+ import { AccountZod, EmailSchema, IPaginationMeta } from './common.types';
4
4
  import { IBaseEntity } from './entity.types';
5
5
  import { TypeID } from 'typeid-js';
6
6
  import { accountIdSchema } from './account.types';
@@ -43,7 +43,7 @@ export const PostEscrowAccount = z.object({
43
43
  accountNumber: z.string().optional().nullable(),
44
44
  routingNumber: z.string().min(9).max(9).optional().nullable(),
45
45
  agentName: z.string().min(2).max(50),
46
- agentEmail: z.string().email(),
46
+ agentEmail: EmailSchema,
47
47
  });
48
48
 
49
49
  export type PostEscrowAccount = z.infer<typeof PostEscrowAccount>;
@@ -65,7 +65,7 @@ export const PatchEscrowAccount = z.object({
65
65
  accountNumber: z.string().min(10).max(18).optional().nullable().optional(),
66
66
  routingNumber: z.string().min(9).max(9).optional().nullable().optional(),
67
67
  agentName: z.string().min(2).max(50).optional(),
68
- agentEmail: z.string().email().optional(),
68
+ agentEmail: EmailSchema.optional(),
69
69
  });
70
70
 
71
71
  export type PatchEscrowAccount = z.infer<typeof PatchEscrowAccount>;
@@ -17,7 +17,7 @@ import { KybZod } from './kyb.types';
17
17
  import { IIndividualZod, individualIdSchema } from './individuals.types';
18
18
  import { LegalEntityZod } from './legal-entity.types';
19
19
  import { IInvestorAccount } from './investor-account.types';
20
- import { TradeZod } from './trade.types';
20
+ import { tradeIdSchema, TradeZod } from './trade.types';
21
21
 
22
22
  extendZodWithOpenApi(z);
23
23
 
@@ -168,13 +168,13 @@ export type PostFileQueryParams = z.infer<typeof PostFileQueryParams>;
168
168
 
169
169
  /**
170
170
  * CLIENT portal specific schema for file uploads
171
- * Only allows INDIVIDUALS as target for file uploads
171
+ * Only allows INDIVIDUALS and TRADES as target for file uploads
172
172
  */
173
173
  export const ClientPostFileQueryParams = z.object({
174
174
  name: z.string().min(1).max(100).openapi({ example: 'file_name' }),
175
175
  category: z.string().max(50).openapi({ example: 'application' }),
176
176
  label: FileLabelsEnum.openapi({ example: FileLabels.OTHER }),
177
- targetId: individualIdSchema.openapi({
177
+ targetId: z.union([individualIdSchema, tradeIdSchema]).openapi({
178
178
  example: 'individual_01kcrsny60fb9rjc8bbqc3b80c',
179
179
  }),
180
180
  metadata: metadataSchema.nullable().optional(),
@@ -314,8 +314,23 @@ export const reviewFiles = z.object({
314
314
  });
315
315
  export type reviewFiles = z.infer<typeof reviewFiles>;
316
316
 
317
+ /**
318
+ * Zod preprocessor that trims strings and converts empty/whitespace-only strings to null
319
+ */
320
+ const trimAndNullifyString = z.preprocess((val) => {
321
+ if (typeof val === 'string') {
322
+ const trimmed = val.trim();
323
+ return trimmed === '' ? null : trimmed;
324
+ }
325
+ return val;
326
+ }, z.unknown());
327
+
317
328
  export const PatchFileMetadata = z.object({
318
- corrected: z.record(z.string(), z.unknown()),
329
+ corrected: z.record(z.string(), trimAndNullifyString),
330
+ expectedCorrected: z
331
+ .record(z.string(), trimAndNullifyString)
332
+ .nullable()
333
+ .optional(),
319
334
  });
320
335
  export type PatchFileMetadata = z.infer<typeof PatchFileMetadata>;
321
336
 
@@ -343,6 +358,7 @@ export const FileMetadataSchema = z.object({
343
358
  },
344
359
  ),
345
360
  corrected: z.record(z.any()).optional(),
361
+ expectedCorrected: z.record(z.any()).optional(),
346
362
  });
347
363
  export type FileMetadata = z.infer<typeof FileMetadataSchema>;
348
364