@devizovaburza/mdm-sdk 1.2.4 → 1.2.5-canary.061d760
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.d.mts +3143 -146
- package/dist/v1/index.d.ts +3143 -146
- package/dist/v1/index.mjs +517 -343
- package/package.json +8 -8
package/dist/v1/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { hc } from 'hono/client';
|
|
2
2
|
import { z, createRoute, OpenAPIHono } from '@hono/zod-openapi';
|
|
3
3
|
import { COUNTRY_CODES_2, LANGUAGE_CODES, CZ_NACE_CODES, CURRENCY_CODES, BANK_CODES, CZ_TRADE_LICENSE_CODES } from '@develit-io/general-codes';
|
|
4
|
-
import { bankAccountMetadataSchema } from '@develit-io/backend-sdk';
|
|
4
|
+
import { bankAccountMetadataSchema, structuredAddressSchema } from '@develit-io/backend-sdk';
|
|
5
5
|
import { jwt, signature, idempotency } from '@develit-io/backend-sdk/middlewares';
|
|
6
6
|
|
|
7
7
|
const createMdmClient = (baseUrl, options) => {
|
|
@@ -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),
|
|
@@ -455,6 +503,14 @@ const createAddressInputSchema = z.object({
|
|
|
455
503
|
countryCode: z.enum(COUNTRY_CODES, "St\xE1t je povinn\xFD"),
|
|
456
504
|
ruianCode: z.string().optional()
|
|
457
505
|
});
|
|
506
|
+
const ownerAddressInputSchema = createAddressInputSchema.extend({
|
|
507
|
+
addressType: z.enum(ADDRESS_TYPE).default("PERMANENT_ADDRESS"),
|
|
508
|
+
district: z.string().max(255).nullish(),
|
|
509
|
+
region: z.string().max(255).nullish()
|
|
510
|
+
});
|
|
511
|
+
const ownerIndividualInputSchema = individualInsertSchema.extend({
|
|
512
|
+
email: z.email("E-mail je povinn\xFD").optional()
|
|
513
|
+
});
|
|
458
514
|
const businessPartnerInputSchema$1 = z.object({
|
|
459
515
|
name: z.string().min(1, "N\xE1zev partnera je povinn\xFD"),
|
|
460
516
|
country: z.enum(COUNTRY_CODES, "St\xE1t partnera je povinn\xFD"),
|
|
@@ -523,7 +579,7 @@ const createAmlInputSchema = z.object({
|
|
|
523
579
|
personName: z.string(),
|
|
524
580
|
personRole: z.enum(PERSON_ROLE),
|
|
525
581
|
answer: z.enum(YES_NO_UNKNOWN),
|
|
526
|
-
|
|
582
|
+
countries: z.array(z.enum(COUNTRY_CODES))
|
|
527
583
|
})
|
|
528
584
|
).optional()
|
|
529
585
|
});
|
|
@@ -536,19 +592,11 @@ const productsInputSchema$1 = z.object({
|
|
|
536
592
|
}).optional(),
|
|
537
593
|
cbs: z.object({}).optional()
|
|
538
594
|
});
|
|
539
|
-
const ownerInputSchema =
|
|
595
|
+
const ownerInputSchema = ownerIndividualInputSchema.extend({
|
|
540
596
|
titleBefore: z.string().optional(),
|
|
541
|
-
name: z.string().min(1, "Jm\xE9no je povinn\xE9"),
|
|
542
|
-
surname: z.string().min(1, "P\u0159\xEDjmen\xED je povinn\xE9"),
|
|
543
597
|
titleAfter: z.string().optional(),
|
|
544
|
-
email: z.email("E-mail je povinn\xFD"),
|
|
545
|
-
birthDate: z.string().min(1, "Datum narozen\xED je povinn\xE9"),
|
|
546
|
-
birthPlace: z.string().optional(),
|
|
547
|
-
personalId: z.string().min(1, "Rodn\xE9 \u010D\xEDslo je povinn\xE9"),
|
|
548
|
-
gender: z.enum(GENDER, "Pohlav\xED je povinn\xE9"),
|
|
549
|
-
citizenship: z.enum(COUNTRY_CODES, "St\xE1tn\xED p\u0159\xEDslu\u0161nost je povinn\xE1"),
|
|
550
598
|
isPep: z.boolean({ error: "Politicky exponovan\xE1 osoba je povinn\xE1" }),
|
|
551
|
-
address:
|
|
599
|
+
address: ownerAddressInputSchema,
|
|
552
600
|
sharePercentage: z.number({ error: "Pod\xEDl na spole\u010Dnosti je povinn\xFD" }).min(1, "Pod\xEDl na spole\u010Dnosti je povinn\xFD").max(100),
|
|
553
601
|
shareEstablishedAt: z.string().min(1, "Datum vzniku pod\xEDlu je povinn\xFD")
|
|
554
602
|
});
|
|
@@ -567,7 +615,7 @@ const legalRepresentativeInputSchema = z.object({
|
|
|
567
615
|
phone: z.string().optional(),
|
|
568
616
|
pin: z.string().optional(),
|
|
569
617
|
identityDocuments: z.array(idDocumentInputSchema$1).optional(),
|
|
570
|
-
address:
|
|
618
|
+
address: ownerAddressInputSchema,
|
|
571
619
|
dateOfEstablishment: z.string().min(1, "Datum vzniku funkce je povinn\xFD")
|
|
572
620
|
});
|
|
573
621
|
const createPartyInputSchema = z.object({
|
|
@@ -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
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
)
|
|
637
|
-
|
|
638
|
-
|
|
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
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
-
|
|
646
|
-
|
|
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:
|
|
733
|
+
storageUrl: ba.statementUri,
|
|
658
734
|
entityId: partyData.id
|
|
659
735
|
});
|
|
660
736
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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
|
|
670
|
-
const
|
|
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:
|
|
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:
|
|
810
|
+
entityId: createdIdDoc.id
|
|
681
811
|
});
|
|
682
812
|
}
|
|
683
813
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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:
|
|
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 (
|
|
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
|
|
874
|
+
{ message: "Could not create legal representative party" },
|
|
703
875
|
500
|
|
704
876
|
);
|
|
705
877
|
}
|
|
706
|
-
const { error:
|
|
707
|
-
fromPartyId:
|
|
878
|
+
const { error: linkRepError } = await mdm.linkPartyToParty({
|
|
879
|
+
fromPartyId: repParty.id,
|
|
708
880
|
toPartyId: partyData.id,
|
|
709
|
-
relationshipType: "
|
|
881
|
+
relationshipType: "BOARD_MEMBER",
|
|
882
|
+
fromDate: new Date(dateOfEstablishment)
|
|
710
883
|
});
|
|
711
|
-
if (
|
|
884
|
+
if (linkRepError) {
|
|
712
885
|
return context.json(
|
|
713
|
-
{ message: "Could not link
|
|
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:
|
|
892
|
+
partyId: repParty.id,
|
|
720
893
|
idDocument: {
|
|
721
|
-
partyId:
|
|
894
|
+
partyId: repParty.id,
|
|
722
895
|
idDocType: doc.idDocType,
|
|
723
896
|
idDocNumber: doc.idDocNumber,
|
|
724
|
-
idDocHolderName: `${
|
|
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
|
-
{
|
|
907
|
+
{
|
|
908
|
+
message: "Could not create legal representative identity document"
|
|
909
|
+
},
|
|
735
910
|
500
|
|
736
911
|
);
|
|
737
912
|
}
|
|
@@ -749,121 +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,
|
|
762
|
-
organizationData: null,
|
|
763
|
-
bankAccounts: null,
|
|
764
|
-
recipients: null,
|
|
765
|
-
disponents: null,
|
|
766
|
-
addresses: [{ ...address, addressType: "PERMANENT_ADDRESS" }],
|
|
767
|
-
aml: null
|
|
768
|
-
});
|
|
769
|
-
if (ownerParty && !ownerError) {
|
|
770
|
-
await mdm.linkPartyToParty({
|
|
771
|
-
fromPartyId: ownerParty.id,
|
|
772
|
-
toPartyId: partyData.id,
|
|
773
|
-
relationshipType: "UBO",
|
|
774
|
-
sharePercentage,
|
|
775
|
-
fromDate: new Date(shareEstablishedAt)
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
if (legalRepresentatives?.length) {
|
|
781
|
-
for (const rep of legalRepresentatives) {
|
|
782
|
-
const {
|
|
783
|
-
address,
|
|
784
|
-
identityDocuments,
|
|
785
|
-
dateOfEstablishment,
|
|
786
|
-
...individual
|
|
787
|
-
} = rep;
|
|
788
|
-
const { data: repParty, error: repError } = await mdm.createParty({
|
|
789
|
-
party: {
|
|
790
|
-
partyType: "INDIVIDUAL",
|
|
791
|
-
language: partyInput.party.language,
|
|
792
|
-
customerStatus: "NEW"
|
|
793
|
-
},
|
|
794
|
-
personalData: individual,
|
|
795
|
-
organizationData: null,
|
|
796
|
-
bankAccounts: null,
|
|
797
|
-
recipients: null,
|
|
798
|
-
disponents: null,
|
|
799
|
-
addresses: [{ ...address, addressType: "PERMANENT_ADDRESS" }],
|
|
800
|
-
aml: null
|
|
801
|
-
});
|
|
802
|
-
if (!repParty || repError) {
|
|
803
|
-
return context.json(
|
|
804
|
-
{ message: "Could not create legal representative party" },
|
|
805
|
-
500
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
const { error: linkRepError } = await mdm.linkPartyToParty({
|
|
809
|
-
fromPartyId: repParty.id,
|
|
810
|
-
toPartyId: partyData.id,
|
|
811
|
-
relationshipType: "BOARD_MEMBER",
|
|
812
|
-
fromDate: new Date(dateOfEstablishment)
|
|
813
|
-
});
|
|
814
|
-
if (linkRepError) {
|
|
815
|
-
return context.json(
|
|
816
|
-
{ message: "Could not link legal representative to party" },
|
|
817
|
-
500
|
|
818
|
-
);
|
|
819
|
-
}
|
|
820
|
-
for (const doc of identityDocuments ?? []) {
|
|
821
|
-
const { data: createdIdDoc, error: idDocError } = await mdm.createIdDocument({
|
|
822
|
-
partyId: repParty.id,
|
|
823
|
-
idDocument: {
|
|
824
|
-
partyId: repParty.id,
|
|
825
|
-
idDocType: doc.idDocType,
|
|
826
|
-
idDocNumber: doc.idDocNumber,
|
|
827
|
-
idDocHolderName: `${individual.name} ${individual.surname}`,
|
|
828
|
-
issueDate: doc.issueDate,
|
|
829
|
-
expirationDate: doc.expirationDate,
|
|
830
|
-
issuer: doc.issuer,
|
|
831
|
-
frontImageUri: doc.frontImageUri,
|
|
832
|
-
backImageUri: doc.backImageUri
|
|
833
|
-
}
|
|
834
|
-
});
|
|
835
|
-
if (!createdIdDoc || idDocError) {
|
|
836
|
-
return context.json(
|
|
837
|
-
{
|
|
838
|
-
message: "Could not create legal representative identity document"
|
|
839
|
-
},
|
|
840
|
-
500
|
|
841
|
-
);
|
|
842
|
-
}
|
|
843
|
-
if (doc.frontImageUri) {
|
|
844
|
-
await docService.acknowledgeDocument({
|
|
845
|
-
storageUrl: doc.frontImageUri,
|
|
846
|
-
entityId: createdIdDoc.id
|
|
847
|
-
});
|
|
848
|
-
}
|
|
849
|
-
if (doc.backImageUri) {
|
|
850
|
-
await docService.acknowledgeDocument({
|
|
851
|
-
storageUrl: doc.backImageUri,
|
|
852
|
-
entityId: createdIdDoc.id
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
return context.json(
|
|
859
|
-
{
|
|
860
|
-
message: "party created",
|
|
861
|
-
party: partyData
|
|
862
|
-
},
|
|
863
|
-
200
|
|
864
|
-
);
|
|
865
927
|
}
|
|
866
|
-
|
|
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
|
+
});
|
|
867
951
|
|
|
868
952
|
const partySchema = z.object({
|
|
869
953
|
partyType: z.enum(PARTY_TYPE),
|
|
@@ -871,7 +955,10 @@ const partySchema = z.object({
|
|
|
871
955
|
id: z.uuid(),
|
|
872
956
|
internalId: z.string().optional(),
|
|
873
957
|
note: z.string().optional(),
|
|
874
|
-
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()
|
|
875
962
|
});
|
|
876
963
|
const idDocumentInputSchema = z.object({
|
|
877
964
|
id: z.uuid().optional(),
|
|
@@ -1060,7 +1147,7 @@ const updateAmlInputSchema = z.object({
|
|
|
1060
1147
|
personName: z.string(),
|
|
1061
1148
|
personRole: z.enum(PERSON_ROLE),
|
|
1062
1149
|
answer: z.enum(YES_NO_UNKNOWN),
|
|
1063
|
-
|
|
1150
|
+
countries: z.array(z.enum(COUNTRY_CODES))
|
|
1064
1151
|
})
|
|
1065
1152
|
).optional()
|
|
1066
1153
|
});
|
|
@@ -1079,7 +1166,7 @@ const ownerSyncSchema = z.object({
|
|
|
1079
1166
|
name: z.string(),
|
|
1080
1167
|
surname: z.string(),
|
|
1081
1168
|
titleAfter: z.string().optional(),
|
|
1082
|
-
email: z.email("E-mail je povinn\xFD"),
|
|
1169
|
+
email: z.email("E-mail je povinn\xFD").optional(),
|
|
1083
1170
|
birthDate: z.string(),
|
|
1084
1171
|
birthPlace: z.string().optional(),
|
|
1085
1172
|
personalId: z.string(),
|
|
@@ -1122,6 +1209,21 @@ const partyUpdateInputSchema = z.object({
|
|
|
1122
1209
|
aml: updateAmlInputSchema.optional(),
|
|
1123
1210
|
products: productsInputSchema.optional(),
|
|
1124
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"]
|
|
1125
1227
|
});
|
|
1126
1228
|
const updatePartyOuputSchema = z.object({
|
|
1127
1229
|
id: z.uuid(),
|
|
@@ -1169,48 +1271,75 @@ const updatePartyRoute = createRoute({
|
|
|
1169
1271
|
...errorResponse("Party", "Update")
|
|
1170
1272
|
}
|
|
1171
1273
|
});
|
|
1172
|
-
new OpenAPIHono().openapi(
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
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 }
|
|
1191
1301
|
});
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
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) {
|
|
1197
1313
|
await docService.acknowledgeDocument({
|
|
1198
|
-
storageUrl:
|
|
1314
|
+
storageUrl: ba.statementUri,
|
|
1199
1315
|
entityId: partyId
|
|
1200
1316
|
});
|
|
1201
1317
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
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) {
|
|
1204
1324
|
await docService.acknowledgeDocument({
|
|
1205
|
-
storageUrl:
|
|
1206
|
-
entityId:
|
|
1325
|
+
storageUrl: doc.frontImageUri,
|
|
1326
|
+
entityId: idDocEntityId
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
if (doc.backImageUri) {
|
|
1330
|
+
await docService.acknowledgeDocument({
|
|
1331
|
+
storageUrl: doc.backImageUri,
|
|
1332
|
+
entityId: idDocEntityId
|
|
1207
1333
|
});
|
|
1208
1334
|
}
|
|
1209
1335
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
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;
|
|
1214
1343
|
if (doc.frontImageUri) {
|
|
1215
1344
|
await docService.acknowledgeDocument({
|
|
1216
1345
|
storageUrl: doc.frontImageUri,
|
|
@@ -1225,54 +1354,45 @@ new OpenAPIHono().openapi(
|
|
|
1225
1354
|
}
|
|
1226
1355
|
}
|
|
1227
1356
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
for (const
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
entityId: idDocEntityId
|
|
1238
|
-
});
|
|
1239
|
-
}
|
|
1240
|
-
if (doc.backImageUri) {
|
|
1241
|
-
await docService.acknowledgeDocument({
|
|
1242
|
-
storageUrl: doc.backImageUri,
|
|
1243
|
-
entityId: idDocEntityId
|
|
1244
|
-
});
|
|
1245
|
-
}
|
|
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
|
+
});
|
|
1246
1366
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
if (doc.frontImageUri) {
|
|
1253
|
-
await docService.acknowledgeDocument({
|
|
1254
|
-
storageUrl: doc.frontImageUri,
|
|
1255
|
-
entityId: idDocEntityId
|
|
1256
|
-
});
|
|
1257
|
-
}
|
|
1258
|
-
if (doc.backImageUri) {
|
|
1259
|
-
await docService.acknowledgeDocument({
|
|
1260
|
-
storageUrl: doc.backImageUri,
|
|
1261
|
-
entityId: idDocEntityId
|
|
1262
|
-
});
|
|
1263
|
-
}
|
|
1367
|
+
if (doc.backImageUri) {
|
|
1368
|
+
await docService.acknowledgeDocument({
|
|
1369
|
+
storageUrl: doc.backImageUri,
|
|
1370
|
+
entityId: idDocEntityId
|
|
1371
|
+
});
|
|
1264
1372
|
}
|
|
1265
1373
|
}
|
|
1266
1374
|
}
|
|
1267
|
-
return context.json(
|
|
1268
|
-
{
|
|
1269
|
-
message: "Party updated successfully",
|
|
1270
|
-
party: updatedParty
|
|
1271
|
-
},
|
|
1272
|
-
200
|
|
1273
|
-
);
|
|
1274
1375
|
}
|
|
1275
|
-
|
|
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
|
+
});
|
|
1276
1396
|
|
|
1277
1397
|
const DOCUMENT_SIDE = ["front", "back"];
|
|
1278
1398
|
const paramsSchema$2 = z.object({
|
|
@@ -1376,64 +1496,91 @@ const createDocumentRoute = createRoute({
|
|
|
1376
1496
|
}
|
|
1377
1497
|
}
|
|
1378
1498
|
});
|
|
1379
|
-
new OpenAPIHono().openapi(
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
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 }
|
|
1403
1531
|
});
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
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
|
|
1407
1545
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
size: file.size
|
|
1419
|
-
}
|
|
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 }
|
|
1420
1556
|
});
|
|
1421
|
-
|
|
1422
|
-
return context.json({ message: "File upload failed" }, 500);
|
|
1423
|
-
}
|
|
1424
|
-
return context.json(
|
|
1425
|
-
{
|
|
1426
|
-
message: "IdDocument created successfully",
|
|
1427
|
-
idDocument: {
|
|
1428
|
-
...createdIdDocument,
|
|
1429
|
-
frontImageUri: documentSide === "front" ? uploadResult.storageUrl : createdIdDocument.frontImageUri,
|
|
1430
|
-
backImageUri: documentSide === "back" ? uploadResult.storageUrl : createdIdDocument.backImageUri
|
|
1431
|
-
}
|
|
1432
|
-
},
|
|
1433
|
-
200
|
|
1434
|
-
);
|
|
1557
|
+
return context.json({ message: "File upload failed" }, 500);
|
|
1435
1558
|
}
|
|
1436
|
-
|
|
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
|
+
});
|
|
1437
1584
|
|
|
1438
1585
|
const partyBaseOutputSchema = z.object({
|
|
1439
1586
|
customerStatus: z.enum(CUSTOMER_STATUS),
|
|
@@ -1521,7 +1668,7 @@ const bankAccountOutputSchema = z.object({
|
|
|
1521
1668
|
currency: z.enum(CURRENCY_CODES),
|
|
1522
1669
|
countryCode: z.enum(COUNTRY_CODES),
|
|
1523
1670
|
iban: z.string().nullable(),
|
|
1524
|
-
address:
|
|
1671
|
+
address: structuredAddressSchema.nullable(),
|
|
1525
1672
|
swiftBic: z.string().nullable(),
|
|
1526
1673
|
bicCor: z.string().nullable(),
|
|
1527
1674
|
routingNumber: z.string().nullable(),
|
|
@@ -1612,7 +1759,7 @@ const createAmlOutputSchema = z.object({
|
|
|
1612
1759
|
personName: z.string(),
|
|
1613
1760
|
personRole: z.enum(PERSON_ROLE),
|
|
1614
1761
|
answer: z.enum(YES_NO_UNKNOWN),
|
|
1615
|
-
|
|
1762
|
+
countries: z.array(z.enum(COUNTRY_CODES))
|
|
1616
1763
|
})
|
|
1617
1764
|
).nullable(),
|
|
1618
1765
|
createdAt: z.iso.datetime().nullable(),
|
|
@@ -1730,7 +1877,12 @@ const paginationAndSearchSchema = z.object({
|
|
|
1730
1877
|
column: z.string(),
|
|
1731
1878
|
direction: z.enum(["asc", "desc"]),
|
|
1732
1879
|
search: z.string().optional(),
|
|
1733
|
-
|
|
1880
|
+
// Hono validator collapses single-value query keys to strings, so accept
|
|
1881
|
+
// both `?ids=<uuid>` and `?ids=<uuid>&ids=<uuid>` and normalize to array.
|
|
1882
|
+
ids: z.preprocess(
|
|
1883
|
+
(v) => v == null ? void 0 : Array.isArray(v) ? v : [v],
|
|
1884
|
+
z.string().array().optional()
|
|
1885
|
+
),
|
|
1734
1886
|
includeDeleted: z.string().transform((v) => v === "true").optional()
|
|
1735
1887
|
});
|
|
1736
1888
|
const partiesOutputDataSchema = z.array(partyOutputSchema);
|
|
@@ -1948,35 +2100,57 @@ const updateIdDocumentRoute = createRoute({
|
|
|
1948
2100
|
}
|
|
1949
2101
|
}
|
|
1950
2102
|
});
|
|
1951
|
-
new OpenAPIHono().openapi(
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
id
|
|
1964
|
-
};
|
|
1965
|
-
const { data: updatedIdDocument, error: updateError } = await context.env.MASTER_DATA_MANAGEMENT_SERVICE.updateIdDocument({
|
|
1966
|
-
idDocument: fullIdDocument
|
|
2103
|
+
new OpenAPIHono().openapi(updateIdDocumentRoute, async (context) => {
|
|
2104
|
+
const { id } = context.req.valid("param");
|
|
2105
|
+
const patch = context.req.valid("json");
|
|
2106
|
+
const { data: existingDocument, error: fetchError } = await context.env.MASTER_DATA_MANAGEMENT_SERVICE.getIdDocument({ id });
|
|
2107
|
+
if (!existingDocument || fetchError) {
|
|
2108
|
+
logAuditEvent(context, {
|
|
2109
|
+
eventType: "id-document.updated",
|
|
2110
|
+
targetType: "id-document",
|
|
2111
|
+
targetId: id,
|
|
2112
|
+
outcome: "failure",
|
|
2113
|
+
severity: "medium",
|
|
2114
|
+
description: `Failed to update ID document ${id}: not found`
|
|
1967
2115
|
});
|
|
1968
|
-
|
|
1969
|
-
return context.json({ message: "Could not update ID document" }, 500);
|
|
1970
|
-
}
|
|
1971
|
-
return context.json(
|
|
1972
|
-
{
|
|
1973
|
-
message: "ID document updated successfully",
|
|
1974
|
-
idDocument: updatedIdDocument
|
|
1975
|
-
},
|
|
1976
|
-
200
|
|
1977
|
-
);
|
|
2116
|
+
return context.json({ message: "ID document not found" }, 404);
|
|
1978
2117
|
}
|
|
1979
|
-
|
|
2118
|
+
const fullIdDocument = {
|
|
2119
|
+
...existingDocument,
|
|
2120
|
+
...patch,
|
|
2121
|
+
id
|
|
2122
|
+
};
|
|
2123
|
+
const { data: updatedIdDocument, error: updateError } = await context.env.MASTER_DATA_MANAGEMENT_SERVICE.updateIdDocument({
|
|
2124
|
+
idDocument: fullIdDocument
|
|
2125
|
+
});
|
|
2126
|
+
if (!updatedIdDocument || updateError) {
|
|
2127
|
+
logAuditEvent(context, {
|
|
2128
|
+
eventType: "id-document.updated",
|
|
2129
|
+
targetType: "id-document",
|
|
2130
|
+
targetId: id,
|
|
2131
|
+
outcome: "failure",
|
|
2132
|
+
severity: "medium",
|
|
2133
|
+
description: `Failed to update ID document ${id}`
|
|
2134
|
+
});
|
|
2135
|
+
return context.json({ message: "Could not update ID document" }, 500);
|
|
2136
|
+
}
|
|
2137
|
+
logAuditEvent(context, {
|
|
2138
|
+
eventType: "id-document.updated",
|
|
2139
|
+
targetType: "id-document",
|
|
2140
|
+
targetId: id,
|
|
2141
|
+
outcome: "success",
|
|
2142
|
+
severity: "medium",
|
|
2143
|
+
description: `ID document ${id} updated`,
|
|
2144
|
+
details: { partyId: updatedIdDocument.partyId }
|
|
2145
|
+
});
|
|
2146
|
+
return context.json(
|
|
2147
|
+
{
|
|
2148
|
+
message: "ID document updated successfully",
|
|
2149
|
+
idDocument: updatedIdDocument
|
|
2150
|
+
},
|
|
2151
|
+
200
|
|
2152
|
+
);
|
|
2153
|
+
});
|
|
1980
2154
|
|
|
1981
2155
|
const messageResponseSchema = z.object({
|
|
1982
2156
|
message: z.string()
|