Refactor HostHeader and HostParent models in Prisma schema for consistency and clarity. Update submitCompanyDetails API to improve JSON field normalization and error handling. Enhance host and parent document management with presigned URL generation for S3 uploads. Implement upsert logic for host documents and parent company details, ensuring proper handling of existing records.

This commit is contained in:
2025-11-28 17:20:27 +05:30
parent 71e3f2a933
commit 6147b0f476
4 changed files with 319 additions and 164 deletions

View File

@@ -648,23 +648,23 @@ model HostHeader {
userXid Int @map("user_xid")
user User @relation("HostUser", fields: [userXid], references: [id], onDelete: Cascade)
companyName String @map("company_name") @db.VarChar(100)
hostRefNumber String? @map("host_ref_number") @db.VarChar(30)
address1 String? @map("address_1") @db.VarChar(150)
hostRefNumber String? @map("host_ref_number") @db.VarChar(30)
address1 String? @map("address_1") @db.VarChar(150)
address2 String? @map("address_2") @db.VarChar(150)
cityXid Int? @map("city_xid")
cities Cities? @relation(fields: [cityXid], references: [id], onDelete: Restrict)
stateXid Int? @map("state_xid")
states States? @relation(fields: [stateXid], references: [id], onDelete: Restrict)
cityXid Int? @map("city_xid")
cities Cities? @relation(fields: [cityXid], references: [id], onDelete: Restrict)
stateXid Int? @map("state_xid")
states States? @relation(fields: [stateXid], references: [id], onDelete: Restrict)
countryXid Int? @map("country_xid")
countries Countries? @relation(fields: [countryXid], references: [id], onDelete: Restrict)
pinCode String? @map("pin_code") @db.VarChar(30)
pinCode String? @map("pin_code") @db.VarChar(30)
logoPath String? @map("logo_path") @db.VarChar(400)
isSubsidairy Boolean? @default(false) @map("is_subsidairy")
registrationNumber String? @map("registration_number") @db.VarChar(30)
panNumber String? @map("pan_number") @db.VarChar(30)
isSubsidairy Boolean? @default(false) @map("is_subsidairy")
registrationNumber String? @map("registration_number") @db.VarChar(30)
panNumber String? @map("pan_number") @db.VarChar(30)
gstNumber String? @map("gst_number") @db.VarChar(30)
formationDate DateTime? @map("formation_date")
companyType String? @map("company_type") @db.VarChar(30)
formationDate DateTime? @map("formation_date")
companyType String? @map("company_type") @db.VarChar(30)
websiteUrl String? @map("website_url") @db.VarChar(50)
instagramUrl String? @map("instagram_url") @db.VarChar(80)
facebookUrl String? @map("facebook_url") @db.VarChar(80)
@@ -673,16 +673,16 @@ model HostHeader {
currencyXid Int? @map("currency_xid")
currencies Currencies? @relation(fields: [currencyXid], references: [id], onDelete: Restrict)
stepper Int? @default(1) @map("stepper")
hostStatusInternal String? @default("pending") @map("host_status_internal") @db.VarChar(20)
hostStatusDisplay String? @default("pending") @map("host_status_Display") @db.VarChar(20)
adminStatusInternal String? @default("pending") @map("admin_status_internal") @db.VarChar(20)
adminStatusDisplay String? @default("pending") @map("admin_status_display") @db.VarChar(20)
hostStatusInternal String? @default("pending") @map("host_status_internal") @db.VarChar(20)
hostStatusDisplay String? @default("pending") @map("host_status_Display") @db.VarChar(20)
adminStatusInternal String? @default("pending") @map("admin_status_internal") @db.VarChar(20)
adminStatusDisplay String? @default("pending") @map("admin_status_display") @db.VarChar(20)
amStatus String? @default("pending") @map("am_status") @db.VarChar(20)
agreementAccepted Boolean? @default(false) @map("agreement_accepted")
agreementAccepted Boolean? @default(false) @map("agreement_accepted")
accountManagerXid Int? @map("account_manager_xid")
accountManager User? @relation("AccountManager", fields: [accountManagerXid], references: [id], onDelete: Restrict)
assignedOn DateTime? @map("assigned_on")
isApproved Boolean? @default(false) @map("is_approved")
isApproved Boolean? @default(false) @map("is_approved")
agreementStartDate DateTime? @map("agreement_start_date")
durationNumber Int? @map("duration_number")
durationFrequency String? @map("duration_frequency") @db.VarChar(20)
@@ -770,22 +770,22 @@ model HostParent {
hostXid Int @map("host_xid")
host HostHeader @relation(fields: [hostXid], references: [id], onDelete: Cascade)
companyName String @map("company_name") @db.VarChar(50)
address1 String @map("address_1") @db.VarChar(150)
address1 String? @map("address_1") @db.VarChar(150)
address2 String? @map("address_2") @db.VarChar(150)
cityXid Int @map("city_xid")
cities Cities @relation(fields: [cityXid], references: [id], onDelete: Restrict)
stateXid Int @map("state_xid")
states States @relation(fields: [stateXid], references: [id], onDelete: Restrict)
countryXid Int @map("country_xid")
countries Countries @relation(fields: [countryXid], references: [id], onDelete: Restrict)
pinCode String @map("pin_code") @db.VarChar(30)
cityXid Int? @map("city_xid")
cities Cities? @relation(fields: [cityXid], references: [id], onDelete: Restrict)
stateXid Int? @map("state_xid")
states States? @relation(fields: [stateXid], references: [id], onDelete: Restrict)
countryXid Int? @map("country_xid")
countries Countries? @relation(fields: [countryXid], references: [id], onDelete: Restrict)
pinCode String? @map("pin_code") @db.VarChar(30)
logoPath String? @map("logo_path") @db.VarChar(400)
isSubsidairy Boolean @default(false) @map("is_subsidairy")
registrationNumber String @map("registration_number") @db.VarChar(30)
panNumber String @map("pan_number") @db.VarChar(30)
registrationNumber String? @map("registration_number") @db.VarChar(30)
panNumber String? @map("pan_number") @db.VarChar(30)
gstNumber String? @map("gst_number") @db.VarChar(30)
formationDate DateTime @map("formation_date")
companyType String @map("company_type") @db.VarChar(30)
formationDate DateTime? @map("formation_date")
companyType String? @map("company_type") @db.VarChar(30)
websiteUrl String? @map("website_url") @db.VarChar(80)
instagramUrl String? @map("instagram_url") @db.VarChar(80)
facebookUrl String? @map("facebook_url") @db.VarChar(80)

View File

@@ -26,9 +26,9 @@ function normalizeJsonField(fields: any, key: string) {
if (!fields[key]) return undefined;
const val = fields[key];
if (typeof val === "object") return val;
if (typeof val === 'object') return val;
if (typeof val === "string") {
if (typeof val === 'string') {
try {
return JSON.parse(val);
} catch (err) {
@@ -39,12 +39,12 @@ function normalizeJsonField(fields: any, key: string) {
}
function cleanEmptyStrings(obj: any) {
if (!obj || typeof obj !== "object") return obj;
if (!obj || typeof obj !== 'object') return obj;
const cleaned: any = {};
for (const key of Object.keys(obj)) {
if (obj[key] === "") cleaned[key] = undefined;
else if (typeof obj[key] === "object") cleaned[key] = cleanEmptyStrings(obj[key]);
if (obj[key] === '') cleaned[key] = undefined;
else if (typeof obj[key] === 'object') cleaned[key] = cleanEmptyStrings(obj[key]);
else cleaned[key] = obj[key];
}
return cleaned;
@@ -104,7 +104,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
file.on('error', (error) => reject(new ApiError(400, `File upload error: ${error.message}`)));
});
bb.on('field', (fieldname, val) => fields[fieldname] = val);
bb.on('field', (fieldname, val) => (fields[fieldname] = val));
bb.on('close', () => resolve());
bb.on('error', (error) => reject(new ApiError(400, `Multipart parsing error: ${error.message}`)));
@@ -113,11 +113,11 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
});
/** 4) Extract and clean isDraft flag */
const isDraft = fields.isDraft === "true" || fields.isDraft === true;
const isDraft = fields.isDraft === 'true' || fields.isDraft === true;
/** 5) PROCESS companyDetails ONCE ONLY (IMPORTANT FIX) */
let companyDetailsRaw = normalizeJsonField(fields, "companyDetails");
if (!companyDetailsRaw) throw new ApiError(400, "companyDetails is required.");
let companyDetailsRaw = normalizeJsonField(fields, 'companyDetails');
if (!companyDetailsRaw) throw new ApiError(400, 'companyDetails is required.');
if (isDraft) {
companyDetailsRaw = cleanEmptyStrings(companyDetailsRaw);
@@ -128,11 +128,9 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
}
}
/** 6) Profile update if provided */
if (fields.userProfile) {
const userProfileRaw = normalizeJsonField(fields, "userProfile");
const userProfileRaw = normalizeJsonField(fields, 'userProfile');
if (userProfileRaw) {
const { firstName, lastName, mobileNumber } = userProfileRaw;
@@ -153,20 +151,20 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
if (!isDraft) {
const validate = hostCompanyDetailsSchema.safeParse(companyDetailsRaw);
if (!validate.success) {
const message = validate.error.issues.map(i => i.message).join(', ');
const message = validate.error.issues.map((i) => i.message).join(', ');
throw new ApiError(400, `Validation failed: ${message}`);
}
parsedCompany = validate.data;
}
/** 8) DOCUMENT METADATA */
const documentsMetadataRaw = normalizeJsonField(fields, "documents");
if (!Array.isArray(documentsMetadataRaw)) throw new ApiError(400, "documents must be an array.");
const documentsMetadataRaw = normalizeJsonField(fields, 'documents');
if (!Array.isArray(documentsMetadataRaw)) throw new ApiError(400, 'documents must be an array.');
if (!isDraft) {
const docsParse = hostDocumentsSchema.safeParse(documentsMetadataRaw);
if (!docsParse.success) {
const message = docsParse.error.issues.map(i => i.message).join(', ');
const message = docsParse.error.issues.map((i) => i.message).join(', ');
throw new ApiError(400, `Documents validation failed: ${message}`);
}
}
@@ -177,7 +175,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
}));
const documentMetadata = documentsMetadata.map((doc: any) => {
const file = files.find(f => f.fieldName === doc.fieldName);
const file = files.find((f) => f.fieldName === doc.fieldName);
// In DRAFT mode → allow missing documents
if (isDraft && !file) {
@@ -192,10 +190,9 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
return { ...doc, file };
});
/** 9) SPLIT host & parent docs */
const hostDocs = documentMetadata.filter(d => d.owner === 'host');
const parentDocs = documentMetadata.filter(d => d.owner === 'parent');
const hostDocs = documentMetadata.filter((d) => d.owner === 'host');
const parentDocs = documentMetadata.filter((d) => d.owner === 'parent');
/** 10) VALIDATE PARENT COMPANY (ONLY IN FINAL SUBMISSION) */
let parsedParentCompany: any = null;
@@ -207,7 +204,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
const parentCheck = parentCompanySchema.safeParse(parsedCompany.parentCompany);
if (!parentCheck.success) {
const message = parentCheck.error.issues.map(i => i.message).join(', ');
const message = parentCheck.error.issues.map((i) => i.message).join(', ');
throw new ApiError(400, `Parent company validation failed: ${message}`);
}
@@ -218,33 +215,39 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
/** 11) UPLOAD DOCUMENTS */
async function uploadToS3(buffer, mimeType, originalName, folderType, documentTypeXid?, fieldName?) {
const sanitizeFileName = (name: string) =>
name.toLowerCase().replace(/[^a-z0-9.]/g, '_').replace(/_+/g, '_');
const ext = originalName.split('.').pop() || 'jpg';
const ext = originalName.split('.').pop() || 'pdf';
let s3Key = '';
let s3Key = "";
if (folderType === 'logo') {
s3Key = `Documents/Host/${userInfo.id}/logo/${sanitizeFileName(originalName)}`;
} else if (folderType === 'documents') {
s3Key = `Documents/Host/${userInfo.id}/documents/${sanitizeFileName(`${documentTypeXid}_${fieldName}.${ext}`)}`;
} else if (folderType === 'parent_company') {
s3Key = `Documents/Host/${userInfo.id}/parent_company/${sanitizeFileName(`${documentTypeXid}_${fieldName}.${ext}`)}`;
s3Key = `Documents/Host/${userInfo.id}/logo/company_logo.${ext}`;
}
else if (folderType === 'parent_company_logo') {
s3Key = `Documents/Host/${userInfo.id}/parent_company/logo/parent_company_logo.${ext}`;
}
else if (folderType === 'documents') {
s3Key = `Documents/Host/${userInfo.id}/documents/${documentTypeXid}_${fieldName}.${ext}`;
}
else if (folderType === 'parent_company') {
s3Key = `Documents/Host/${userInfo.id}/parent_company/${documentTypeXid}_${fieldName}.${ext}`;
}
await s3.upload({
Bucket: config.aws.bucketName,
Key: s3Key,
Body: buffer,
ContentType: mimeType,
ACL: 'private'
}).promise();
await s3
.upload({
Bucket: config.aws.bucketName,
Key: s3Key,
Body: buffer,
ContentType: mimeType,
ACL: 'private',
})
.promise();
return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
}
/** Upload host docs */
const uploadedHostDocs = [];
const uploadedHostDocs: Array<any> = [];
for (const doc of hostDocs) {
if (isDraft && !doc.file) continue;
@@ -254,7 +257,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
doc.file.fileName,
'documents',
doc.documentTypeXid,
doc.fieldName
doc.fieldName,
);
uploadedHostDocs.push({
@@ -264,9 +267,8 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
});
}
/** Upload parent docs */
const uploadedParentDocs = [];
const uploadedParentDocs: Array<any> = [];
for (const doc of parentDocs) {
if (!doc.file && isDraft) continue; // skip missing files in draft mode
@@ -276,7 +278,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
doc.file.fileName,
'parent_company',
doc.documentTypeXid,
doc.fieldName
doc.fieldName,
);
uploadedParentDocs.push({
@@ -286,6 +288,31 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
});
}
/** UPLOAD LOGO (if provided) */
const logoFile = files.find((f) => f.fieldName === 'companyLogo' || f.fieldName === 'companyLogoFile');
if (logoFile) {
const logoUrl = await uploadToS3(logoFile.buffer, logoFile.mimeType, logoFile.fileName, 'logo');
parsedCompany.logoPath = logoUrl;
}
/** UPLOAD PARENT COMPANY LOGO (if provided) */
const parentLogoFile = files.find((f) => f.fieldName === 'parentCompanyLogo');
if (parentLogoFile) {
const parentLogoUrl = await uploadToS3(
parentLogoFile.buffer,
parentLogoFile.mimeType,
parentLogoFile.fileName,
'parent_company_logo',
);
if (parsedParentCompany) {
parsedParentCompany.logoPath = parentLogoUrl;
} else {
// if no parent object exists yet (drafts or other flows), attach it safely
parsedParentCompany = parsedParentCompany || {};
parsedParentCompany.logoPath = parentLogoUrl;
}
}
/** 12) SAVE / UPDATE HOST ENTRY */
const createdOrUpdated = await hostService.addOrUpdateCompanyDetails(
@@ -294,7 +321,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
uploadedHostDocs,
parsedParentCompany,
uploadedParentDocs,
isDraft
isDraft,
);
if (!createdOrUpdated) throw new ApiError(400, 'Failed to add/update company details.');
@@ -308,14 +335,14 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
details.hostDetails.accountManager.emailAddress,
details.hostDetails.accountManager.firstName,
details.hostDetails.companyName,
details.hostDetails.hostRefNumber
details.hostDetails.hostRefNumber,
);
} else {
await sendEmailToMinglarAdmin(
config.MinglarAdminEmail,
config.MinglarAdminName,
details.hostDetails.companyName,
details.hostDetails.hostRefNumber
details.hostDetails.hostRefNumber,
);
}
}
@@ -329,17 +356,17 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
},
body: JSON.stringify({
success: true,
message: isDraft ? "Company details saved as draft successfully." : "Company details uploaded successfully.",
message: isDraft ? 'Company details saved as draft successfully.' : 'Company details uploaded successfully.',
data: {
id: createdOrUpdated.id,
hostRefNumber: createdOrUpdated.hostRefNumber,
isDraft
}
isDraft,
},
}),
};
} catch (error: any) {
console.error("❌ Error in addCompanyDetails:", error);
console.error('❌ Error in addCompanyDetails:', error);
throw error;
}
});

