diff --git a/apps/api/prisma/migrations/20261003120000_person_lead_address_instagram/migration.sql b/apps/api/prisma/migrations/20261003120000_person_lead_address_instagram/migration.sql new file mode 100644 index 0000000..c0b76de --- /dev/null +++ b/apps/api/prisma/migrations/20261003120000_person_lead_address_instagram/migration.sql @@ -0,0 +1,7 @@ +-- AlterTable +ALTER TABLE "Person" ADD COLUMN IF NOT EXISTS "instagram" TEXT; +ALTER TABLE "Person" ADD COLUMN IF NOT EXISTS "addresses" JSONB NOT NULL DEFAULT '[]'; + +-- AlterTable +ALTER TABLE "Lead" ADD COLUMN IF NOT EXISTS "instagram" TEXT; +ALTER TABLE "Lead" ADD COLUMN IF NOT EXISTS "addresses" JSONB NOT NULL DEFAULT '[]'; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 8c7e421..ef01db9 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -432,8 +432,12 @@ model Person { position String? /// آدرس وب‌سایت website String? + /// پیج اینستاگرام + instagram String? /// نام معرف referrer String? + /// لیست آدرس‌ها (JSON array of strings) + addresses Json @default("[]") notes String? mood PersonMood @default(newcomer) tags String[] @default([]) @@ -2064,6 +2068,10 @@ model Lead { phone String? city String? region String? + /// پیج اینستاگرام + instagram String? + /// لیست آدرس‌ها (JSON array of strings) + addresses Json @default("[]") /// صنف trade String? source String? diff --git a/apps/api/src/common/custom-fields.ts b/apps/api/src/common/custom-fields.ts index bb72c95..654ee28 100644 --- a/apps/api/src/common/custom-fields.ts +++ b/apps/api/src/common/custom-fields.ts @@ -12,3 +12,23 @@ export function toJsonCustomFields( ): Prisma.InputJsonValue { return (value ?? {}) as Prisma.InputJsonValue; } + +export function asAddresses(value: unknown): string[] { + if (!Array.isArray(value)) return []; + const out: string[] = []; + for (const item of value) { + const text = String(item ?? '').trim(); + if (text && !out.includes(text)) out.push(text); + if (out.length >= 20) break; + } + return out; +} + +export function toJsonAddresses(value?: string[] | null): Prisma.InputJsonValue { + return asAddresses(value ?? []); +} + +/** Collect address-like cells from an excel row into a unique list. */ +export function collectAddresses(...parts: Array): string[] { + return asAddresses(parts.map((p) => (p ?? '').trim()).filter(Boolean)); +} diff --git a/apps/api/src/contacts/contacts-excel.service.ts b/apps/api/src/contacts/contacts-excel.service.ts index 7896fbf..e957fe1 100644 --- a/apps/api/src/contacts/contacts-excel.service.ts +++ b/apps/api/src/contacts/contacts-excel.service.ts @@ -4,9 +4,13 @@ import { PERSON_MOOD_LABELS, PERSON_MOODS } from '@mento/shared'; import { PrismaService } from '../prisma/prisma.service'; import type { Actor } from '../common/record-scope'; import { ownerWhere, resolveOwnerId } from '../common/record-scope'; +import { + collectAddresses, +} from '../common/custom-fields'; import { formatJalaliDate, parseExcelMapping, + parseJalaliDateTime, previewExcel, readMappedRows, writeXlsx, @@ -73,8 +77,17 @@ const PERSON_ALIASES: Record = { گروهمشتری: 'group', آدرس: 'address', آدرس1: 'address', + آدرساصلی: 'address', + address: 'address', + address2: 'address2', + آدرس۲: 'address2', + آدرس2: 'address2', + آدرس_1: 'address2', + آدرسدوم: 'address2', آدرسمنزل: 'homeAddress', آدرمنزل: 'homeAddress', + آدرسخانه: 'homeAddress', + homeaddress: 'homeAddress', کدپستی: 'postalCode', کدپستی1: 'postalCode', استانفیلدپیشفرض: 'province', @@ -93,8 +106,13 @@ const PERSON_ALIASES: Record = { type: '_type', _type: '_type', آیدیاینستاگرام: 'instagram', + اینستاگرام: 'instagram', + پیجاینستاگرام: 'instagram', + instagram: 'instagram', معرف: 'referrer', سایتهایمشتری: 'website', + تاریخثبت: 'registeredAt', + registeredat: 'registeredAt', شمارهفکس: 'fax', تلفن2: 'phone2', تلفنهایدیگرمشتری: 'otherPhones', @@ -128,6 +146,11 @@ const PERSON_FIELDS = [ { key: 'position', label: 'سمت' }, { key: 'companyName', label: 'شرکت' }, { key: 'ownerName', label: 'کارشناس فروش' }, + { key: 'address', label: 'آدرس' }, + { key: 'address2', label: 'آدرس ۲' }, + { key: 'homeAddress', label: 'آدرس منزل' }, + { key: 'instagram', label: 'اینستاگرام' }, + { key: 'registeredAt', label: 'تاریخ ثبت' }, { key: 'notes', label: 'یادداشت' }, { key: 'mood', label: 'وضعیت مشتری' }, ]; @@ -218,6 +241,7 @@ const CUSTOM_KEYS = [ 'province', 'city', 'address', + 'address2', 'nationalId', 'birthDate', 'group', @@ -279,20 +303,20 @@ export class ContactsExcelService { birthDate: extra.birthDate ?? '', mood: extra.group || PERSON_MOOD_LABELS[p.mood || 'newcomer'], otherPhones: extra.otherPhones ?? '', - address: extra.address ?? '', - website: extra.website ?? '', + address: p.addresses[0] ?? extra.address ?? '', + website: p.website ?? extra.website ?? '', otherEmails: extra.otherEmails ?? '', position2: p.position ?? extra.position ?? '', city2: extra.city ?? '', code2: extra.customerCode ?? '', group2: extra.group ?? '', - address2: extra.address ?? '', + address2: p.addresses[1] ?? extra.address ?? '', phone2: extra.phone2 ?? p.phone ?? '', postalCode2: extra.postalCode ?? '', - referrer: extra.referrer ?? '', - homeAddress: extra.homeAddress ?? '', + referrer: extra.referrer ?? p.referrer ?? '', + homeAddress: p.addresses[2] ?? extra.homeAddress ?? '', birthDate2: extra.birthDate ?? '', - instagram: extra.instagram ?? '', + instagram: p.instagram ?? extra.instagram ?? '', group: extra.group ?? '', colleagueContact: extra.colleagueContact ?? '', previousCompany: extra.previousCompany ?? '', @@ -388,10 +412,33 @@ export class ContactsExcelService { const customFields: Record = {}; for (const key of CUSTOM_KEYS) { + if ( + key === 'address' || + key === 'address2' || + key === 'homeAddress' || + key === 'instagram' || + key === 'website' + ) { + continue; + } const value = row[key]?.trim(); if (value) customFields[key] = value; } + const addresses = collectAddresses( + row.address, + row.address2, + row.homeAddress, + ); + const registeredAt = + parseJalaliDateTime(row.registeredAt) ?? + (() => { + const raw = row.registeredAt?.trim(); + if (!raw) return null; + const d = new Date(raw); + return Number.isNaN(d.getTime()) ? null : d; + })(); + try { await this.people.create(actor, { firstName: names.firstName, @@ -400,11 +447,15 @@ export class ContactsExcelService { phone: row.phone || row.phone2 || undefined, mobile: row.mobile || undefined, position: row.position || undefined, + website: row.website || undefined, + instagram: row.instagram || undefined, + addresses, notes: row.notes || undefined, mood: parseMood(row.mood) ?? parseMood(row.group), companyId, ownerId: resolveOwnerId(actor, ownerId ?? null), customFields, + registeredAt: registeredAt?.toISOString() ?? null, }); result.created += 1; if (ownerMissing) { diff --git a/apps/api/src/contacts/dto.ts b/apps/api/src/contacts/dto.ts index bbe2884..fa2661d 100644 --- a/apps/api/src/contacts/dto.ts +++ b/apps/api/src/contacts/dto.ts @@ -181,11 +181,21 @@ export class CreatePersonDto { @MaxLength(300) website?: string; + @IsOptional() + @IsString() + @MaxLength(200) + instagram?: string; + @IsOptional() @IsString() @MaxLength(120) referrer?: string; + @IsOptional() + @IsArray() + @IsString({ each: true }) + addresses?: string[]; + @IsOptional() @IsString() @MaxLength(2000) @@ -213,6 +223,11 @@ export class CreatePersonDto { @ValidateIf((_, v) => v !== '' && v != null) @IsString() ownerId?: string | null; + + /** فقط برای ایمپورت اکسل — تاریخ ثبت مبدأ */ + @IsOptional() + @IsString() + registeredAt?: string | null; } export class UpdatePersonDto { @@ -251,11 +266,21 @@ export class UpdatePersonDto { @MaxLength(300) website?: string; + @IsOptional() + @IsString() + @MaxLength(200) + instagram?: string; + @IsOptional() @IsString() @MaxLength(120) referrer?: string; + @IsOptional() + @IsArray() + @IsString({ each: true }) + addresses?: string[]; + @IsOptional() @IsString() @MaxLength(2000) diff --git a/apps/api/src/contacts/people.service.ts b/apps/api/src/contacts/people.service.ts index 84f6774..177dd36 100644 --- a/apps/api/src/contacts/people.service.ts +++ b/apps/api/src/contacts/people.service.ts @@ -15,7 +15,9 @@ import { PrismaService } from '../prisma/prisma.service'; import { CompaniesService } from './companies.service'; import { emptyToNull, normalizeEmail, normalizePhone } from './normalize'; import { + asAddresses, asCustomFields, + toJsonAddresses, toJsonCustomFields, } from '../common/custom-fields'; import { @@ -47,7 +49,9 @@ type PersonRow = { mobile: string | null; position: string | null; website: string | null; + instagram: string | null; referrer: string | null; + addresses: unknown; notes: string | null; mood: PersonMood; tags: string[]; @@ -138,6 +142,7 @@ export class PeopleService { await this.companies.ensureInOrg(actor.organizationId, companyId, actor); } + const registeredAt = dto.registeredAt ? new Date(dto.registeredAt) : null; const data: Prisma.PersonUncheckedCreateInput = { organizationId: actor.organizationId, firstName: (dto.firstName ?? '').trim(), @@ -147,13 +152,18 @@ export class PeopleService { mobile: normalizePhone(dto.mobile), position: emptyToNull(dto.position), website: emptyToNull(dto.website), + instagram: emptyToNull(dto.instagram), referrer: emptyToNull(dto.referrer), + addresses: toJsonAddresses(dto.addresses), notes: emptyToNull(dto.notes), mood: dto.mood ?? 'newcomer', tags: normalizeTags(dto.tags), companyId, ownerId: resolveOwnerId(actor, dto.ownerId), customFields: toJsonCustomFields(dto.customFields), + ...(registeredAt && !Number.isNaN(registeredAt.getTime()) + ? { createdAt: registeredAt } + : {}), }; await this.assertNoDuplicate(actor.organizationId, data); @@ -202,7 +212,9 @@ export class PeopleService { if ('mobile' in dto) data.mobile = normalizePhone(dto.mobile); if ('position' in dto) data.position = emptyToNull(dto.position); if ('website' in dto) data.website = emptyToNull(dto.website); + if ('instagram' in dto) data.instagram = emptyToNull(dto.instagram); if ('referrer' in dto) data.referrer = emptyToNull(dto.referrer); + if ('addresses' in dto) data.addresses = toJsonAddresses(dto.addresses); if ('notes' in dto) data.notes = emptyToNull(dto.notes); if (dto.mood !== undefined) data.mood = dto.mood; if ('tags' in dto) data.tags = normalizeTags(dto.tags); @@ -295,7 +307,18 @@ export class PeopleService { mobile: row.mobile, position: row.position, website: row.website, + instagram: row.instagram, referrer: row.referrer, + addresses: (() => { + const fromCol = asAddresses(row.addresses); + if (fromCol.length) return fromCol; + const extra = asCustomFields(row.customFields); + return asAddresses( + [extra.address, extra.homeAddress, extra.address2] + .map((v) => (typeof v === 'string' ? v : '')) + .filter(Boolean), + ); + })(), notes: row.notes, mood: row.mood, tags: row.tags ?? [], diff --git a/apps/api/src/leads/dto.ts b/apps/api/src/leads/dto.ts index 81cd7c9..2264510 100644 --- a/apps/api/src/leads/dto.ts +++ b/apps/api/src/leads/dto.ts @@ -21,6 +21,8 @@ export class CreateLeadDto { @IsOptional() @IsString() phone?: string; @IsOptional() @IsString() city?: string; @IsOptional() @IsString() region?: string; + @IsOptional() @IsString() instagram?: string; + @IsOptional() @IsArray() @IsString({ each: true }) addresses?: string[]; @IsOptional() @IsString() trade?: string; @IsOptional() @IsString() source?: string; @IsOptional() @IsString() notes?: string; @@ -28,6 +30,8 @@ export class CreateLeadDto { @IsOptional() @IsString() stageId?: string; @IsOptional() @IsString() assigneeId?: string | null; @IsOptional() @IsString() nextFollowUpAt?: string | null; + /** فقط برای ایمپورت اکسل — تاریخ ثبت مبدأ */ + @IsOptional() @IsString() registeredAt?: string | null; } export class UpdateLeadDto { @@ -37,6 +41,8 @@ export class UpdateLeadDto { @IsOptional() @IsString() phone?: string | null; @IsOptional() @IsString() city?: string | null; @IsOptional() @IsString() region?: string | null; + @IsOptional() @IsString() instagram?: string | null; + @IsOptional() @IsArray() @IsString({ each: true }) addresses?: string[]; @IsOptional() @IsString() trade?: string | null; @IsOptional() @IsString() source?: string | null; @IsOptional() @IsString() notes?: string | null; diff --git a/apps/api/src/leads/leads-import.service.ts b/apps/api/src/leads/leads-import.service.ts index 2ce998a..0ae1e8a 100644 --- a/apps/api/src/leads/leads-import.service.ts +++ b/apps/api/src/leads/leads-import.service.ts @@ -5,6 +5,7 @@ import type { LeadImportJobDto } from '@mento/shared'; import { PrismaService } from '../prisma/prisma.service'; import { parseExcelMapping, + parseJalaliDateTime, previewExcel, readMappedRows, type ExcelFieldOption, @@ -40,6 +41,23 @@ const LEAD_ALIASES: Record = { region: 'region', منطقه: 'region', محله: 'region', + address: 'address', + آدرس: 'address', + آدرساصلی: 'address', + address2: 'address2', + آدرس۲: 'address2', + آدرس2: 'address2', + آدرسدوم: 'address2', + homeaddress: 'homeAddress', + آدرسمنزل: 'homeAddress', + آدرسخانه: 'homeAddress', + instagram: 'instagram', + اینستاگرام: 'instagram', + پیجاینستاگرام: 'instagram', + آیدیاینستاگرام: 'instagram', + registeredat: 'registeredAt', + تاریخثبت: 'registeredAt', + تاریخثبتمشتری: 'registeredAt', trade: 'trade', صنف: 'trade', رسته: 'trade', @@ -58,6 +76,11 @@ const LEAD_FIELDS: ExcelFieldOption[] = [ { key: 'phone', label: 'تلفن' }, { key: 'city', label: 'شهر' }, { key: 'region', label: 'منطقه' }, + { key: 'address', label: 'آدرس' }, + { key: 'address2', label: 'آدرس ۲' }, + { key: 'homeAddress', label: 'آدرس منزل' }, + { key: 'instagram', label: 'اینستاگرام' }, + { key: 'registeredAt', label: 'تاریخ ثبت' }, { key: 'trade', label: 'صنف' }, { key: 'source', label: 'منبع' }, { key: 'notes', label: 'توضیحات' }, @@ -237,6 +260,19 @@ export class LeadsImportService { } return ''; }; + const registeredRaw = get( + 'registeredAt', + 'تاریخ ثبت', + 'تاریخ ثبت مشتری', + 'registeredat', + ); + const registeredAt = + parseJalaliDateTime(registeredRaw)?.toISOString() ?? + (() => { + if (!registeredRaw) return ''; + const d = new Date(registeredRaw); + return Number.isNaN(d.getTime()) ? '' : d.toISOString(); + })(); return { title: get('title', 'نام', 'نام فروشگاه', 'فروشگاه', 'shop', 'name'), contactName: get('contactName', 'مالک', 'مسئول', 'نام مالک', 'owner'), @@ -244,6 +280,11 @@ export class LeadsImportService { phone: get('phone', 'تلفن'), city: get('city', 'شهر'), region: get('region', 'منطقه', 'محله'), + address: get('address', 'آدرس', 'آدرس اصلی'), + address2: get('address2', 'آدرس ۲', 'آدرس2', 'آدرس دوم'), + homeAddress: get('homeAddress', 'آدرس منزل', 'آدرس خانه'), + instagram: get('instagram', 'اینستاگرام', 'پیج اینستاگرام', 'آی دی اینستاگرام'), + registeredAt, trade: get('trade', 'صنف', 'رسته'), source: get('source', 'منبع', 'منبع لید'), notes: get('notes', 'توضیحات', 'یادداشت'), diff --git a/apps/api/src/leads/leads.service.ts b/apps/api/src/leads/leads.service.ts index 7b3d4dc..029d0a5 100644 --- a/apps/api/src/leads/leads.service.ts +++ b/apps/api/src/leads/leads.service.ts @@ -20,6 +20,11 @@ import { type Actor, elevated, } from '../common/record-scope'; +import { + asAddresses, + collectAddresses, + toJsonAddresses, +} from '../common/custom-fields'; import { emptyToNull, normalizePhone } from '../contacts/normalize'; import { LocalFileStorage } from '../storage/local-file.storage'; import { LeadStagesService } from './lead-stages.service'; @@ -238,6 +243,8 @@ export class LeadsService { phone?: string | null; city?: string | null; region?: string | null; + instagram?: string | null; + addresses?: string[]; trade?: string | null; source?: string | null; notes?: string | null; @@ -245,6 +252,7 @@ export class LeadsService { stageId?: string; assigneeId?: string | null; nextFollowUpAt?: string | null; + registeredAt?: string | null; }, ): Promise { await this.stages.ensureDefaults(actor.organizationId); @@ -271,6 +279,7 @@ export class LeadsService { assigneeId = actor.userId; } + const registeredAt = dto.registeredAt ? new Date(dto.registeredAt) : null; const row = await this.prisma.lead.create({ data: { organizationId: actor.organizationId, @@ -280,6 +289,8 @@ export class LeadsService { phone: emptyToNull(dto.phone), city: emptyToNull(dto.city), region: emptyToNull(dto.region), + instagram: emptyToNull(dto.instagram), + addresses: toJsonAddresses(dto.addresses), trade: emptyToNull(dto.trade), source: emptyToNull(dto.source), notes: emptyToNull(dto.notes), @@ -287,6 +298,9 @@ export class LeadsService { dto.estimatedValue != null ? new Prisma.Decimal(dto.estimatedValue) : null, + ...(registeredAt && !Number.isNaN(registeredAt.getTime()) + ? { createdAt: registeredAt } + : {}), stageId, assigneeId, createdById: actor.userId, @@ -351,6 +365,12 @@ export class LeadsService { ...(dto.region !== undefined ? { region: emptyToNull(dto.region as string) } : {}), + ...(dto.instagram !== undefined + ? { instagram: emptyToNull(dto.instagram as string) } + : {}), + ...(dto.addresses !== undefined + ? { addresses: toJsonAddresses(dto.addresses) } + : {}), ...(dto.trade !== undefined ? { trade: emptyToNull(dto.trade as string) } : {}), @@ -717,6 +737,8 @@ export class LeadsService { lastName, mobile: lead.mobile, phone: lead.phone, + instagram: lead.instagram, + addresses: toJsonAddresses(asAddresses(lead.addresses)), notes: [ lead.notes, lead.city ? `شهر: ${lead.city}` : null, @@ -727,6 +749,7 @@ export class LeadsService { .filter(Boolean) .join('\n'), ownerId: lead.assigneeId ?? actor.userId, + createdAt: lead.createdAt, }, }); createdPerson = true; @@ -884,6 +907,11 @@ export class LeadsService { phone?: string; city?: string; region?: string; + address?: string; + address2?: string; + homeAddress?: string; + instagram?: string; + registeredAt?: string; trade?: string; source?: string; notes?: string; @@ -910,6 +938,7 @@ export class LeadsService { if (existingPerson) return 'duplicate'; try { + const registeredAt = row.registeredAt ? new Date(row.registeredAt) : null; const lead = await this.prisma.lead.create({ data: { organizationId, @@ -919,11 +948,18 @@ export class LeadsService { phone: emptyToNull(row.phone), city: emptyToNull(row.city), region: emptyToNull(row.region), + instagram: emptyToNull(row.instagram), + addresses: toJsonAddresses( + collectAddresses(row.address, row.address2, row.homeAddress), + ), trade: emptyToNull(row.trade), source: emptyToNull(row.source) ?? 'ایمپورت اکسل', notes: emptyToNull(row.notes), stageId: defaultStageId, createdById, + ...(registeredAt && !Number.isNaN(registeredAt.getTime()) + ? { createdAt: registeredAt } + : {}), }, }); await this.prisma.leadEvent.create({ @@ -990,6 +1026,8 @@ export class LeadsService { phone: row.phone, city: row.city, region: row.region, + instagram: row.instagram, + addresses: asAddresses(row.addresses), trade: row.trade, source: row.source, notes: row.notes, diff --git a/apps/web/src/components/ExcelImportFlow.vue b/apps/web/src/components/ExcelImportFlow.vue index 26e768a..e351ad5 100644 --- a/apps/web/src/components/ExcelImportFlow.vue +++ b/apps/web/src/components/ExcelImportFlow.vue @@ -3,7 +3,7 @@ import { computed, reactive, ref } from 'vue'; import axios from 'axios'; import { api } from '@/lib/api'; import type { ExcelPreviewResult, ImportContactsResult } from '@mento/shared'; -import { UiButton, UiModal, UiSelect } from '@/components/ui'; +import { UiBusyOverlay, UiButton, UiModal, UiSelect } from '@/components/ui'; const props = defineProps<{ previewPath: string; @@ -23,6 +23,7 @@ const pending = ref(null); const preview = ref(null); const mapping = reactive>({}); const submitting = ref(false); +const phase = ref<'idle' | 'preview' | 'import'>('idle'); const requiredKeys = computed( () => preview.value?.fields.filter((f) => f.required).map((f) => f.key) ?? [], @@ -32,6 +33,11 @@ const mappedFields = computed(() => new Set(Object.values(mapping).filter(Boolea const canImport = computed(() => requiredKeys.value.every((key) => mappedFields.value.has(key))); +const busyOpen = computed(() => submitting.value && phase.value !== 'idle'); +const busyMessage = computed(() => + phase.value === 'import' ? 'در حال ورود اطلاعات از اکسل…' : 'در حال خواندن و بررسی فایل اکسل…', +); + function pick() { if (props.disabled || submitting.value) return; input.value?.click(); @@ -41,10 +47,12 @@ function reset() { pending.value = null; preview.value = null; submitting.value = false; + phase.value = 'idle'; for (const key of Object.keys(mapping)) delete mapping[key]; } function closeMapper() { + if (submitting.value) return; reset(); emit('settled'); } @@ -59,6 +67,7 @@ function setColumnField(header: string, field: string) { } async function postImport(file: File, map?: Record) { + phase.value = 'import'; const form = new FormData(); form.append('file', file); if (map && Object.values(map).some(Boolean)) { @@ -89,6 +98,7 @@ async function onFile(ev: Event) { if (!file) return; emit('start'); submitting.value = true; + phase.value = 'preview'; try { const form = new FormData(); form.append('file', file); @@ -106,42 +116,51 @@ async function onFile(ev: Event) { for (const col of data.columns) { mapping[col.header] = data.suggested[col.header] ?? ''; } + phase.value = 'idle'; + submitting.value = false; + // Allow parent toolbar while user maps columns. + emit('settled'); } catch (e) { emit('fail', apiMessage(e, 'خواندن فایل اکسل ناموفق بود')); reset(); emit('settled'); - } finally { - submitting.value = false; } } async function confirmMapping() { - if (!pending.value || !canImport.value) return; + if (!pending.value || !canImport.value || submitting.value) return; + emit('start'); submitting.value = true; + phase.value = 'import'; try { emit('done', await postImport(pending.value, { ...mapping })); reset(); emit('settled'); } catch (e) { emit('fail', apiMessage(e, 'ایمپورت ناموفق بود')); + phase.value = 'idle'; submitting.value = false; + emit('settled'); } }