From 48ccbdafc89dd95f7e7ab2256b2468206b32c3dd Mon Sep 17 00:00:00 2001 From: TradeIn Deploy Date: Fri, 22 May 2026 11:15:15 +0500 Subject: [PATCH] =?UTF-8?q?feat(tradein):=20CRM-=D0=BF=D0=BE=D0=BB=D1=8F?= =?UTF-8?q?=20=D0=B2=20=D1=84=D0=BE=D1=80=D0=BC=D0=B5=20=D0=BE=D1=86=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=B8=20(#395)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Доп. поля для трейд-ин менеджера: тип собственности, ипотека/ обременение, имя и телефон клиента. На расчёт оценки не влияют — сохраняются в trade_in_estimates вместе с записью. - data/sql/008_crm_fields.sql — 4 колонки в trade_in_estimates. - TradeInEstimateInput + estimator INSERT — приём и сохранение. - EstimateForm — секция «CRM — для менеджера» (4 поля). Closes #395 --- tradein-mvp/backend/app/schemas/trade_in.py | 5 ++ tradein-mvp/backend/app/services/estimator.py | 6 ++ .../backend/data/sql/008_crm_fields.sql | 16 +++++ .../src/components/trade-in/EstimateForm.tsx | 69 +++++++++++++++++++ tradein-mvp/frontend/src/types/trade-in.ts | 5 ++ 5 files changed, 101 insertions(+) create mode 100644 tradein-mvp/backend/data/sql/008_crm_fields.sql diff --git a/tradein-mvp/backend/app/schemas/trade_in.py b/tradein-mvp/backend/app/schemas/trade_in.py index fff895ee..b0e70835 100644 --- a/tradein-mvp/backend/app/schemas/trade_in.py +++ b/tradein-mvp/backend/app/schemas/trade_in.py @@ -22,6 +22,11 @@ class TradeInEstimateInput(BaseModel): house_type: Literal["panel", "brick", "monolith", "monolith_brick", "other"] | None = None repair_state: Literal["needs_repair", "standard", "good", "excellent"] | None = None has_balcony: bool | None = None + # CRM-поля (#395) — операционные, на расчёт оценки не влияют + ownership_type: str | None = Field(default=None, max_length=100) + has_mortgage: bool | None = None + client_name: str | None = Field(default=None, max_length=200) + client_phone: str | None = Field(default=None, max_length=50) class AnalogLot(BaseModel): diff --git a/tradein-mvp/backend/app/services/estimator.py b/tradein-mvp/backend/app/services/estimator.py index f4a78b19..743a333b 100644 --- a/tradein-mvp/backend/app/services/estimator.py +++ b/tradein-mvp/backend/app/services/estimator.py @@ -195,6 +195,7 @@ async def estimate_quality( id, address, lat, lon, area_m2, rooms, floor, total_floors, year_built, house_type, repair_state, has_balcony, + ownership_type, has_mortgage, client_name, client_phone, median_price, range_low, range_high, median_price_per_m2, confidence, confidence_explanation, n_analogs, analogs, actual_deals, @@ -205,6 +206,7 @@ async def estimate_quality( :address, :lat, :lon, :area, :rooms, :floor, :total_floors, :year_built, :house_type, :repair_state, :has_balcony, + :ownership_type, :has_mortgage, :client_name, :client_phone, :median_price, :range_low, :range_high, :median_ppm2, :confidence, :explanation, :n_analogs, CAST(:analogs_json AS jsonb), @@ -228,6 +230,10 @@ async def estimate_quality( "house_type": target_house_type, "repair_state": payload.repair_state, "has_balcony": payload.has_balcony, + "ownership_type": payload.ownership_type, + "has_mortgage": payload.has_mortgage, + "client_name": payload.client_name, + "client_phone": payload.client_phone, "median_price": median_price, "range_low": range_low, "range_high": range_high, diff --git a/tradein-mvp/backend/data/sql/008_crm_fields.sql b/tradein-mvp/backend/data/sql/008_crm_fields.sql new file mode 100644 index 00000000..b58c72b6 --- /dev/null +++ b/tradein-mvp/backend/data/sql/008_crm_fields.sql @@ -0,0 +1,16 @@ +-- Trade-In MVP — CRM-поля оценки (#395). +-- Операционные поля трейд-ин менеджера: тип собственности, ипотека/обременение, +-- контакт клиента. На расчёт оценки не влияют — хранятся вместе с записью. + +BEGIN; + +ALTER TABLE trade_in_estimates + ADD COLUMN IF NOT EXISTS ownership_type text, + ADD COLUMN IF NOT EXISTS has_mortgage boolean, + ADD COLUMN IF NOT EXISTS client_name text, + ADD COLUMN IF NOT EXISTS client_phone text; + +COMMENT ON COLUMN trade_in_estimates.client_phone IS + '#395 — контакт клиента (CRM). PII — доступ только у трейд-ин менеджера.'; + +COMMIT; diff --git a/tradein-mvp/frontend/src/components/trade-in/EstimateForm.tsx b/tradein-mvp/frontend/src/components/trade-in/EstimateForm.tsx index fe232b20..384f6f6a 100644 --- a/tradein-mvp/frontend/src/components/trade-in/EstimateForm.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/EstimateForm.tsx @@ -25,6 +25,10 @@ interface FormState { house_type: string; repair_state: string; has_balcony: boolean; + ownership_type: string; + has_mortgage: boolean; + client_name: string; + client_phone: string; } const INITIAL: FormState = { @@ -37,6 +41,10 @@ const INITIAL: FormState = { house_type: "", repair_state: "", has_balcony: false, + ownership_type: "", + has_mortgage: false, + client_name: "", + client_phone: "", }; function validate(f: FormState): string | null { @@ -69,6 +77,10 @@ function toPayload(f: FormState): TradeInEstimateInput { if (f.year_built !== "") out.year_built = Number(f.year_built); if (f.house_type) out.house_type = f.house_type as HouseType; if (f.repair_state) out.repair_state = f.repair_state as RepairState; + if (f.ownership_type) out.ownership_type = f.ownership_type; + if (f.has_mortgage) out.has_mortgage = true; + if (f.client_name.trim()) out.client_name = f.client_name.trim(); + if (f.client_phone.trim()) out.client_phone = f.client_phone.trim(); return out; } @@ -271,6 +283,63 @@ export function EstimateForm({ onSubmit, isPending, error }: Props) { + {/* CRM — для менеджера (#395) */} +
+
+ CRM — для менеджера +
+
+ + +
+
+ +
+
+
+ + +
+
+ + +
+
+
+ {touched && valError && (