refactor: centralize profile field retrieval and filtering logic in buildFieldValuesResponse helper
This commit is contained in:
@@ -1099,103 +1099,54 @@ export class AgentsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getFieldValues(userId: string) {
|
async getFieldValues(userId: string) {
|
||||||
// Get agent profile
|
|
||||||
const profile = await this.prisma.agentProfile.findUnique({
|
const profile = await this.prisma.agentProfile.findUnique({
|
||||||
where: { userId },
|
where: { userId },
|
||||||
|
select: { id: true, agentTypeId: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
throw new NotFoundException('Agent profile not found');
|
throw new NotFoundException('Agent profile not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all field values for this profile, ordered by admin-defined section + field sort order
|
return this.buildFieldValuesResponse(profile);
|
||||||
const fieldValues = await this.prisma.agentProfileFieldValue.findMany({
|
|
||||||
where: { agentProfileId: profile.id },
|
|
||||||
include: {
|
|
||||||
field: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
slug: true,
|
|
||||||
name: true,
|
|
||||||
fieldType: true,
|
|
||||||
options: true,
|
|
||||||
section: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
slug: true,
|
|
||||||
name: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
orderBy: [
|
|
||||||
{ field: { section: { sortOrder: 'asc' } } },
|
|
||||||
{ field: { sortOrder: 'asc' } },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Helper to resolve a raw value to its option label (for SELECT/RADIO/MULTI_SELECT fields)
|
|
||||||
// Returns `null` when no matching option is found — the caller can treat those as orphans.
|
|
||||||
const resolveLabel = (rawValue: any, options: any): any => {
|
|
||||||
if (!options || !Array.isArray(options) || options.length === 0) return rawValue;
|
|
||||||
const lookup = (val: any): string | null => {
|
|
||||||
const match = (options as any[]).find((o: any) => o.value === val || o.value === String(val));
|
|
||||||
return match?.label ?? null;
|
|
||||||
};
|
|
||||||
if (Array.isArray(rawValue)) return rawValue.map(lookup);
|
|
||||||
return lookup(rawValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Transform to a more usable format. For enumerated field types, drop any array entry
|
|
||||||
// whose value no longer resolves to a current option so stale selections never leak through.
|
|
||||||
const transformedValues = fieldValues.map((fv) => {
|
|
||||||
const rawValue = this.extractValue(fv);
|
|
||||||
let value: unknown = rawValue;
|
|
||||||
let valueLabel: unknown = resolveLabel(rawValue, fv.field.options);
|
|
||||||
const isEnum =
|
|
||||||
fv.field.fieldType === 'CHECKBOX_GROUP' ||
|
|
||||||
fv.field.fieldType === 'MULTI_SELECT' ||
|
|
||||||
fv.field.fieldType === 'SELECT' ||
|
|
||||||
fv.field.fieldType === 'RADIO';
|
|
||||||
if (isEnum && Array.isArray(value) && Array.isArray(valueLabel)) {
|
|
||||||
const pairs = (value as unknown[]).map((v, i) => ({ v, l: (valueLabel as unknown[])[i] }))
|
|
||||||
.filter((p) => p.l !== null && p.l !== undefined);
|
|
||||||
value = pairs.map((p) => p.v);
|
|
||||||
valueLabel = pairs.map((p) => p.l);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
fieldSlug: fv.field.slug,
|
|
||||||
fieldName: fv.field.name,
|
|
||||||
fieldType: fv.field.fieldType,
|
|
||||||
sectionSlug: fv.field.section.slug,
|
|
||||||
sectionName: fv.field.section.name,
|
|
||||||
value,
|
|
||||||
valueLabel,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
agentProfileId: profile.id,
|
|
||||||
fieldValues: transformedValues,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFieldValuesByAgentId(agentId: string) {
|
async getFieldValuesByAgentId(agentId: string) {
|
||||||
// Get agent profile by ID
|
|
||||||
const profile = await this.prisma.agentProfile.findUnique({
|
const profile = await this.prisma.agentProfile.findUnique({
|
||||||
where: { id: agentId },
|
where: { id: agentId },
|
||||||
|
select: { id: true, agentTypeId: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
throw new NotFoundException('Agent profile not found');
|
throw new NotFoundException('Agent profile not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all field values for this profile, ordered by admin-defined section + field sort order
|
return this.buildFieldValuesResponse(profile);
|
||||||
const fieldValues = await this.prisma.agentProfileFieldValue.findMany({
|
}
|
||||||
where: { agentProfileId: profile.id },
|
|
||||||
include: {
|
// Returns one entry per field applicable to this agent's type (global sections +
|
||||||
field: {
|
// sections linked to the agent type). Fields without a saved value come back with
|
||||||
|
// `value: null` and `valueLabel: null` so the frontend always has the admin-configured
|
||||||
|
// labels available.
|
||||||
|
private async buildFieldValuesResponse(profile: {
|
||||||
|
id: string;
|
||||||
|
agentTypeId: string | null;
|
||||||
|
}) {
|
||||||
|
const sectionFilter: Prisma.ProfileSectionWhereInput = {
|
||||||
|
isActive: true,
|
||||||
|
OR: [
|
||||||
|
{ isGlobal: true },
|
||||||
|
...(profile.agentTypeId
|
||||||
|
? [{ agentTypeSections: { some: { agentTypeId: profile.agentTypeId } } }]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = await this.prisma.profileField.findMany({
|
||||||
|
where: {
|
||||||
|
isActive: true,
|
||||||
|
section: sectionFilter,
|
||||||
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
@@ -1203,24 +1154,27 @@ export class AgentsService {
|
|||||||
fieldType: true,
|
fieldType: true,
|
||||||
options: true,
|
options: true,
|
||||||
section: {
|
section: {
|
||||||
select: {
|
select: { id: true, slug: true, name: true },
|
||||||
id: true,
|
|
||||||
slug: true,
|
|
||||||
name: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: [
|
orderBy: [
|
||||||
{ field: { section: { sortOrder: 'asc' } } },
|
{ section: { sortOrder: 'asc' } },
|
||||||
{ field: { sortOrder: 'asc' } },
|
{ sortOrder: 'asc' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const savedValues = await this.prisma.agentProfileFieldValue.findMany({
|
||||||
|
where: {
|
||||||
|
agentProfileId: profile.id,
|
||||||
|
fieldId: { in: fields.map((f) => f.id) },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const savedByFieldId = new Map(savedValues.map((v) => [v.fieldId, v]));
|
||||||
|
|
||||||
// Helper to resolve a raw value to its option label (for SELECT/RADIO/MULTI_SELECT fields)
|
// Helper to resolve a raw value to its option label (for SELECT/RADIO/MULTI_SELECT fields)
|
||||||
// Returns `null` when no matching option is found — the caller can treat those as orphans.
|
// Returns `null` when no matching option is found — the caller can treat those as orphans.
|
||||||
const resolveLabel = (rawValue: any, options: any): any => {
|
const resolveLabel = (rawValue: any, options: any): any => {
|
||||||
|
if (rawValue === null || rawValue === undefined) return rawValue;
|
||||||
if (!options || !Array.isArray(options) || options.length === 0) return rawValue;
|
if (!options || !Array.isArray(options) || options.length === 0) return rawValue;
|
||||||
const lookup = (val: any): string | null => {
|
const lookup = (val: any): string | null => {
|
||||||
const match = (options as any[]).find((o: any) => o.value === val || o.value === String(val));
|
const match = (options as any[]).find((o: any) => o.value === val || o.value === String(val));
|
||||||
@@ -1230,31 +1184,33 @@ export class AgentsService {
|
|||||||
return lookup(rawValue);
|
return lookup(rawValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Transform to a more usable format. For enumerated field types, drop any array entry
|
const transformedValues = fields.map((field) => {
|
||||||
// whose value no longer resolves to a current option so stale selections never leak through.
|
const saved = savedByFieldId.get(field.id);
|
||||||
const transformedValues = fieldValues.map((fv) => {
|
const rawValue = saved
|
||||||
const rawValue = this.extractValue(fv);
|
? this.extractValue({ ...saved, field: { fieldType: field.fieldType } })
|
||||||
let value: unknown = rawValue;
|
: null;
|
||||||
let valueLabel: unknown = resolveLabel(rawValue, fv.field.options);
|
let value: unknown = rawValue ?? null;
|
||||||
|
let valueLabel: unknown = resolveLabel(rawValue, field.options);
|
||||||
const isEnum =
|
const isEnum =
|
||||||
fv.field.fieldType === 'CHECKBOX_GROUP' ||
|
field.fieldType === 'CHECKBOX_GROUP' ||
|
||||||
fv.field.fieldType === 'MULTI_SELECT' ||
|
field.fieldType === 'MULTI_SELECT' ||
|
||||||
fv.field.fieldType === 'SELECT' ||
|
field.fieldType === 'SELECT' ||
|
||||||
fv.field.fieldType === 'RADIO';
|
field.fieldType === 'RADIO';
|
||||||
if (isEnum && Array.isArray(value) && Array.isArray(valueLabel)) {
|
if (isEnum && Array.isArray(value) && Array.isArray(valueLabel)) {
|
||||||
const pairs = (value as unknown[]).map((v, i) => ({ v, l: (valueLabel as unknown[])[i] }))
|
const pairs = (value as unknown[])
|
||||||
|
.map((v, i) => ({ v, l: (valueLabel as unknown[])[i] }))
|
||||||
.filter((p) => p.l !== null && p.l !== undefined);
|
.filter((p) => p.l !== null && p.l !== undefined);
|
||||||
value = pairs.map((p) => p.v);
|
value = pairs.map((p) => p.v);
|
||||||
valueLabel = pairs.map((p) => p.l);
|
valueLabel = pairs.map((p) => p.l);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
fieldSlug: fv.field.slug,
|
fieldSlug: field.slug,
|
||||||
fieldName: fv.field.name,
|
fieldName: field.name,
|
||||||
fieldType: fv.field.fieldType,
|
fieldType: field.fieldType,
|
||||||
sectionSlug: fv.field.section.slug,
|
sectionSlug: field.section.slug,
|
||||||
sectionName: fv.field.section.name,
|
sectionName: field.section.name,
|
||||||
value,
|
value,
|
||||||
valueLabel,
|
valueLabel: valueLabel ?? null,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user