@voyantjs/customer-portal 0.6.8 → 0.7.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/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/routes-public.d.ts +20 -50
- package/dist/routes-public.d.ts.map +1 -1
- package/dist/routes-public.js +3 -3
- package/dist/service-public.d.ts +4 -3
- package/dist/service-public.d.ts.map +1 -1
- package/dist/service-public.js +119 -172
- package/dist/validation-public.d.ts +85 -49
- package/dist/validation-public.d.ts.map +1 -1
- package/dist/validation-public.js +17 -33
- package/package.json +10 -10
package/dist/service-public.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bookingDocuments, bookingFulfillments,
|
|
1
|
+
import { bookingDocuments, bookingFulfillments, bookingItems, bookingItemTravelers, bookingSessionStates, bookingStaffAssignments, bookings, bookingTravelers, } from "@voyantjs/bookings/schema";
|
|
2
2
|
import { crmService, people } from "@voyantjs/crm";
|
|
3
3
|
import { authUser, travelDocumentSchema, userProfilesTable, } from "@voyantjs/db/schema/iam";
|
|
4
4
|
import { invoiceRenditions, invoices, payments } from "@voyantjs/finance/schema";
|
|
@@ -109,20 +109,6 @@ function getRecord(value) {
|
|
|
109
109
|
}
|
|
110
110
|
return value;
|
|
111
111
|
}
|
|
112
|
-
function formatCustomerAddress(address) {
|
|
113
|
-
if (address.fullText) {
|
|
114
|
-
return address.fullText;
|
|
115
|
-
}
|
|
116
|
-
const parts = [
|
|
117
|
-
address.line1,
|
|
118
|
-
address.line2,
|
|
119
|
-
address.city,
|
|
120
|
-
address.region,
|
|
121
|
-
address.postalCode,
|
|
122
|
-
address.country,
|
|
123
|
-
].filter((value) => Boolean(value));
|
|
124
|
-
return parts.length > 0 ? parts.join(", ") : null;
|
|
125
|
-
}
|
|
126
112
|
function toCustomerAddress(address) {
|
|
127
113
|
return {
|
|
128
114
|
id: address.id,
|
|
@@ -358,7 +344,7 @@ async function listLegalDocumentsForBooking(db, bookingId, options = {}) {
|
|
|
358
344
|
{
|
|
359
345
|
id: attachment.id,
|
|
360
346
|
source: "legal",
|
|
361
|
-
|
|
347
|
+
travelerId: null,
|
|
362
348
|
type: "contract",
|
|
363
349
|
fileName: attachment.name,
|
|
364
350
|
fileUrl: downloadUrl,
|
|
@@ -477,7 +463,7 @@ async function getFinanceDataForBooking(db, bookingId, options = {}) {
|
|
|
477
463
|
{
|
|
478
464
|
id: document.invoiceId,
|
|
479
465
|
source: "finance",
|
|
480
|
-
|
|
466
|
+
travelerId: null,
|
|
481
467
|
type: document.invoiceType,
|
|
482
468
|
fileName: resolveFinanceDocumentFileName(document.invoiceNumber, document.invoiceType, document.format),
|
|
483
469
|
fileUrl: document.downloadUrl,
|
|
@@ -600,9 +586,6 @@ async function listCustomerRecordCandidatesByEmail(db, email) {
|
|
|
600
586
|
birthday: row.birthday ?? null,
|
|
601
587
|
email: normalizedEmail,
|
|
602
588
|
phone: null,
|
|
603
|
-
address: null,
|
|
604
|
-
city: null,
|
|
605
|
-
country: null,
|
|
606
589
|
billingAddress: null,
|
|
607
590
|
relation: row.relation ?? null,
|
|
608
591
|
status: row.status,
|
|
@@ -644,9 +627,6 @@ async function listCustomerRecordCandidatesByPhone(db, phone) {
|
|
|
644
627
|
birthday: row.birthday ?? null,
|
|
645
628
|
email: null,
|
|
646
629
|
phone: normalizedPhone,
|
|
647
|
-
address: null,
|
|
648
|
-
city: null,
|
|
649
|
-
country: null,
|
|
650
630
|
billingAddress: null,
|
|
651
631
|
relation: row.relation ?? null,
|
|
652
632
|
status: row.status,
|
|
@@ -676,32 +656,23 @@ async function getCustomerRecord(db, userId) {
|
|
|
676
656
|
birthday: person.birthday ?? null,
|
|
677
657
|
email: person.email ?? null,
|
|
678
658
|
phone: person.phone ?? null,
|
|
679
|
-
address: person.address ?? null,
|
|
680
|
-
city: person.city ?? null,
|
|
681
|
-
country: person.country ?? null,
|
|
682
659
|
billingAddress: billingAddress ? toCustomerAddress(billingAddress) : null,
|
|
683
660
|
relation: person.relation ?? null,
|
|
684
661
|
status: person.status,
|
|
685
662
|
};
|
|
686
663
|
}
|
|
687
|
-
async function upsertCustomerBillingAddress(db, personId, input
|
|
664
|
+
async function upsertCustomerBillingAddress(db, personId, input) {
|
|
688
665
|
const existingAddresses = await identityService.listAddressesForEntity(db, "person", personId);
|
|
689
666
|
const existingAddress = selectPreferredAddress(existingAddresses);
|
|
690
|
-
const fallbackAddress = normalizeNullableString(fallback?.address);
|
|
691
|
-
const fallbackCity = normalizeNullableString(fallback?.city);
|
|
692
|
-
const fallbackCountry = normalizeNullableString(fallback?.country);
|
|
693
667
|
const merged = {
|
|
694
668
|
label: input.label ?? existingAddress?.label ?? "billing",
|
|
695
|
-
fullText: normalizeNullableString(input.fullText) ??
|
|
696
|
-
(input.line1 === undefined ? fallbackAddress : null) ??
|
|
697
|
-
existingAddress?.fullText ??
|
|
698
|
-
null,
|
|
669
|
+
fullText: normalizeNullableString(input.fullText) ?? existingAddress?.fullText ?? null,
|
|
699
670
|
line1: normalizeNullableString(input.line1) ?? existingAddress?.line1 ?? null,
|
|
700
671
|
line2: normalizeNullableString(input.line2) ?? existingAddress?.line2 ?? null,
|
|
701
|
-
city: normalizeNullableString(input.city) ??
|
|
672
|
+
city: normalizeNullableString(input.city) ?? existingAddress?.city ?? null,
|
|
702
673
|
region: normalizeNullableString(input.region) ?? existingAddress?.region ?? null,
|
|
703
674
|
postalCode: normalizeNullableString(input.postalCode) ?? existingAddress?.postalCode ?? null,
|
|
704
|
-
country: normalizeNullableString(input.country) ??
|
|
675
|
+
country: normalizeNullableString(input.country) ?? existingAddress?.country ?? null,
|
|
705
676
|
isPrimary: input.isPrimary ?? existingAddress?.isPrimary ?? existingAddresses.length === 0,
|
|
706
677
|
};
|
|
707
678
|
if (existingAddress) {
|
|
@@ -725,27 +696,27 @@ async function getAccessibleBookingIds(db, params) {
|
|
|
725
696
|
: Promise.resolve([]),
|
|
726
697
|
linkedPersonId
|
|
727
698
|
? db
|
|
728
|
-
.select({ bookingId:
|
|
729
|
-
.from(
|
|
730
|
-
.where(eq(
|
|
699
|
+
.select({ bookingId: bookingTravelers.bookingId })
|
|
700
|
+
.from(bookingTravelers)
|
|
701
|
+
.where(eq(bookingTravelers.personId, linkedPersonId))
|
|
731
702
|
: Promise.resolve([]),
|
|
732
703
|
db
|
|
733
|
-
.select({ bookingId:
|
|
734
|
-
.from(
|
|
735
|
-
.where(sql `lower(${
|
|
704
|
+
.select({ bookingId: bookingTravelers.bookingId })
|
|
705
|
+
.from(bookingTravelers)
|
|
706
|
+
.where(sql `lower(${bookingTravelers.email}) = ${email}`),
|
|
736
707
|
]);
|
|
737
708
|
return Array.from(new Set([...directBookingRows, ...participantPersonRows, ...participantEmailRows].map((row) => row.bookingId)));
|
|
738
709
|
}
|
|
739
710
|
async function hasBookingAccess(params) {
|
|
740
|
-
const ownershipConditions = [sql `lower(${
|
|
711
|
+
const ownershipConditions = [sql `lower(${bookingTravelers.email}) = ${params.authEmail}`];
|
|
741
712
|
if (params.linkedPersonId) {
|
|
742
|
-
ownershipConditions.push(eq(
|
|
713
|
+
ownershipConditions.push(eq(bookingTravelers.personId, params.linkedPersonId));
|
|
743
714
|
}
|
|
744
715
|
const [participantMatch, bookingMatch] = await Promise.all([
|
|
745
716
|
params.db
|
|
746
|
-
.select({ bookingId:
|
|
747
|
-
.from(
|
|
748
|
-
.where(and(eq(
|
|
717
|
+
.select({ bookingId: bookingTravelers.bookingId })
|
|
718
|
+
.from(bookingTravelers)
|
|
719
|
+
.where(and(eq(bookingTravelers.bookingId, params.bookingId), or(...ownershipConditions)))
|
|
749
720
|
.limit(1),
|
|
750
721
|
params.linkedPersonId
|
|
751
722
|
? params.db
|
|
@@ -758,7 +729,22 @@ async function hasBookingAccess(params) {
|
|
|
758
729
|
return Boolean(participantMatch[0] || bookingMatch[0]);
|
|
759
730
|
}
|
|
760
731
|
async function getBookingBillingContact(db, bookingId, customerRecord) {
|
|
761
|
-
const [stateRows, primaryParticipantRows] = await Promise.all([
|
|
732
|
+
const [bookingRows, stateRows, primaryParticipantRows] = await Promise.all([
|
|
733
|
+
db
|
|
734
|
+
.select({
|
|
735
|
+
contactFirstName: bookings.contactFirstName,
|
|
736
|
+
contactLastName: bookings.contactLastName,
|
|
737
|
+
contactEmail: bookings.contactEmail,
|
|
738
|
+
contactPhone: bookings.contactPhone,
|
|
739
|
+
contactCountry: bookings.contactCountry,
|
|
740
|
+
contactRegion: bookings.contactRegion,
|
|
741
|
+
contactCity: bookings.contactCity,
|
|
742
|
+
contactAddressLine1: bookings.contactAddressLine1,
|
|
743
|
+
contactPostalCode: bookings.contactPostalCode,
|
|
744
|
+
})
|
|
745
|
+
.from(bookings)
|
|
746
|
+
.where(eq(bookings.id, bookingId))
|
|
747
|
+
.limit(1),
|
|
762
748
|
db
|
|
763
749
|
.select({ payload: bookingSessionStates.payload })
|
|
764
750
|
.from(bookingSessionStates)
|
|
@@ -766,36 +752,53 @@ async function getBookingBillingContact(db, bookingId, customerRecord) {
|
|
|
766
752
|
.limit(1),
|
|
767
753
|
db
|
|
768
754
|
.select({
|
|
769
|
-
firstName:
|
|
770
|
-
lastName:
|
|
771
|
-
email:
|
|
772
|
-
phone:
|
|
755
|
+
firstName: bookingTravelers.firstName,
|
|
756
|
+
lastName: bookingTravelers.lastName,
|
|
757
|
+
email: bookingTravelers.email,
|
|
758
|
+
phone: bookingTravelers.phone,
|
|
773
759
|
})
|
|
774
|
-
.from(
|
|
775
|
-
.where(and(eq(
|
|
776
|
-
.orderBy(asc(
|
|
760
|
+
.from(bookingTravelers)
|
|
761
|
+
.where(and(eq(bookingTravelers.bookingId, bookingId), eq(bookingTravelers.isPrimary, true)))
|
|
762
|
+
.orderBy(asc(bookingTravelers.createdAt))
|
|
777
763
|
.limit(1),
|
|
778
764
|
]);
|
|
765
|
+
const booking = bookingRows[0] ?? null;
|
|
779
766
|
const stateRow = stateRows[0] ?? null;
|
|
780
767
|
const primaryParticipant = primaryParticipantRows[0] ?? null;
|
|
781
768
|
const sessionBillingContact = resolveBillingContactFromSessionPayload(stateRow?.payload ?? null);
|
|
782
769
|
const billingAddress = customerRecord?.billingAddress ?? null;
|
|
783
770
|
const result = {
|
|
784
|
-
email:
|
|
785
|
-
|
|
786
|
-
|
|
771
|
+
email: booking?.contactEmail ??
|
|
772
|
+
sessionBillingContact?.email ??
|
|
773
|
+
primaryParticipant?.email ??
|
|
774
|
+
customerRecord?.email ??
|
|
775
|
+
null,
|
|
776
|
+
phone: booking?.contactPhone ??
|
|
777
|
+
sessionBillingContact?.phone ??
|
|
778
|
+
primaryParticipant?.phone ??
|
|
779
|
+
customerRecord?.phone ??
|
|
780
|
+
null,
|
|
781
|
+
firstName: booking?.contactFirstName ??
|
|
782
|
+
sessionBillingContact?.firstName ??
|
|
787
783
|
primaryParticipant?.firstName ??
|
|
788
784
|
customerRecord?.firstName ??
|
|
789
785
|
null,
|
|
790
|
-
lastName:
|
|
786
|
+
lastName: booking?.contactLastName ??
|
|
787
|
+
sessionBillingContact?.lastName ??
|
|
791
788
|
primaryParticipant?.lastName ??
|
|
792
789
|
customerRecord?.lastName ??
|
|
793
790
|
null,
|
|
794
|
-
country:
|
|
795
|
-
state: sessionBillingContact?.state ?? billingAddress?.region ?? null,
|
|
796
|
-
city:
|
|
797
|
-
address1:
|
|
798
|
-
|
|
791
|
+
country: booking?.contactCountry ?? sessionBillingContact?.country ?? billingAddress?.country ?? null,
|
|
792
|
+
state: booking?.contactRegion ?? sessionBillingContact?.state ?? billingAddress?.region ?? null,
|
|
793
|
+
city: booking?.contactCity ?? sessionBillingContact?.city ?? billingAddress?.city ?? null,
|
|
794
|
+
address1: booking?.contactAddressLine1 ??
|
|
795
|
+
sessionBillingContact?.address1 ??
|
|
796
|
+
billingAddress?.line1 ??
|
|
797
|
+
null,
|
|
798
|
+
postal: booking?.contactPostalCode ??
|
|
799
|
+
sessionBillingContact?.postal ??
|
|
800
|
+
billingAddress?.postalCode ??
|
|
801
|
+
null,
|
|
799
802
|
};
|
|
800
803
|
const hasValue = Object.values(result).some((value) => typeof value === "string" && value.length > 0);
|
|
801
804
|
return hasValue ? result : null;
|
|
@@ -808,9 +811,9 @@ async function buildBookingDetail(db, bookingId, customerRecord = null, options
|
|
|
808
811
|
const [participants, items, itemParticipantLinks, documents, fulfillments, legalDocuments, financeData, billingContact,] = await Promise.all([
|
|
809
812
|
db
|
|
810
813
|
.select()
|
|
811
|
-
.from(
|
|
812
|
-
.where(eq(
|
|
813
|
-
.orderBy(asc(
|
|
814
|
+
.from(bookingTravelers)
|
|
815
|
+
.where(eq(bookingTravelers.bookingId, booking.id))
|
|
816
|
+
.orderBy(asc(bookingTravelers.createdAt)),
|
|
814
817
|
db
|
|
815
818
|
.select()
|
|
816
819
|
.from(bookingItems)
|
|
@@ -818,16 +821,16 @@ async function buildBookingDetail(db, bookingId, customerRecord = null, options
|
|
|
818
821
|
.orderBy(asc(bookingItems.createdAt)),
|
|
819
822
|
db
|
|
820
823
|
.select({
|
|
821
|
-
id:
|
|
822
|
-
bookingItemId:
|
|
823
|
-
|
|
824
|
-
role:
|
|
825
|
-
isPrimary:
|
|
824
|
+
id: bookingItemTravelers.id,
|
|
825
|
+
bookingItemId: bookingItemTravelers.bookingItemId,
|
|
826
|
+
travelerId: bookingItemTravelers.travelerId,
|
|
827
|
+
role: bookingItemTravelers.role,
|
|
828
|
+
isPrimary: bookingItemTravelers.isPrimary,
|
|
826
829
|
})
|
|
827
|
-
.from(
|
|
828
|
-
.innerJoin(bookingItems, eq(bookingItems.id,
|
|
830
|
+
.from(bookingItemTravelers)
|
|
831
|
+
.innerJoin(bookingItems, eq(bookingItems.id, bookingItemTravelers.bookingItemId))
|
|
829
832
|
.where(eq(bookingItems.bookingId, booking.id))
|
|
830
|
-
.orderBy(asc(
|
|
833
|
+
.orderBy(asc(bookingItemTravelers.createdAt)),
|
|
831
834
|
db
|
|
832
835
|
.select()
|
|
833
836
|
.from(bookingDocuments)
|
|
@@ -847,7 +850,7 @@ async function buildBookingDetail(db, bookingId, customerRecord = null, options
|
|
|
847
850
|
const existing = itemLinksByItemId.get(link.bookingItemId) ?? [];
|
|
848
851
|
existing.push({
|
|
849
852
|
id: link.id,
|
|
850
|
-
|
|
853
|
+
travelerId: link.travelerId,
|
|
851
854
|
role: link.role,
|
|
852
855
|
isPrimary: link.isPrimary,
|
|
853
856
|
});
|
|
@@ -857,7 +860,7 @@ async function buildBookingDetail(db, bookingId, customerRecord = null, options
|
|
|
857
860
|
...documents.map((document) => ({
|
|
858
861
|
id: document.id,
|
|
859
862
|
source: "booking_document",
|
|
860
|
-
|
|
863
|
+
travelerId: document.travelerId ?? null,
|
|
861
864
|
type: document.type,
|
|
862
865
|
fileName: document.fileName,
|
|
863
866
|
fileUrl: document.fileUrl,
|
|
@@ -871,6 +874,7 @@ async function buildBookingDetail(db, bookingId, customerRecord = null, options
|
|
|
871
874
|
documents: financeData.documents,
|
|
872
875
|
payments: financeData.payments,
|
|
873
876
|
};
|
|
877
|
+
const travelerParticipants = participants.filter((participant) => ["traveler", "occupant", "other"].includes(participant.participantType));
|
|
874
878
|
return customerPortalBookingDetailSchema.parse({
|
|
875
879
|
bookingId: booking.id,
|
|
876
880
|
bookingNumber: booking.bookingNumber,
|
|
@@ -883,7 +887,7 @@ async function buildBookingDetail(db, bookingId, customerRecord = null, options
|
|
|
883
887
|
confirmedAt: normalizeDateTime(booking.confirmedAt),
|
|
884
888
|
cancelledAt: normalizeDateTime(booking.cancelledAt),
|
|
885
889
|
completedAt: normalizeDateTime(booking.completedAt),
|
|
886
|
-
|
|
890
|
+
travelers: travelerParticipants.map((participant) => ({
|
|
887
891
|
id: participant.id,
|
|
888
892
|
participantType: participant.participantType,
|
|
889
893
|
firstName: participant.firstName,
|
|
@@ -904,7 +908,7 @@ async function buildBookingDetail(db, bookingId, customerRecord = null, options
|
|
|
904
908
|
unitSellAmountCents: item.unitSellAmountCents ?? null,
|
|
905
909
|
totalSellAmountCents: item.totalSellAmountCents ?? null,
|
|
906
910
|
notes: item.notes ?? null,
|
|
907
|
-
|
|
911
|
+
travelerLinks: itemLinksByItemId.get(item.id) ?? [],
|
|
908
912
|
})),
|
|
909
913
|
billingContact,
|
|
910
914
|
documents: unifiedDocuments,
|
|
@@ -912,7 +916,7 @@ async function buildBookingDetail(db, bookingId, customerRecord = null, options
|
|
|
912
916
|
fulfillments: fulfillments.map((fulfillment) => ({
|
|
913
917
|
id: fulfillment.id,
|
|
914
918
|
bookingItemId: fulfillment.bookingItemId ?? null,
|
|
915
|
-
|
|
919
|
+
travelerId: fulfillment.travelerId ?? null,
|
|
916
920
|
fulfillmentType: fulfillment.fulfillmentType,
|
|
917
921
|
deliveryChannel: fulfillment.deliveryChannel,
|
|
918
922
|
status: fulfillment.status,
|
|
@@ -1021,8 +1025,6 @@ export const publicCustomerPortalService = {
|
|
|
1021
1025
|
const nextDateOfBirth = input.dateOfBirth !== undefined ? input.dateOfBirth : undefined;
|
|
1022
1026
|
const nextAddressRecord = input.address !== undefined
|
|
1023
1027
|
? {
|
|
1024
|
-
city: input.address.city,
|
|
1025
|
-
country: input.address.country,
|
|
1026
1028
|
billingAddress: {
|
|
1027
1029
|
line1: input.address.addressLine1,
|
|
1028
1030
|
line2: input.address.addressLine2,
|
|
@@ -1110,13 +1112,9 @@ export const publicCustomerPortalService = {
|
|
|
1110
1112
|
}
|
|
1111
1113
|
: undefined;
|
|
1112
1114
|
if (nextCustomerRecord || input.firstName !== undefined || input.lastName !== undefined) {
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
city: nextCustomerRecord.city,
|
|
1117
|
-
country: nextCustomerRecord.country,
|
|
1118
|
-
})
|
|
1119
|
-
: null;
|
|
1115
|
+
if (nextCustomerRecord?.billingAddress !== undefined) {
|
|
1116
|
+
await upsertCustomerBillingAddress(db, customerRecordId, nextCustomerRecord.billingAddress);
|
|
1117
|
+
}
|
|
1120
1118
|
await crmService.updatePerson(db, customerRecordId, {
|
|
1121
1119
|
...(input.firstName !== undefined ? { firstName: input.firstName ?? "" } : {}),
|
|
1122
1120
|
...(input.lastName !== undefined ? { lastName: input.lastName ?? "" } : {}),
|
|
@@ -1130,30 +1128,6 @@ export const publicCustomerPortalService = {
|
|
|
1130
1128
|
? { birthday: nextCustomerRecord.birthday }
|
|
1131
1129
|
: {}),
|
|
1132
1130
|
...(nextCustomerRecord?.phone !== undefined ? { phone: nextCustomerRecord.phone } : {}),
|
|
1133
|
-
...(nextCustomerRecord?.address !== undefined
|
|
1134
|
-
? { address: nextCustomerRecord.address }
|
|
1135
|
-
: {}),
|
|
1136
|
-
...(nextCustomerRecord?.city !== undefined ? { city: nextCustomerRecord.city } : {}),
|
|
1137
|
-
...(nextCustomerRecord?.country !== undefined
|
|
1138
|
-
? { country: nextCustomerRecord.country }
|
|
1139
|
-
: {}),
|
|
1140
|
-
...(nextCustomerRecord?.billingAddress !== undefined
|
|
1141
|
-
? {
|
|
1142
|
-
address: nextCustomerRecord.address !== undefined
|
|
1143
|
-
? nextCustomerRecord.address
|
|
1144
|
-
: formatCustomerAddress(billingAddress ?? nextCustomerRecord.billingAddress),
|
|
1145
|
-
city: nextCustomerRecord.city !== undefined
|
|
1146
|
-
? nextCustomerRecord.city
|
|
1147
|
-
: (normalizeNullableString(nextCustomerRecord.billingAddress.city) ??
|
|
1148
|
-
billingAddress?.city ??
|
|
1149
|
-
null),
|
|
1150
|
-
country: nextCustomerRecord.country !== undefined
|
|
1151
|
-
? nextCustomerRecord.country
|
|
1152
|
-
: (normalizeNullableString(nextCustomerRecord.billingAddress.country) ??
|
|
1153
|
-
billingAddress?.country ??
|
|
1154
|
-
null),
|
|
1155
|
-
}
|
|
1156
|
-
: {}),
|
|
1157
1131
|
});
|
|
1158
1132
|
}
|
|
1159
1133
|
}
|
|
@@ -1231,36 +1205,12 @@ export const publicCustomerPortalService = {
|
|
|
1231
1205
|
? { birthday: input.customerRecord.birthday }
|
|
1232
1206
|
: {}),
|
|
1233
1207
|
...(input.customerRecord?.phone !== undefined ? { phone: input.customerRecord.phone } : {}),
|
|
1234
|
-
...(input.customerRecord?.address !== undefined
|
|
1235
|
-
? { address: input.customerRecord.address }
|
|
1236
|
-
: {}),
|
|
1237
|
-
...(input.customerRecord?.city !== undefined ? { city: input.customerRecord.city } : {}),
|
|
1238
|
-
...(input.customerRecord?.country !== undefined
|
|
1239
|
-
? { country: input.customerRecord.country }
|
|
1240
|
-
: {}),
|
|
1241
|
-
...(input.customerRecord?.billingAddress !== undefined
|
|
1242
|
-
? {
|
|
1243
|
-
address: input.customerRecord.address !== undefined
|
|
1244
|
-
? input.customerRecord.address
|
|
1245
|
-
: formatCustomerAddress(input.customerRecord.billingAddress),
|
|
1246
|
-
city: input.customerRecord.city !== undefined
|
|
1247
|
-
? input.customerRecord.city
|
|
1248
|
-
: (normalizeNullableString(input.customerRecord.billingAddress.city) ?? null),
|
|
1249
|
-
country: input.customerRecord.country !== undefined
|
|
1250
|
-
? input.customerRecord.country
|
|
1251
|
-
: (normalizeNullableString(input.customerRecord.billingAddress.country) ?? null),
|
|
1252
|
-
}
|
|
1253
|
-
: {}),
|
|
1254
1208
|
});
|
|
1255
1209
|
if (!updated) {
|
|
1256
1210
|
return { error: "customer_record_not_found" };
|
|
1257
1211
|
}
|
|
1258
1212
|
if (input.customerRecord?.billingAddress) {
|
|
1259
|
-
await upsertCustomerBillingAddress(db, input.customerRecordId, input.customerRecord.billingAddress
|
|
1260
|
-
address: input.customerRecord.address,
|
|
1261
|
-
city: input.customerRecord.city,
|
|
1262
|
-
country: input.customerRecord.country,
|
|
1263
|
-
});
|
|
1213
|
+
await upsertCustomerBillingAddress(db, input.customerRecordId, input.customerRecord.billingAddress);
|
|
1264
1214
|
}
|
|
1265
1215
|
const profile = await this.getProfile(db, userId);
|
|
1266
1216
|
return {
|
|
@@ -1299,31 +1249,12 @@ export const publicCustomerPortalService = {
|
|
|
1299
1249
|
email: normalizedEmail,
|
|
1300
1250
|
phone: input.customerRecord?.phone ?? null,
|
|
1301
1251
|
website: null,
|
|
1302
|
-
address: input.customerRecord?.billingAddress !== undefined
|
|
1303
|
-
? input.customerRecord.address !== undefined
|
|
1304
|
-
? input.customerRecord.address
|
|
1305
|
-
: formatCustomerAddress(input.customerRecord.billingAddress)
|
|
1306
|
-
: (input.customerRecord?.address ?? null),
|
|
1307
|
-
city: input.customerRecord?.billingAddress !== undefined
|
|
1308
|
-
? input.customerRecord.city !== undefined
|
|
1309
|
-
? input.customerRecord.city
|
|
1310
|
-
: (normalizeNullableString(input.customerRecord.billingAddress.city) ?? null)
|
|
1311
|
-
: (input.customerRecord?.city ?? null),
|
|
1312
|
-
country: input.customerRecord?.billingAddress !== undefined
|
|
1313
|
-
? input.customerRecord.country !== undefined
|
|
1314
|
-
? input.customerRecord.country
|
|
1315
|
-
: (normalizeNullableString(input.customerRecord.billingAddress.country) ?? null)
|
|
1316
|
-
: (input.customerRecord?.country ?? null),
|
|
1317
1252
|
});
|
|
1318
1253
|
if (!created) {
|
|
1319
1254
|
return { error: "not_found" };
|
|
1320
1255
|
}
|
|
1321
1256
|
if (input.customerRecord?.billingAddress) {
|
|
1322
|
-
await upsertCustomerBillingAddress(db, created.id, input.customerRecord.billingAddress
|
|
1323
|
-
address: input.customerRecord.address,
|
|
1324
|
-
city: input.customerRecord.city,
|
|
1325
|
-
country: input.customerRecord.country,
|
|
1326
|
-
});
|
|
1257
|
+
await upsertCustomerBillingAddress(db, created.id, input.customerRecord.billingAddress);
|
|
1327
1258
|
}
|
|
1328
1259
|
const profile = await this.getProfile(db, userId);
|
|
1329
1260
|
return {
|
|
@@ -1343,7 +1274,7 @@ export const publicCustomerPortalService = {
|
|
|
1343
1274
|
companionMetadataKind)
|
|
1344
1275
|
.map(toCustomerCompanion);
|
|
1345
1276
|
},
|
|
1346
|
-
async
|
|
1277
|
+
async importBookingTravelersAsCompanions(db, userId, input) {
|
|
1347
1278
|
const authProfile = await getAuthProfileRow(db, userId);
|
|
1348
1279
|
const personId = await resolveLinkedCustomerRecordId(db, userId);
|
|
1349
1280
|
if (!authProfile || !personId) {
|
|
@@ -1358,13 +1289,18 @@ export const publicCustomerPortalService = {
|
|
|
1358
1289
|
if (targetBookingIds.length === 0) {
|
|
1359
1290
|
return { created: [], skippedCount: 0 };
|
|
1360
1291
|
}
|
|
1361
|
-
const [existingCompanionRows, participantRows] = await Promise.all([
|
|
1292
|
+
const [existingCompanionRows, participantRows, staffAssignmentRows] = await Promise.all([
|
|
1362
1293
|
identityService.listNamedContactsForEntity(db, "person", personId),
|
|
1363
1294
|
db
|
|
1364
1295
|
.select()
|
|
1365
|
-
.from(
|
|
1366
|
-
.where(inArray(
|
|
1367
|
-
.orderBy(asc(
|
|
1296
|
+
.from(bookingTravelers)
|
|
1297
|
+
.where(inArray(bookingTravelers.bookingId, targetBookingIds))
|
|
1298
|
+
.orderBy(asc(bookingTravelers.createdAt)),
|
|
1299
|
+
db
|
|
1300
|
+
.select()
|
|
1301
|
+
.from(bookingStaffAssignments)
|
|
1302
|
+
.where(inArray(bookingStaffAssignments.bookingId, targetBookingIds))
|
|
1303
|
+
.orderBy(asc(bookingStaffAssignments.createdAt)),
|
|
1368
1304
|
]);
|
|
1369
1305
|
const existingKeys = new Set(existingCompanionRows
|
|
1370
1306
|
.filter((row) => (row.metadata?.kind ?? null) ===
|
|
@@ -1376,11 +1312,19 @@ export const publicCustomerPortalService = {
|
|
|
1376
1312
|
})));
|
|
1377
1313
|
let skippedCount = 0;
|
|
1378
1314
|
const created = [];
|
|
1315
|
+
const distinctStaffAssignmentKeys = new Set();
|
|
1316
|
+
for (const assignment of staffAssignmentRows) {
|
|
1317
|
+
distinctStaffAssignmentKeys.add(JSON.stringify([
|
|
1318
|
+
assignment.bookingId,
|
|
1319
|
+
assignment.personId ?? null,
|
|
1320
|
+
assignment.firstName,
|
|
1321
|
+
assignment.lastName,
|
|
1322
|
+
assignment.email ?? null,
|
|
1323
|
+
assignment.phone ?? null,
|
|
1324
|
+
]));
|
|
1325
|
+
}
|
|
1326
|
+
skippedCount += distinctStaffAssignmentKeys.size;
|
|
1379
1327
|
for (const participant of participantRows) {
|
|
1380
|
-
if (participant.participantType === "staff") {
|
|
1381
|
-
skippedCount += 1;
|
|
1382
|
-
continue;
|
|
1383
|
-
}
|
|
1384
1328
|
const name = `${participant.firstName} ${participant.lastName}`.trim();
|
|
1385
1329
|
if (!name) {
|
|
1386
1330
|
skippedCount += 1;
|
|
@@ -1407,7 +1351,7 @@ export const publicCustomerPortalService = {
|
|
|
1407
1351
|
metadata: {
|
|
1408
1352
|
source: "booking_participant_import",
|
|
1409
1353
|
bookingId: participant.bookingId,
|
|
1410
|
-
|
|
1354
|
+
travelerId: participant.id,
|
|
1411
1355
|
participantType: participant.participantType,
|
|
1412
1356
|
travelerCategory: participant.travelerCategory ?? null,
|
|
1413
1357
|
},
|
|
@@ -1428,6 +1372,9 @@ export const publicCustomerPortalService = {
|
|
|
1428
1372
|
}
|
|
1429
1373
|
return { created, skippedCount };
|
|
1430
1374
|
},
|
|
1375
|
+
async importBookingParticipantsAsCompanions(db, userId, input) {
|
|
1376
|
+
return this.importBookingTravelersAsCompanions(db, userId, input);
|
|
1377
|
+
},
|
|
1431
1378
|
async createCompanion(db, userId, input) {
|
|
1432
1379
|
const personId = await resolveLinkedCustomerRecordId(db, userId);
|
|
1433
1380
|
if (!personId) {
|
|
@@ -1522,9 +1469,9 @@ export const publicCustomerPortalService = {
|
|
|
1522
1469
|
.orderBy(desc(bookings.createdAt)),
|
|
1523
1470
|
db
|
|
1524
1471
|
.select()
|
|
1525
|
-
.from(
|
|
1526
|
-
.where(inArray(
|
|
1527
|
-
.orderBy(asc(
|
|
1472
|
+
.from(bookingTravelers)
|
|
1473
|
+
.where(inArray(bookingTravelers.bookingId, bookingIds))
|
|
1474
|
+
.orderBy(asc(bookingTravelers.createdAt)),
|
|
1528
1475
|
db
|
|
1529
1476
|
.select({
|
|
1530
1477
|
bookingId: bookingItems.bookingId,
|
|
@@ -1584,7 +1531,7 @@ export const publicCustomerPortalService = {
|
|
|
1584
1531
|
pax: booking.pax ?? null,
|
|
1585
1532
|
confirmedAt: normalizeDateTime(booking.confirmedAt),
|
|
1586
1533
|
completedAt: normalizeDateTime(booking.completedAt),
|
|
1587
|
-
|
|
1534
|
+
travelerCount: participants.length,
|
|
1588
1535
|
primaryTravelerName: primaryTraveler
|
|
1589
1536
|
? `${primaryTraveler.firstName} ${primaryTraveler.lastName}`.trim()
|
|
1590
1537
|
: null,
|