diff --git a/src/agents/agents.service.ts b/src/agents/agents.service.ts index e26604d..9020024 100644 --- a/src/agents/agents.service.ts +++ b/src/agents/agents.service.ts @@ -112,9 +112,16 @@ export class AgentsService { throw new NotFoundException('Agent profile not found'); } + // Filter out fields that don't exist in the database schema + // serviceAreas is accepted by DTO but stored in field values, not direct column + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { serviceAreas, ...profileData } = dto as UpdateAgentProfileDto & { + serviceAreas?: string[]; + }; + const updatedProfile = await this.prisma.agentProfile.update({ where: { userId }, - data: dto, + data: profileData, include: { user: { select: { @@ -127,9 +134,50 @@ export class AgentsService { }, }); + // Sync phone to field values if phone was updated + // This keeps AgentProfile.phone and AgentProfileFieldValue in sync + if (dto.phone !== undefined) { + await this.syncPhoneToFieldValues(profile.id, dto.phone); + } + return updatedProfile; } + /** + * Sync phone from AgentProfile to AgentProfileFieldValue + * This ensures both storage locations have the same phone value + */ + private async syncPhoneToFieldValues( + agentProfileId: string, + phone: string | null, + ) { + // Find any phone-related field definitions + const phoneField = await this.prisma.profileField.findFirst({ + where: { + slug: { in: ['phone', 'phone_number', 'cell_number', 'office_number'] }, + }, + }); + + if (phoneField) { + await this.prisma.agentProfileFieldValue.upsert({ + where: { + agentProfileId_fieldId: { + agentProfileId, + fieldId: phoneField.id, + }, + }, + create: { + agentProfileId, + fieldId: phoneField.id, + textValue: phone, + }, + update: { + textValue: phone, + }, + }); + } + } + async searchAgents(dto: SearchAgentsDto) { const { search, @@ -574,6 +622,26 @@ export class AgentsService { results.push(result); } + // Sync phone from field values to AgentProfile.phone + // This keeps both storage locations in sync + const phoneSlugs = ['phone', 'phone_number', 'cell_number', 'office_number']; + const phoneFieldValue = dto.fieldValues.find((fv) => + phoneSlugs.includes(fv.fieldSlug), + ); + + if (phoneFieldValue !== undefined) { + const phoneValue = phoneFieldValue.value; + await this.prisma.agentProfile.update({ + where: { id: profile.id }, + data: { + phone: + phoneValue === '' || phoneValue === null + ? null + : String(phoneValue), + }, + }); + } + return { message: 'Field values saved successfully', savedCount: results.length, diff --git a/src/agents/dto/create-agent-profile.dto.ts b/src/agents/dto/create-agent-profile.dto.ts index 4ffe618..ba5d9aa 100644 --- a/src/agents/dto/create-agent-profile.dto.ts +++ b/src/agents/dto/create-agent-profile.dto.ts @@ -1,4 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; import { IsString, IsOptional, @@ -6,10 +7,12 @@ import { IsUrl, IsUUID, IsBoolean, + IsArray, MinLength, MaxLength, Min, Max, + ValidateIf, } from 'class-validator'; export class CreateAgentProfileDto { @@ -33,11 +36,13 @@ export class CreateAgentProfileDto { @MaxLength(100) lastName: string; - @ApiPropertyOptional({ example: '+1234567890' }) + @ApiPropertyOptional({ example: '+1234567890', nullable: true }) @IsOptional() + @ValidateIf((o) => o.phone !== null) @IsString() @MaxLength(20) - phone?: string; + @Transform(({ value }) => (value === '' ? null : value)) + phone?: string | null; @ApiPropertyOptional({ example: 'Experienced real estate agent...' }) @IsOptional() @@ -140,4 +145,13 @@ export class CreateAgentProfileDto { @IsOptional() @IsBoolean() isAvailable?: boolean; + + @ApiPropertyOptional({ + example: ['Los Angeles', 'San Francisco'], + description: 'Service areas the agent covers', + }) + @IsOptional() + @IsArray() + @IsString({ each: true }) + serviceAreas?: string[]; }