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) {
|
||||
// Get agent profile
|
||||
const profile = await this.prisma.agentProfile.findUnique({
|
||||
where: { userId },
|
||||
select: { id: true, agentTypeId: true },
|
||||
});
|
||||
|
||||
if (!profile) {
|
||||
throw new NotFoundException('Agent profile not found');
|
||||
}
|
||||
|
||||
// Get all field values for this profile, ordered by admin-defined section + field sort order
|
||||
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,
|
||||
};
|
||||
return this.buildFieldValuesResponse(profile);
|
||||
}
|
||||
|
||||
async getFieldValuesByAgentId(agentId: string) {
|
||||
// Get agent profile by ID
|
||||
const profile = await this.prisma.agentProfile.findUnique({
|
||||
where: { id: agentId },
|
||||
select: { id: true, agentTypeId: true },
|
||||
});
|
||||
|
||||
if (!profile) {
|
||||
throw new NotFoundException('Agent profile not found');
|
||||
}
|
||||
|
||||
// Get all field values for this profile, ordered by admin-defined section + field sort order
|
||||
const fieldValues = await this.prisma.agentProfileFieldValue.findMany({
|
||||
where: { agentProfileId: profile.id },
|
||||
include: {
|
||||
field: {
|
||||
return this.buildFieldValuesResponse(profile);
|
||||
}
|
||||
|
||||
// Returns one entry per field applicable to this agent's type (global sections +
|
||||
// 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: {
|
||||
id: true,
|
||||
slug: true,
|
||||
@@ -1203,24 +1154,27 @@ export class AgentsService {
|
||||
fieldType: true,
|
||||
options: true,
|
||||
section: {
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: { id: true, slug: true, name: true },
|
||||
},
|
||||
},
|
||||
orderBy: [
|
||||
{ field: { section: { sortOrder: 'asc' } } },
|
||||
{ field: { sortOrder: 'asc' } },
|
||||
{ section: { 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)
|
||||
// Returns `null` when no matching option is found — the caller can treat those as orphans.
|
||||
const resolveLabel = (rawValue: any, options: any): any => {
|
||||
if (rawValue === null || rawValue === undefined) return rawValue;
|
||||
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));
|
||||
@@ -1230,31 +1184,33 @@ export class AgentsService {
|
||||
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 transformedValues = fields.map((field) => {
|
||||
const saved = savedByFieldId.get(field.id);
|
||||
const rawValue = saved
|
||||
? this.extractValue({ ...saved, field: { fieldType: field.fieldType } })
|
||||
: null;
|
||||
let value: unknown = rawValue ?? null;
|
||||
let valueLabel: unknown = resolveLabel(rawValue, field.options);
|
||||
const isEnum =
|
||||
fv.field.fieldType === 'CHECKBOX_GROUP' ||
|
||||
fv.field.fieldType === 'MULTI_SELECT' ||
|
||||
fv.field.fieldType === 'SELECT' ||
|
||||
fv.field.fieldType === 'RADIO';
|
||||
field.fieldType === 'CHECKBOX_GROUP' ||
|
||||
field.fieldType === 'MULTI_SELECT' ||
|
||||
field.fieldType === 'SELECT' ||
|
||||
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] }))
|
||||
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,
|
||||
fieldSlug: field.slug,
|
||||
fieldName: field.name,
|
||||
fieldType: field.fieldType,
|
||||
sectionSlug: field.section.slug,
|
||||
sectionName: field.section.name,
|
||||
value,
|
||||
valueLabel,
|
||||
valueLabel: valueLabel ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user