View File

@@ -63,15 +63,27 @@ export class HostService {
const host = await this.prisma.hostHeader.findFirst({
where: { userXid: id },
include: {
hostParent: true,
hostParent: {
include: {
HostParenetDocuments: {
select: {
id: true,
filePath: true,
documentName: true,
documentTypeXid: true,
documentType: true
}
}
}
},
HostBankDetails: true,
HostDocuments: {
include: {
documentType: true,
},
},
user:{
select:{
user: {
select: {
id: true,
emailAddress: true,
firstName: true,
@@ -96,8 +108,9 @@ export class HostService {
return { stepper: STEPPER.NOT_SUBMITTED } as any;
}
const bucket = config.aws.bucketName;
if (host.HostDocuments?.length) {
const bucket = config.aws.bucketName;
for (const doc of host.HostDocuments) {
if (doc.filePath) {
@@ -113,6 +126,40 @@ export class HostService {
}
}
if (host.logoPath) {
const key = host.logoPath.startsWith('http')
? host.logoPath.split('.com/')[1]
: host.logoPath;
host.logoPath = await getPresignedUrl(bucket, key);
}
if (host.hostParent?.length) {
const parent = host.hostParent[0]; // since you allow only 1 parent
// Parent company logo
if (parent.logoPath) {
const key = parent.logoPath.startsWith("http")
? parent.logoPath.split(".com/")[1]
: parent.logoPath;
parent.logoPath = await getPresignedUrl(bucket, key);
}
// Parent documents
if (parent.HostParenetDocuments?.length) {
for (const doc of parent.HostParenetDocuments) {
if (doc.filePath) {
const key = doc.filePath.startsWith("http")
? doc.filePath.split(".com/")[1]
: doc.filePath;
(doc as any).presignedUrl = await getPresignedUrl(bucket, key);
}
}
}
}
return host;
}
@@ -337,7 +384,7 @@ export class HostService {
documents: HostDocumentInput[],
parentCompanyData?: any | null,
parentDocuments?: HostDocumentInput[],
isDraft: boolean = false, // Add isDraft parameter with default false
isDraft: boolean = false,
) {
return await this.prisma.$transaction(async (tx) => {
// Check if host already has a company
@@ -363,9 +410,7 @@ export class HostService {
? MINGLAR_STATUS_DISPLAY.DRAFT
: MINGLAR_STATUS_DISPLAY.NEW;
const stepper = isDraft
? STEPPER.NOT_SUBMITTED
: STEPPER.UNDER_REVIEW;
const stepper = isDraft ? STEPPER.NOT_SUBMITTED : STEPPER.UNDER_REVIEW;
// CREATE
if (!existingHostCompany) {
@@ -375,10 +420,7 @@ export class HostService {
where: { panNumber: companyData.panNumber },
});
if (existingByPan)
throw new ApiError(
400,
'Company already exists with this pan/bin number',
);
throw new ApiError(400, 'Company already exists with this pan/bin number');
}
const refNumber = await this.generateHostRefNumber(tx);
@@ -390,17 +432,16 @@ export class HostService {
hostRefNumber: refNumber,
address1: companyData.address1,
address2: companyData.address2,
cities: { connect: { id: companyData.cityXid } },
states: { connect: { id: companyData.stateXid } },
countries: { connect: { id: companyData.countryXid } },
cities: companyData.cityXid ? { connect: { id: companyData.cityXid } } : undefined,
states: companyData.stateXid ? { connect: { id: companyData.stateXid } } : undefined,
countries: companyData.countryXid ? { connect: { id: companyData.countryXid } } : undefined,
pinCode: companyData.pinCode,
logoPath: companyData.logoPath || null,
isSubsidairy: companyData.isSubsidairy,
registrationNumber: companyData.registrationNumber,
panNumber: companyData.panNumber,
gstNumber: companyData.gstNumber || null,
// formationDate: new Date(companyData.formationDate),
formationDate: companyData.formationDate ? new Date(companyData.formationDate) : null,
formationDate: companyData.formationDate ? new Date(companyData.formationDate as any) : null,
companyType: companyData.companyType,
websiteUrl: companyData.websiteUrl || null,
instagramUrl: companyData.instagramUrl || null,
@@ -415,7 +456,7 @@ export class HostService {
},
});
// Create host documents
// Create host documents (initial insert)
if (documents?.length) {
const docsData = documents.map((doc) => ({
hostXid: createdHost.id,
@@ -432,20 +473,19 @@ export class HostService {
data: {
hostXid: createdHost.id,
companyName: parentCompanyData.companyName,
address1: parentCompanyData.address1,
address1: parentCompanyData.address1 || null,
address2: parentCompanyData.address2 || null,
cityXid: parentCompanyData.cityXid,
stateXid: parentCompanyData.stateXid,
countryXid: parentCompanyData.countryXid,
pinCode: parentCompanyData.pinCode,
cityXid: parentCompanyData.cityXid || null,
stateXid: parentCompanyData.stateXid || null,
countryXid: parentCompanyData.countryXid || null,
pinCode: parentCompanyData.pinCode || null,
logoPath: parentCompanyData.logoPath || null,
isSubsidairy: false,
registrationNumber: parentCompanyData.registrationNumber,
panNumber: parentCompanyData.panNumber,
registrationNumber: parentCompanyData.registrationNumber || null,
panNumber: parentCompanyData.panNumber || null,
gstNumber: parentCompanyData.gstNumber || null,
// formationDate: new Date(parentCompanyData.formationDate),
formationDate: parentCompanyData.formationDate ? new Date(parentCompanyData.formationDate) : null,
companyType: parentCompanyData.companyType,
formationDate: parentCompanyData.formationDate ? new Date(parentCompanyData.formationDate as any) : null,
companyType: parentCompanyData.companyType || null,
websiteUrl: parentCompanyData.websiteUrl || null,
instagramUrl: parentCompanyData.instagramUrl || null,
facebookUrl: parentCompanyData.facebookUrl || null,
@@ -475,16 +515,16 @@ export class HostService {
companyName: companyData.companyName,
address1: companyData.address1,
address2: companyData.address2,
cities: { connect: { id: companyData.cityXid } },
states: { connect: { id: companyData.stateXid } },
countries: { connect: { id: companyData.countryXid } },
cities: companyData.cityXid ? { connect: { id: companyData.cityXid } } : undefined,
states: companyData.stateXid ? { connect: { id: companyData.stateXid } } : undefined,
countries: companyData.countryXid ? { connect: { id: companyData.countryXid } } : undefined,
pinCode: companyData.pinCode,
logoPath: companyData.logoPath || null,
isSubsidairy: companyData.isSubsidairy,
registrationNumber: companyData.registrationNumber,
panNumber: companyData.panNumber,
gstNumber: companyData.gstNumber || null,
formationDate: companyData.formationDate ? new Date(companyData.formationDate) : null,
formationDate: companyData.formationDate ? new Date(companyData.formationDate as any) : null,
companyType: companyData.companyType,
websiteUrl: companyData.websiteUrl || null,
instagramUrl: companyData.instagramUrl || null,
@@ -499,19 +539,39 @@ export class HostService {
},
});
// Replace host documents (delete old, insert new)
await tx.hostDocuments.deleteMany({ where: { hostXid: updatedHost.id } });
// REPLACE/UPSERT host documents by documentTypeXid (Option A)
if (documents?.length) {
const docsData = documents.map((doc) => ({
hostXid: updatedHost.id,
documentTypeXid: doc.documentTypeXid,
documentName: doc.documentName,
filePath: doc.filePath,
}));
await tx.hostDocuments.createMany({ data: docsData });
for (const doc of documents) {
const existingDoc = await tx.hostDocuments.findFirst({
where: {
hostXid: updatedHost.id,
documentTypeXid: doc.documentTypeXid,
},
});
if (existingDoc) {
// update only filePath (and name if required)
await tx.hostDocuments.update({
where: { id: existingDoc.id },
data: {
filePath: doc.filePath,
documentName: doc.documentName || existingDoc.documentName,
},
});
} else {
await tx.hostDocuments.create({
data: {
hostXid: updatedHost.id,
documentTypeXid: doc.documentTypeXid,
documentName: doc.documentName,
filePath: doc.filePath,
},
});
}
}
}
// Parent company create/update and replace parent docs
// Parent company create/update and replace parent docs by documentTypeXid
if (companyData.isSubsidairy) {
let parentRecord = (existingHostCompany as any).hostParent;
if (Array.isArray(parentRecord)) parentRecord = parentRecord[0];
@@ -522,19 +582,19 @@ export class HostService {
data: {
hostXid: updatedHost.id,
companyName: parentCompanyData.companyName,
address1: parentCompanyData.address1,
address1: parentCompanyData.address1 || null,
address2: parentCompanyData.address2 || null,
cityXid: parentCompanyData.cityXid,
stateXid: parentCompanyData.stateXid,
countryXid: parentCompanyData.countryXid,
pinCode: parentCompanyData.pinCode,
cityXid: parentCompanyData.cityXid || null,
stateXid: parentCompanyData.stateXid || null,
countryXid: parentCompanyData.countryXid || null,
pinCode: parentCompanyData.pinCode || null,
logoPath: parentCompanyData.logoPath || null,
isSubsidairy: false,
registrationNumber: parentCompanyData.registrationNumber,
panNumber: parentCompanyData.panNumber,
registrationNumber: parentCompanyData.registrationNumber || null,
panNumber: parentCompanyData.panNumber || null,
gstNumber: parentCompanyData.gstNumber || null,
formationDate: parentCompanyData.formationDate ? new Date(parentCompanyData.formationDate) : null,
companyType: parentCompanyData.companyType,
formationDate: parentCompanyData.formationDate ? new Date(parentCompanyData.formationDate as any) : null,
companyType: parentCompanyData.companyType || null,
websiteUrl: parentCompanyData.websiteUrl || null,
instagramUrl: parentCompanyData.instagramUrl || null,
facebookUrl: parentCompanyData.facebookUrl || null,
@@ -544,32 +604,35 @@ export class HostService {
});
if (parentDocuments?.length) {
const parentDocsData = parentDocuments.map((doc) => ({
hostParentXid: createdParent.id,
documentTypeXid: doc.documentTypeXid,
documentName: doc.documentName,
filePath: doc.filePath,
}));
await tx.hostParenetDocuments.createMany({ data: parentDocsData });
for (const doc of parentDocuments) {
await tx.hostParenetDocuments.create({
data: {
hostParentXid: createdParent.id,
documentTypeXid: doc.documentTypeXid,
documentName: doc.documentName,
filePath: doc.filePath,
},
});
}
}
} else {
// update
// update existing parent
await tx.hostParent.update({
where: { id: parentRecord.id },
data: {
companyName: parentCompanyData.companyName,
address1: parentCompanyData.address1,
address1: parentCompanyData.address1 || null,
address2: parentCompanyData.address2 || null,
cityXid: parentCompanyData.cityXid,
stateXid: parentCompanyData.stateXid,
countryXid: parentCompanyData.countryXid,
pinCode: parentCompanyData.pinCode,
cityXid: parentCompanyData.cityXid || null,
stateXid: parentCompanyData.stateXid || null,
countryXid: parentCompanyData.countryXid || null,
pinCode: parentCompanyData.pinCode || null,
logoPath: parentCompanyData.logoPath || null,
registrationNumber: parentCompanyData.registrationNumber,
panNumber: parentCompanyData.panNumber,
registrationNumber: parentCompanyData.registrationNumber || null,
panNumber: parentCompanyData.panNumber || null,
gstNumber: parentCompanyData.gstNumber || null,
formationDate: parentCompanyData.formationDate ? new Date(parentCompanyData.formationDate) : null,
companyType: parentCompanyData.companyType,
formationDate: parentCompanyData.formationDate ? new Date(parentCompanyData.formationDate as any) : null,
companyType: parentCompanyData.companyType || null,
websiteUrl: parentCompanyData.websiteUrl || null,
instagramUrl: parentCompanyData.instagramUrl || null,
facebookUrl: parentCompanyData.facebookUrl || null,
@@ -578,18 +641,35 @@ export class HostService {
},
});
// replace parent docs
await tx.hostParenetDocuments.deleteMany({
where: { hostParentXid: parentRecord.id },
});
// replace / upsert parent docs by documentTypeXid (no deletes)
if (parentDocuments?.length) {
const parentDocsData = parentDocuments.map((doc) => ({
hostParentXid: parentRecord.id,
documentTypeXid: doc.documentTypeXid,
documentName: doc.documentName,
filePath: doc.filePath,
}));
await tx.hostParenetDocuments.createMany({ data: parentDocsData });
for (const doc of parentDocuments) {
const existingParentDoc = await tx.hostParenetDocuments.findFirst({
where: {
hostParentXid: parentRecord.id,
documentTypeXid: doc.documentTypeXid,
},
});
if (existingParentDoc) {
await tx.hostParenetDocuments.update({
where: { id: existingParentDoc.id },
data: {
filePath: doc.filePath,
documentName: doc.documentName || existingParentDoc.documentName,
},
});
} else {
await tx.hostParenetDocuments.create({
data: {
hostParentXid: parentRecord.id,
documentTypeXid: doc.documentTypeXid,
documentName: doc.documentName,
filePath: doc.filePath,
},
});
}
}
}
}
} else {
@@ -609,16 +689,17 @@ export class HostService {
}
}
const hostDetails = await this.prisma.hostHeader.findFirst({
// Create a host track entry
const hostDetails = await tx.hostHeader.findFirst({
where: { userXid: user_xid },
});
await this.prisma.hostTrack.create({
await tx.hostTrack.create({
data: {
hostXid: hostDetails.id,
hostXid: hostDetails!.id,
updatedByRole: ROLE_NAME.HOST,
updatedByXid: user_xid,
trackStatus: hostDetails.hostStatusInternal,
trackStatus: hostDetails!.hostStatusInternal,
},
});

View File

@@ -1324,7 +1324,19 @@ export class MinglarService {
const host = await this.prisma.hostHeader.findFirst({
where: { id: host_xid },
include: {
hostParent: true,
hostParent: {
include: {
HostParenetDocuments: {
select: {
id: true,
filePath: true,
documentName: true,
documentTypeXid: true,
documentType: true
}
}
}
},
HostBankDetails: true,
HostDocuments: {
include: {
@@ -1351,8 +1363,9 @@ export class MinglarService {
},
});
const bucket = config.aws.bucketName;
if (host.HostDocuments?.length) {
const bucket = config.aws.bucketName;
for (const doc of host.HostDocuments) {
if (doc.filePath) {
@@ -1368,6 +1381,40 @@ export class MinglarService {
}
}
if (host.logoPath) {
const key = host.logoPath.startsWith('http')
? host.logoPath.split('.com/')[1]
: host.logoPath;
host.logoPath = await getPresignedUrl(bucket, key);
}
if (host.hostParent?.length) {
const parent = host.hostParent[0]; // since you allow only 1 parent
// Parent company logo
if (parent.logoPath) {
const key = parent.logoPath.startsWith("http")
? parent.logoPath.split(".com/")[1]
: parent.logoPath;
parent.logoPath = await getPresignedUrl(bucket, key);
}
// Parent documents
if (parent.HostParenetDocuments?.length) {
for (const doc of parent.HostParenetDocuments) {
if (doc.filePath) {
const key = doc.filePath.startsWith("http")
? doc.filePath.split(".com/")[1]
: doc.filePath;
(doc as any).presignedUrl = await getPresignedUrl(bucket, key);
}
}
}
}
return host;
}
}