diff --git a/src/agents/agents.service.ts b/src/agents/agents.service.ts index 48cd8c3..5c591be 100644 --- a/src/agents/agents.service.ts +++ b/src/agents/agents.service.ts @@ -1099,128 +1099,82 @@ 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: { - select: { - id: true, - slug: true, - name: true, - fieldType: true, - options: true, - section: { - select: { - id: true, - slug: true, - name: true, - }, - }, - }, + 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, + name: true, + fieldType: true, + options: true, + section: { + 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, }; });