@devizovaburza/mdm-sdk 1.3.0 → 1.4.0

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/dist/v1/index.mjs CHANGED
@@ -194,6 +194,19 @@ const ID_DOC_STATUS = [
194
194
  "REVOKED"
195
195
  ];
196
196
  const CUSTOMER_STATUS = ["NEW", "ACTIVE", "CLOSED", "BLOCKED"];
197
+ const BLOCK_REASON = [
198
+ "MISSING_OWNERSHIP_STRUCTURE",
199
+ "REMOVED_FROM_REGISTRY",
200
+ "INSOLVENCY",
201
+ "EXECUTION",
202
+ "INACTIVE_FROM_START",
203
+ "INACTIVE_OVER_2_YEARS",
204
+ "COMPANY_SOLD",
205
+ "UNINTERESTING",
206
+ "OUTDATED_ID_DOCUMENTS",
207
+ "OTHER",
208
+ "BLOCKED_BY_CLIENT"
209
+ ];
197
210
  const PARTY_TYPE = [
198
211
  "INDIVIDUAL",
199
212
  "SELF_EMPLOYED",
@@ -310,6 +323,38 @@ const RISK_BUSINESS_AREA = [
310
323
  "VIRTUAL_ASSET_SERVICES"
311
324
  ];
312
325
 
326
+ function logAuditEvent(context, input) {
327
+ const identity = context.get("identity");
328
+ const sourceIp = context.req.header("CF-Connecting-IP");
329
+ const correlationId = input.correlationId ?? context.get("correlationId");
330
+ if (!correlationId) {
331
+ throw new Error(
332
+ "logAuditEvent: missing correlationId \u2014 auditLog() middleware must be mounted before handlers that call logAuditEvent"
333
+ );
334
+ }
335
+ context.executionCtx.waitUntil(
336
+ context.env.ACTIVITY_SERVICE.logAuditEvent({
337
+ logType: "event",
338
+ service: "gateway",
339
+ eventType: input.eventType,
340
+ correlationId,
341
+ actorUserId: input.actorUserId ?? identity?.user?.id,
342
+ actorEmail: input.actorEmail ?? identity?.user?.email ?? "not-authorized@system.internal",
343
+ actorOrganizationId: input.actorOrganizationId ?? identity?.user?.organizationId,
344
+ targetType: input.targetType,
345
+ targetId: input.targetId,
346
+ outcome: input.outcome,
347
+ description: input.description,
348
+ severity: input.severity ?? "low",
349
+ details: input.details,
350
+ userAgent: context.req.header("User-Agent"),
351
+ ...sourceIp && { sourceIp }
352
+ }).catch((error) => {
353
+ console.error(`Failed to log audit event "${input.eventType}":`, error);
354
+ })
355
+ );
356
+ }
357
+
313
358
  z.object({
314
359
  message: z.string(),
315
360
  data: z.null(),
@@ -350,7 +395,10 @@ const partySchema$1 = z.object({
350
395
  id: z.uuid(),
351
396
  internalId: z.string().optional(),
352
397
  note: z.string().optional(),
353
- countryCode: z.enum(COUNTRY_CODES).optional()
398
+ countryCode: z.enum(COUNTRY_CODES).optional(),
399
+ customerStatus: z.enum(CUSTOMER_STATUS).optional(),
400
+ blockReason: z.enum(BLOCK_REASON).nullable().optional(),
401
+ blockReasonNote: z.string().trim().min(1).nullable().optional()
354
402
  });
355
403
  const idDocumentInputSchema$1 = z.object({
356
404
  idDocType: z.enum(ID_DOC_TYPE),
@@ -583,6 +631,21 @@ const createPartyInputSchema = z.object({
583
631
  recipients: z.array(bankAccountInputSchema),
584
632
  products: productsInputSchema$1.optional(),
585
633
  traderIds: z.array(z.uuid()).optional()
634
+ }).refine(
635
+ (b) => b.party.customerStatus !== "BLOCKED" || !!b.party.blockReason,
636
+ {
637
+ message: "blockReason is required when customerStatus is BLOCKED",
638
+ path: ["party", "blockReason"]
639
+ }
640
+ ).refine(
641
+ (b) => b.party.customerStatus === "BLOCKED" || !b.party.blockReason && !b.party.blockReasonNote,
642
+ {
643
+ message: "blockReason and blockReasonNote must be null when customerStatus is not BLOCKED",
644
+ path: ["party", "blockReason"]
645
+ }
646
+ ).refine((b) => b.party.blockReason !== "OTHER" || !!b.party.blockReasonNote, {
647
+ message: "blockReasonNote is required when blockReason is OTHER",
648
+ path: ["party", "blockReasonNote"]
586
649
  });
587
650
  const partyCreateOutputSchema = z.object({
588
651
  customerStatus: z.enum(CUSTOMER_STATUS),
@@ -627,101 +690,211 @@ const createPartyRoute = createRoute({
627
690
  ...errorResponse("Party", "Creation")
628
691
  }
629
692
  });
630
- new OpenAPIHono().openapi(
631
- createPartyRoute,
632
- async (context) => {
633
- const { owners, legalRepresentatives, ...partyInput } = context.req.valid("json");
634
- const disponentsWithDocs = (partyInput.disponents ?? []).filter(
635
- (d) => d.partyType === "INDIVIDUAL" && !!d.data.identityDocuments?.length
636
- );
637
- const disponentsForBatch = (partyInput.disponents ?? []).filter(
638
- (d) => !(d.partyType === "INDIVIDUAL" && !!d.data.identityDocuments?.length)
693
+ new OpenAPIHono().openapi(createPartyRoute, async (context) => {
694
+ const { owners, legalRepresentatives, ...partyInput } = context.req.valid("json");
695
+ const disponentsWithDocs = (partyInput.disponents ?? []).filter(
696
+ (d) => d.partyType === "INDIVIDUAL" && !!d.data.identityDocuments?.length
697
+ );
698
+ const disponentsForBatch = (partyInput.disponents ?? []).filter(
699
+ (d) => !(d.partyType === "INDIVIDUAL" && !!d.data.identityDocuments?.length)
700
+ );
701
+ const { data: partyData, error } = await context.env.MASTER_DATA_MANAGEMENT_SERVICE.createParty({
702
+ ...partyInput,
703
+ disponents: disponentsForBatch,
704
+ party: { ...partyInput.party, isRoot: true }
705
+ });
706
+ if (!partyData || error) {
707
+ logAuditEvent(context, {
708
+ eventType: "party.created",
709
+ targetType: "party",
710
+ outcome: "failure",
711
+ severity: "medium",
712
+ description: "Failed to create party",
713
+ details: { partyType: partyInput.party.partyType }
714
+ });
715
+ return context.json(
716
+ {
717
+ message: "Could not create a party"
718
+ },
719
+ 500
639
720
  );
640
- const { data: partyData, error } = await context.env.MASTER_DATA_MANAGEMENT_SERVICE.createParty({
641
- ...partyInput,
642
- disponents: disponentsForBatch,
643
- party: { ...partyInput.party, isRoot: true }
721
+ }
722
+ const mdm = context.env.MASTER_DATA_MANAGEMENT_SERVICE;
723
+ const docService = context.env.DOCUMENT_SERVICE;
724
+ if (partyInput.organizationData?.registerUri) {
725
+ await docService.acknowledgeDocument({
726
+ storageUrl: partyInput.organizationData.registerUri,
727
+ entityId: partyData.id
644
728
  });
645
- if (!partyData || error) {
646
- return context.json(
647
- {
648
- message: "Could not create a party"
649
- },
650
- 500
651
- );
652
- }
653
- const mdm = context.env.MASTER_DATA_MANAGEMENT_SERVICE;
654
- const docService = context.env.DOCUMENT_SERVICE;
655
- if (partyInput.organizationData?.registerUri) {
729
+ }
730
+ for (const ba of partyInput.bankAccounts ?? []) {
731
+ if (ba.statementUri) {
656
732
  await docService.acknowledgeDocument({
657
- storageUrl: partyInput.organizationData.registerUri,
733
+ storageUrl: ba.statementUri,
658
734
  entityId: partyData.id
659
735
  });
660
736
  }
661
- for (const ba of partyInput.bankAccounts ?? []) {
662
- if (ba.statementUri) {
663
- await docService.acknowledgeDocument({
664
- storageUrl: ba.statementUri,
665
- entityId: partyData.id
666
- });
667
- }
737
+ }
738
+ for (const [i, doc] of (partyInput.personalData?.identityDocuments ?? []).entries()) {
739
+ const idDocId = partyData.personalIdDocIds?.[i] ?? partyData.id;
740
+ if (doc.frontImageUri) {
741
+ await docService.acknowledgeDocument({
742
+ storageUrl: doc.frontImageUri,
743
+ entityId: idDocId
744
+ });
745
+ }
746
+ if (doc.backImageUri) {
747
+ await docService.acknowledgeDocument({
748
+ storageUrl: doc.backImageUri,
749
+ entityId: idDocId
750
+ });
751
+ }
752
+ }
753
+ for (const disponent of disponentsWithDocs) {
754
+ const { identityDocuments, ...individualData } = disponent.data;
755
+ const { data: disponentParty, error: disponentError } = await mdm.createParty({
756
+ party: {
757
+ partyType: "INDIVIDUAL",
758
+ language: partyInput.party.language,
759
+ customerStatus: "NEW"
760
+ },
761
+ personalData: individualData,
762
+ organizationData: null,
763
+ bankAccounts: null,
764
+ recipients: null,
765
+ disponents: null,
766
+ addresses: [],
767
+ aml: null
768
+ });
769
+ if (!disponentParty || disponentError) {
770
+ return context.json({ message: "Could not create disponent party" }, 500);
771
+ }
772
+ const { error: linkDisponentError } = await mdm.linkPartyToParty({
773
+ fromPartyId: disponentParty.id,
774
+ toPartyId: partyData.id,
775
+ relationshipType: "AUTHORIZED_SIGNATORY"
776
+ });
777
+ if (linkDisponentError) {
778
+ return context.json({ message: "Could not link disponent to party" }, 500);
668
779
  }
669
- for (const [i, doc] of (partyInput.personalData?.identityDocuments ?? []).entries()) {
670
- const idDocId = partyData.personalIdDocIds?.[i] ?? partyData.id;
780
+ for (const doc of identityDocuments ?? []) {
781
+ const { data: createdIdDoc, error: idDocError } = await mdm.createIdDocument({
782
+ partyId: disponentParty.id,
783
+ idDocument: {
784
+ partyId: disponentParty.id,
785
+ idDocType: doc.idDocType,
786
+ idDocNumber: doc.idDocNumber,
787
+ idDocHolderName: `${individualData.name} ${individualData.surname}`,
788
+ issueDate: doc.issueDate,
789
+ expirationDate: doc.expirationDate,
790
+ issuer: doc.issuer,
791
+ frontImageUri: doc.frontImageUri,
792
+ backImageUri: doc.backImageUri
793
+ }
794
+ });
795
+ if (!createdIdDoc || idDocError) {
796
+ return context.json(
797
+ { message: "Could not create disponent identity document" },
798
+ 500
799
+ );
800
+ }
671
801
  if (doc.frontImageUri) {
672
802
  await docService.acknowledgeDocument({
673
803
  storageUrl: doc.frontImageUri,
674
- entityId: idDocId
804
+ entityId: createdIdDoc.id
675
805
  });
676
806
  }
677
807
  if (doc.backImageUri) {
678
808
  await docService.acknowledgeDocument({
679
809
  storageUrl: doc.backImageUri,
680
- entityId: idDocId
810
+ entityId: createdIdDoc.id
681
811
  });
682
812
  }
683
813
  }
684
- for (const disponent of disponentsWithDocs) {
685
- const { identityDocuments, ...individualData } = disponent.data;
686
- const { data: disponentParty, error: disponentError } = await mdm.createParty({
814
+ }
815
+ if (owners?.length) {
816
+ for (const owner of owners) {
817
+ const { address, sharePercentage, shareEstablishedAt, ...individual } = owner;
818
+ const { data: ownerParty, error: ownerError } = await mdm.createParty({
687
819
  party: {
688
820
  partyType: "INDIVIDUAL",
689
821
  language: partyInput.party.language,
690
822
  customerStatus: "NEW"
691
823
  },
692
- personalData: individualData,
824
+ personalData: { ...individual, email: individual.email ?? "" },
693
825
  organizationData: null,
694
826
  bankAccounts: null,
695
827
  recipients: null,
696
828
  disponents: null,
697
- addresses: [],
829
+ addresses: [
830
+ {
831
+ ...address,
832
+ district: address.district ?? "",
833
+ region: address.region ?? ""
834
+ }
835
+ ],
698
836
  aml: null
699
837
  });
700
- if (!disponentParty || disponentError) {
838
+ if (ownerParty && !ownerError) {
839
+ await mdm.linkPartyToParty({
840
+ fromPartyId: ownerParty.id,
841
+ toPartyId: partyData.id,
842
+ relationshipType: "UBO",
843
+ sharePercentage,
844
+ fromDate: new Date(shareEstablishedAt)
845
+ });
846
+ }
847
+ }
848
+ }
849
+ if (legalRepresentatives?.length) {
850
+ for (const rep of legalRepresentatives) {
851
+ const { address, identityDocuments, dateOfEstablishment, ...individual } = rep;
852
+ const { data: repParty, error: repError } = await mdm.createParty({
853
+ party: {
854
+ partyType: "INDIVIDUAL",
855
+ language: partyInput.party.language,
856
+ customerStatus: "NEW"
857
+ },
858
+ personalData: individual,
859
+ organizationData: null,
860
+ bankAccounts: null,
861
+ recipients: null,
862
+ disponents: null,
863
+ addresses: [
864
+ {
865
+ ...address,
866
+ district: address.district ?? "",
867
+ region: address.region ?? ""
868
+ }
869
+ ],
870
+ aml: null
871
+ });
872
+ if (!repParty || repError) {
701
873
  return context.json(
702
- { message: "Could not create disponent party" },
874
+ { message: "Could not create legal representative party" },
703
875
  500
704
876
  );
705
877
  }
706
- const { error: linkDisponentError } = await mdm.linkPartyToParty({
707
- fromPartyId: disponentParty.id,
878
+ const { error: linkRepError } = await mdm.linkPartyToParty({
879
+ fromPartyId: repParty.id,
708
880
  toPartyId: partyData.id,
709
- relationshipType: "AUTHORIZED_SIGNATORY"
881
+ relationshipType: "BOARD_MEMBER",
882
+ fromDate: new Date(dateOfEstablishment)
710
883
  });
711
- if (linkDisponentError) {
884
+ if (linkRepError) {
712
885
  return context.json(
713
- { message: "Could not link disponent to party" },
886
+ { message: "Could not link legal representative to party" },
714
887
  500
715
888
  );
716
889
  }
717
890
  for (const doc of identityDocuments ?? []) {
718
891
  const { data: createdIdDoc, error: idDocError } = await mdm.createIdDocument({
719
- partyId: disponentParty.id,
892
+ partyId: repParty.id,
720
893
  idDocument: {
721
- partyId: disponentParty.id,
894
+ partyId: repParty.id,
722
895
  idDocType: doc.idDocType,
723
896
  idDocNumber: doc.idDocNumber,
724
- idDocHolderName: `${individualData.name} ${individualData.surname}`,
897
+ idDocHolderName: `${individual.name} ${individual.surname}`,
725
898
  issueDate: doc.issueDate,
726
899
  expirationDate: doc.expirationDate,
727
900
  issuer: doc.issuer,
@@ -731,7 +904,9 @@ new OpenAPIHono().openapi(
731
904
  });
732
905
  if (!createdIdDoc || idDocError) {
733
906
  return context.json(
734
- { message: "Could not create disponent identity document" },
907
+ {
908
+ message: "Could not create legal representative identity document"
909
+ },
735
910
  500
736
911
  );
737
912
  }
@@ -749,133 +924,30 @@ new OpenAPIHono().openapi(
749
924
  }
750
925
  }
751
926
  }
752
- if (owners?.length) {
753
- for (const owner of owners) {
754
- const { address, sharePercentage, shareEstablishedAt, ...individual } = owner;
755
- const { data: ownerParty, error: ownerError } = await mdm.createParty({
756
- party: {
757
- partyType: "INDIVIDUAL",
758
- language: partyInput.party.language,
759
- customerStatus: "NEW"
760
- },
761
- personalData: { ...individual, email: individual.email ?? "" },
762
- organizationData: null,
763
- bankAccounts: null,
764
- recipients: null,
765
- disponents: null,
766
- addresses: [
767
- {
768
- ...address,
769
- district: address.district ?? "",
770
- region: address.region ?? ""
771
- }
772
- ],
773
- aml: null
774
- });
775
- if (ownerParty && !ownerError) {
776
- await mdm.linkPartyToParty({
777
- fromPartyId: ownerParty.id,
778
- toPartyId: partyData.id,
779
- relationshipType: "UBO",
780
- sharePercentage,
781
- fromDate: new Date(shareEstablishedAt)
782
- });
783
- }
784
- }
785
- }
786
- if (legalRepresentatives?.length) {
787
- for (const rep of legalRepresentatives) {
788
- const {
789
- address,
790
- identityDocuments,
791
- dateOfEstablishment,
792
- ...individual
793
- } = rep;
794
- const { data: repParty, error: repError } = await mdm.createParty({
795
- party: {
796
- partyType: "INDIVIDUAL",
797
- language: partyInput.party.language,
798
- customerStatus: "NEW"
799
- },
800
- personalData: individual,
801
- organizationData: null,
802
- bankAccounts: null,
803
- recipients: null,
804
- disponents: null,
805
- addresses: [
806
- {
807
- ...address,
808
- district: address.district ?? "",
809
- region: address.region ?? ""
810
- }
811
- ],
812
- aml: null
813
- });
814
- if (!repParty || repError) {
815
- return context.json(
816
- { message: "Could not create legal representative party" },
817
- 500
818
- );
819
- }
820
- const { error: linkRepError } = await mdm.linkPartyToParty({
821
- fromPartyId: repParty.id,
822
- toPartyId: partyData.id,
823
- relationshipType: "BOARD_MEMBER",
824
- fromDate: new Date(dateOfEstablishment)
825
- });
826
- if (linkRepError) {
827
- return context.json(
828
- { message: "Could not link legal representative to party" },
829
- 500
830
- );
831
- }
832
- for (const doc of identityDocuments ?? []) {
833
- const { data: createdIdDoc, error: idDocError } = await mdm.createIdDocument({
834
- partyId: repParty.id,
835
- idDocument: {
836
- partyId: repParty.id,
837
- idDocType: doc.idDocType,
838
- idDocNumber: doc.idDocNumber,
839
- idDocHolderName: `${individual.name} ${individual.surname}`,
840
- issueDate: doc.issueDate,
841
- expirationDate: doc.expirationDate,
842
- issuer: doc.issuer,
843
- frontImageUri: doc.frontImageUri,
844
- backImageUri: doc.backImageUri
845
- }
846
- });
847
- if (!createdIdDoc || idDocError) {
848
- return context.json(
849
- {
850
- message: "Could not create legal representative identity document"
851
- },
852
- 500
853
- );
854
- }
855
- if (doc.frontImageUri) {
856
- await docService.acknowledgeDocument({
857
- storageUrl: doc.frontImageUri,
858
- entityId: createdIdDoc.id
859
- });
860
- }
861
- if (doc.backImageUri) {
862
- await docService.acknowledgeDocument({
863
- storageUrl: doc.backImageUri,
864
- entityId: createdIdDoc.id
865
- });
866
- }
867
- }
868
- }
869
- }
870
- return context.json(
871
- {
872
- message: "party created",
873
- party: partyData
874
- },
875
- 200
876
- );
877
927
  }
878
- );
928
+ logAuditEvent(context, {
929
+ eventType: "party.created",
930
+ targetType: "party",
931
+ targetId: partyData.id,
932
+ outcome: "success",
933
+ severity: "medium",
934
+ description: `Party ${partyData.id} created`,
935
+ details: {
936
+ partyType: partyInput.party.partyType,
937
+ disponentCount: partyInput.disponents?.length ?? 0,
938
+ bankAccountCount: partyInput.bankAccounts?.length ?? 0,
939
+ ownerCount: owners?.length ?? 0,
940
+ legalRepresentativeCount: legalRepresentatives?.length ?? 0
941
+ }
942
+ });
943
+ return context.json(
944
+ {
945
+ message: "party created",
946
+ party: partyData
947
+ },
948
+ 200
949
+ );
950
+ });
879
951
 
880
952
  const partySchema = z.object({
881
953
  partyType: z.enum(PARTY_TYPE),
@@ -883,7 +955,10 @@ const partySchema = z.object({
883
955
  id: z.uuid(),
884
956
  internalId: z.string().optional(),
885
957
  note: z.string().optional(),
886
- countryCode: z.enum(COUNTRY_CODES).optional()
958
+ countryCode: z.enum(COUNTRY_CODES).optional(),
959
+ customerStatus: z.enum(CUSTOMER_STATUS).optional(),
960
+ blockReason: z.enum(BLOCK_REASON).nullable().optional(),
961
+ blockReasonNote: z.string().trim().min(1).nullable().optional()
887
962
  });
888
963
  const idDocumentInputSchema = z.object({
889
964
  id: z.uuid().optional(),
@@ -1134,6 +1209,21 @@ const partyUpdateInputSchema = z.object({
1134
1209
  aml: updateAmlInputSchema.optional(),
1135
1210
  products: productsInputSchema.optional(),
1136
1211
  traderIds: z.array(z.uuid()).optional()
1212
+ }).refine(
1213
+ (b) => b.party.customerStatus !== "BLOCKED" || !!b.party.blockReason,
1214
+ {
1215
+ message: "blockReason is required when customerStatus is BLOCKED",
1216
+ path: ["party", "blockReason"]
1217
+ }
1218
+ ).refine(
1219
+ (b) => b.party.customerStatus === "BLOCKED" || b.party.customerStatus === void 0 || !b.party.blockReason && !b.party.blockReasonNote,
1220
+ {
1221
+ message: "blockReason and blockReasonNote must be null when customerStatus is not BLOCKED",
1222
+ path: ["party", "blockReason"]
1223
+ }
1224
+ ).refine((b) => b.party.blockReason !== "OTHER" || !!b.party.blockReasonNote, {
1225
+ message: "blockReasonNote is required when blockReason is OTHER",
1226
+ path: ["party", "blockReasonNote"]
1137
1227
  });
1138
1228
  const updatePartyOuputSchema = z.object({
1139
1229
  id: z.uuid(),
@@ -1181,48 +1271,75 @@ const updatePartyRoute = createRoute({
1181
1271
  ...errorResponse("Party", "Update")
1182
1272
  }
1183
1273
  });
1184
- new OpenAPIHono().openapi(
1185
- updatePartyRoute,
1186
- async (context) => {
1187
- const party = context.req.valid("json");
1188
- const { partyId } = context.req.valid("param");
1189
- const { data: updatedParty, error: partyError } = await context.env.MASTER_DATA_MANAGEMENT_SERVICE.updateParty({
1190
- partyId,
1191
- party: party.party,
1192
- personalData: party.personalData ?? void 0,
1193
- organizationData: party.organizationData ?? void 0,
1194
- bankAccounts: party.bankAccounts ?? void 0,
1195
- disponents: party.disponents ?? void 0,
1196
- addresses: party.addresses ?? void 0,
1197
- recipients: party.recipients ?? void 0,
1198
- aml: party.aml ?? void 0,
1199
- products: party.products ?? void 0,
1200
- traderIds: party.traderIds ?? void 0,
1201
- legalRepresentatives: party.legalRepresentatives ?? void 0,
1202
- owners: party.owners ?? void 0
1274
+ new OpenAPIHono().openapi(updatePartyRoute, async (context) => {
1275
+ const party = context.req.valid("json");
1276
+ const { partyId } = context.req.valid("param");
1277
+ const { data: updatedParty, error: partyError } = await context.env.MASTER_DATA_MANAGEMENT_SERVICE.updateParty({
1278
+ partyId,
1279
+ party: party.party,
1280
+ personalData: party.personalData ?? void 0,
1281
+ organizationData: party.organizationData ?? void 0,
1282
+ bankAccounts: party.bankAccounts ?? void 0,
1283
+ disponents: party.disponents ?? void 0,
1284
+ addresses: party.addresses ?? void 0,
1285
+ recipients: party.recipients ?? void 0,
1286
+ aml: party.aml ?? void 0,
1287
+ products: party.products ?? void 0,
1288
+ traderIds: party.traderIds ?? void 0,
1289
+ legalRepresentatives: party.legalRepresentatives ?? void 0,
1290
+ owners: party.owners ?? void 0
1291
+ });
1292
+ if (!updatedParty || partyError) {
1293
+ logAuditEvent(context, {
1294
+ eventType: "party.updated",
1295
+ targetType: "party",
1296
+ targetId: partyId,
1297
+ outcome: "failure",
1298
+ severity: "medium",
1299
+ description: `Failed to update party ${partyId}`,
1300
+ details: { partyType: party.party.partyType }
1203
1301
  });
1204
- if (!updatedParty || partyError) {
1205
- return context.json({ message: "Could not update a Party" }, 404);
1206
- }
1207
- const docService = context.env.DOCUMENT_SERVICE;
1208
- if (party.organizationData?.registerUri) {
1302
+ return context.json({ message: "Could not update a Party" }, 404);
1303
+ }
1304
+ const docService = context.env.DOCUMENT_SERVICE;
1305
+ if (party.organizationData?.registerUri) {
1306
+ await docService.acknowledgeDocument({
1307
+ storageUrl: party.organizationData.registerUri,
1308
+ entityId: partyId
1309
+ });
1310
+ }
1311
+ for (const ba of party.bankAccounts ?? []) {
1312
+ if (ba.statementUri) {
1209
1313
  await docService.acknowledgeDocument({
1210
- storageUrl: party.organizationData.registerUri,
1314
+ storageUrl: ba.statementUri,
1211
1315
  entityId: partyId
1212
1316
  });
1213
1317
  }
1214
- for (const ba of party.bankAccounts ?? []) {
1215
- if (ba.statementUri) {
1318
+ }
1319
+ if (party.personalData?.identityDocuments?.length) {
1320
+ let newDocIdx = 0;
1321
+ for (const doc of party.personalData.identityDocuments) {
1322
+ const idDocEntityId = doc.id ? doc.id : updatedParty.newPersonalIdDocIds?.[newDocIdx++] ?? partyId;
1323
+ if (doc.frontImageUri) {
1216
1324
  await docService.acknowledgeDocument({
1217
- storageUrl: ba.statementUri,
1218
- entityId: partyId
1325
+ storageUrl: doc.frontImageUri,
1326
+ entityId: idDocEntityId
1327
+ });
1328
+ }
1329
+ if (doc.backImageUri) {
1330
+ await docService.acknowledgeDocument({
1331
+ storageUrl: doc.backImageUri,
1332
+ entityId: idDocEntityId
1219
1333
  });
1220
1334
  }
1221
1335
  }
1222
- if (party.personalData?.identityDocuments?.length) {
1223
- let newDocIdx = 0;
1224
- for (const doc of party.personalData.identityDocuments) {
1225
- const idDocEntityId = doc.id ? doc.id : updatedParty.newPersonalIdDocIds?.[newDocIdx++] ?? partyId;
1336
+ }
1337
+ if (party.legalRepresentatives?.length) {
1338
+ let newBmDocIdx = 0;
1339
+ for (const rep of party.legalRepresentatives) {
1340
+ if (rep.id) continue;
1341
+ for (const doc of rep.identityDocuments ?? []) {
1342
+ const idDocEntityId = updatedParty.newBoardMemberIdDocIds?.[newBmDocIdx++] ?? partyId;
1226
1343
  if (doc.frontImageUri) {
1227
1344
  await docService.acknowledgeDocument({
1228
1345
  storageUrl: doc.frontImageUri,
@@ -1237,54 +1354,45 @@ new OpenAPIHono().openapi(
1237
1354
  }
1238
1355
  }
1239
1356
  }
1240
- if (party.legalRepresentatives?.length) {
1241
- let newBmDocIdx = 0;
1242
- for (const rep of party.legalRepresentatives) {
1243
- if (rep.id) continue;
1244
- for (const doc of rep.identityDocuments ?? []) {
1245
- const idDocEntityId = updatedParty.newBoardMemberIdDocIds?.[newBmDocIdx++] ?? partyId;
1246
- if (doc.frontImageUri) {
1247
- await docService.acknowledgeDocument({
1248
- storageUrl: doc.frontImageUri,
1249
- entityId: idDocEntityId
1250
- });
1251
- }
1252
- if (doc.backImageUri) {
1253
- await docService.acknowledgeDocument({
1254
- storageUrl: doc.backImageUri,
1255
- entityId: idDocEntityId
1256
- });
1257
- }
1357
+ for (const rep of party.legalRepresentatives) {
1358
+ if (!rep.id) continue;
1359
+ for (const doc of rep.identityDocuments ?? []) {
1360
+ const idDocEntityId = doc.id ? doc.id : updatedParty.newBoardMemberIdDocIds?.[newBmDocIdx++] ?? partyId;
1361
+ if (doc.frontImageUri) {
1362
+ await docService.acknowledgeDocument({
1363
+ storageUrl: doc.frontImageUri,
1364
+ entityId: idDocEntityId
1365
+ });
1258
1366
  }
1259
- }
1260
- for (const rep of party.legalRepresentatives) {
1261
- if (!rep.id) continue;
1262
- for (const doc of rep.identityDocuments ?? []) {
1263
- const idDocEntityId = doc.id ? doc.id : updatedParty.newBoardMemberIdDocIds?.[newBmDocIdx++] ?? partyId;
1264
- if (doc.frontImageUri) {
1265
- await docService.acknowledgeDocument({
1266
- storageUrl: doc.frontImageUri,
1267
- entityId: idDocEntityId
1268
- });
1269
- }
1270
- if (doc.backImageUri) {
1271
- await docService.acknowledgeDocument({
1272
- storageUrl: doc.backImageUri,
1273
- entityId: idDocEntityId
1274
- });
1275
- }
1367
+ if (doc.backImageUri) {
1368
+ await docService.acknowledgeDocument({
1369
+ storageUrl: doc.backImageUri,
1370
+ entityId: idDocEntityId
1371
+ });
1276
1372
  }
1277
1373
  }
1278
1374
  }
1279
- return context.json(
1280
- {
1281
- message: "Party updated successfully",
1282
- party: updatedParty
1283
- },
1284
- 200
1285
- );
1286
1375
  }
1287
- );
1376
+ logAuditEvent(context, {
1377
+ eventType: "party.updated",
1378
+ targetType: "party",
1379
+ targetId: partyId,
1380
+ outcome: "success",
1381
+ severity: "medium",
1382
+ description: `Party ${partyId} updated`,
1383
+ details: {
1384
+ partyType: party.party.partyType,
1385
+ customerStatus: updatedParty.customerStatus
1386
+ }
1387
+ });
1388
+ return context.json(
1389
+ {
1390
+ message: "Party updated successfully",
1391
+ party: updatedParty
1392
+ },
1393
+ 200
1394
+ );
1395
+ });
1288
1396
 
1289
1397
  const DOCUMENT_SIDE = ["front", "back"];
1290
1398
  const paramsSchema$2 = z.object({
@@ -1388,64 +1496,91 @@ const createDocumentRoute = createRoute({
1388
1496
  }
1389
1497
  }
1390
1498
  });
1391
- new OpenAPIHono().openapi(
1392
- createDocumentRoute,
1393
- async (context) => {
1394
- const rawBody = await context.req.parseBody();
1395
- const { partyId } = context.req.valid("param");
1396
- const idDocumentRaw = rawBody.idDocument;
1397
- const documentSide = rawBody.documentSide;
1398
- const file = rawBody.file;
1399
- if (typeof idDocumentRaw !== "string" || !DOCUMENT_SIDE.includes(documentSide) || !(file instanceof File)) {
1400
- return context.json({ message: "Invalid multipart payload" }, 400);
1401
- }
1402
- const idDocumentJson = JSON.parse(JSON.parse(idDocumentRaw));
1403
- const idDocumentResult = idDocumentCreateInputSchema.safeParse(idDocumentJson);
1404
- if (!idDocumentResult.success) {
1405
- console.error(idDocumentResult.error);
1406
- return context.json({ message: "Could not parse IdDocument" }, 400);
1407
- }
1408
- const {
1409
- data: createdIdDocument,
1410
- error: createError,
1411
- message
1412
- } = await context.env.MASTER_DATA_MANAGEMENT_SERVICE.createIdDocument({
1413
- idDocument: idDocumentResult.data,
1414
- partyId
1499
+ new OpenAPIHono().openapi(createDocumentRoute, async (context) => {
1500
+ const rawBody = await context.req.parseBody();
1501
+ const { partyId } = context.req.valid("param");
1502
+ const idDocumentRaw = rawBody.idDocument;
1503
+ const documentSide = rawBody.documentSide;
1504
+ const file = rawBody.file;
1505
+ if (typeof idDocumentRaw !== "string" || !DOCUMENT_SIDE.includes(documentSide) || !(file instanceof File)) {
1506
+ return context.json({ message: "Invalid multipart payload" }, 400);
1507
+ }
1508
+ const idDocumentJson = JSON.parse(JSON.parse(idDocumentRaw));
1509
+ const idDocumentResult = idDocumentCreateInputSchema.safeParse(idDocumentJson);
1510
+ if (!idDocumentResult.success) {
1511
+ console.error(idDocumentResult.error);
1512
+ return context.json({ message: "Could not parse IdDocument" }, 400);
1513
+ }
1514
+ const {
1515
+ data: createdIdDocument,
1516
+ error: createError,
1517
+ message
1518
+ } = await context.env.MASTER_DATA_MANAGEMENT_SERVICE.createIdDocument({
1519
+ idDocument: idDocumentResult.data,
1520
+ partyId
1521
+ });
1522
+ if (!createdIdDocument || createError) {
1523
+ console.error(message);
1524
+ logAuditEvent(context, {
1525
+ eventType: "id-document.created",
1526
+ targetType: "id-document",
1527
+ outcome: "failure",
1528
+ severity: "medium",
1529
+ description: `Failed to create ID document for party ${partyId}`,
1530
+ details: { partyId, idDocType: idDocumentResult.data.idDocType }
1415
1531
  });
1416
- if (!createdIdDocument || createError) {
1417
- console.error(message);
1418
- return context.json({ message: "Could not create IdDocument" }, 500);
1532
+ return context.json({ message: "Could not create IdDocument" }, 500);
1533
+ }
1534
+ const bytes = new Uint8Array(await file.arrayBuffer());
1535
+ const { data: uploadResult, error: uploadError } = await context.env.DOCUMENT_SERVICE.uploadDocument({
1536
+ entityType: "client",
1537
+ entityId: createdIdDocument.id,
1538
+ type: "kyc",
1539
+ metadata: {},
1540
+ file: {
1541
+ bytes,
1542
+ type: file.type,
1543
+ name: file.name,
1544
+ size: file.size
1419
1545
  }
1420
- const bytes = new Uint8Array(await file.arrayBuffer());
1421
- const { data: uploadResult, error: uploadError } = await context.env.DOCUMENT_SERVICE.uploadDocument({
1422
- entityType: "client",
1423
- entityId: createdIdDocument.id,
1424
- type: "kyc",
1425
- metadata: {},
1426
- file: {
1427
- bytes,
1428
- type: file.type,
1429
- name: file.name,
1430
- size: file.size
1431
- }
1546
+ });
1547
+ if (!uploadResult || uploadError) {
1548
+ logAuditEvent(context, {
1549
+ eventType: "id-document.created",
1550
+ targetType: "id-document",
1551
+ targetId: createdIdDocument.id,
1552
+ outcome: "error",
1553
+ severity: "medium",
1554
+ description: `ID document ${createdIdDocument.id} created but file upload failed`,
1555
+ details: { partyId, documentSide }
1432
1556
  });
1433
- if (!uploadResult || uploadError) {
1434
- return context.json({ message: "File upload failed" }, 500);
1435
- }
1436
- return context.json(
1437
- {
1438
- message: "IdDocument created successfully",
1439
- idDocument: {
1440
- ...createdIdDocument,
1441
- frontImageUri: documentSide === "front" ? uploadResult.storageUrl : createdIdDocument.frontImageUri,
1442
- backImageUri: documentSide === "back" ? uploadResult.storageUrl : createdIdDocument.backImageUri
1443
- }
1444
- },
1445
- 200
1446
- );
1557
+ return context.json({ message: "File upload failed" }, 500);
1447
1558
  }
1448
- );
1559
+ logAuditEvent(context, {
1560
+ eventType: "id-document.created",
1561
+ targetType: "id-document",
1562
+ targetId: createdIdDocument.id,
1563
+ outcome: "success",
1564
+ severity: "medium",
1565
+ description: `ID document ${createdIdDocument.id} created for party ${partyId}`,
1566
+ details: {
1567
+ partyId,
1568
+ idDocType: createdIdDocument.idDocType,
1569
+ documentSide
1570
+ }
1571
+ });
1572
+ return context.json(
1573
+ {
1574
+ message: "IdDocument created successfully",
1575
+ idDocument: {
1576
+ ...createdIdDocument,
1577
+ frontImageUri: documentSide === "front" ? uploadResult.storageUrl : createdIdDocument.frontImageUri,
1578
+ backImageUri: documentSide === "back" ? uploadResult.storageUrl : createdIdDocument.backImageUri
1579
+ }
1580
+ },
1581
+ 200
1582
+ );
1583
+ });
1449
1584
 
1450
1585
  const partyBaseOutputSchema = z.object({
1451
1586
  customerStatus: z.enum(CUSTOMER_STATUS),
@@ -1960,35 +2095,57 @@ const updateIdDocumentRoute = createRoute({
1960
2095
  }
1961
2096
  }
1962
2097
  });
1963
- new OpenAPIHono().openapi(
1964
- updateIdDocumentRoute,
1965
- async (context) => {
1966
- const { id } = context.req.valid("param");
1967
- const patch = context.req.valid("json");
1968
- const { data: existingDocument, error: fetchError } = await context.env.MASTER_DATA_MANAGEMENT_SERVICE.getIdDocument({ id });
1969
- if (!existingDocument || fetchError) {
1970
- return context.json({ message: "ID document not found" }, 404);
1971
- }
1972
- const fullIdDocument = {
1973
- ...existingDocument,
1974
- ...patch,
1975
- id
1976
- };
1977
- const { data: updatedIdDocument, error: updateError } = await context.env.MASTER_DATA_MANAGEMENT_SERVICE.updateIdDocument({
1978
- idDocument: fullIdDocument
2098
+ new OpenAPIHono().openapi(updateIdDocumentRoute, async (context) => {
2099
+ const { id } = context.req.valid("param");
2100
+ const patch = context.req.valid("json");
2101
+ const { data: existingDocument, error: fetchError } = await context.env.MASTER_DATA_MANAGEMENT_SERVICE.getIdDocument({ id });
2102
+ if (!existingDocument || fetchError) {
2103
+ logAuditEvent(context, {
2104
+ eventType: "id-document.updated",
2105
+ targetType: "id-document",
2106
+ targetId: id,
2107
+ outcome: "failure",
2108
+ severity: "medium",
2109
+ description: `Failed to update ID document ${id}: not found`
1979
2110
  });
1980
- if (!updatedIdDocument || updateError) {
1981
- return context.json({ message: "Could not update ID document" }, 500);
1982
- }
1983
- return context.json(
1984
- {
1985
- message: "ID document updated successfully",
1986
- idDocument: updatedIdDocument
1987
- },
1988
- 200
1989
- );
2111
+ return context.json({ message: "ID document not found" }, 404);
1990
2112
  }
1991
- );
2113
+ const fullIdDocument = {
2114
+ ...existingDocument,
2115
+ ...patch,
2116
+ id
2117
+ };
2118
+ const { data: updatedIdDocument, error: updateError } = await context.env.MASTER_DATA_MANAGEMENT_SERVICE.updateIdDocument({
2119
+ idDocument: fullIdDocument
2120
+ });
2121
+ if (!updatedIdDocument || updateError) {
2122
+ logAuditEvent(context, {
2123
+ eventType: "id-document.updated",
2124
+ targetType: "id-document",
2125
+ targetId: id,
2126
+ outcome: "failure",
2127
+ severity: "medium",
2128
+ description: `Failed to update ID document ${id}`
2129
+ });
2130
+ return context.json({ message: "Could not update ID document" }, 500);
2131
+ }
2132
+ logAuditEvent(context, {
2133
+ eventType: "id-document.updated",
2134
+ targetType: "id-document",
2135
+ targetId: id,
2136
+ outcome: "success",
2137
+ severity: "medium",
2138
+ description: `ID document ${id} updated`,
2139
+ details: { partyId: updatedIdDocument.partyId }
2140
+ });
2141
+ return context.json(
2142
+ {
2143
+ message: "ID document updated successfully",
2144
+ idDocument: updatedIdDocument
2145
+ },
2146
+ 200
2147
+ );
2148
+ });
1992
2149
 
1993
2150
  const messageResponseSchema = z.object({
1994
2151
  message: z.string()