Spaces:
Running
Running
fixed issue
Browse files- app/services/validation.py +14 -0
- docs.md +83 -0
app/services/validation.py
CHANGED
|
@@ -328,10 +328,24 @@ def is_plausible_phone(raw: str) -> bool:
|
|
| 328 |
phone number at all: a character that isn't a digit or common phone punctuation, or too few digits.
|
| 329 |
Empty is plausible (a lead is allowed to have no phone at all) -- only a GIVEN garbage value should
|
| 330 |
block a save; see CreateLeadInput/UpdateLeadInput's own field_validators.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
"""
|
| 332 |
value = (raw or "").strip()
|
| 333 |
if not value:
|
| 334 |
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
if any(ch not in "0123456789+()-. " for ch in value):
|
| 336 |
return False
|
| 337 |
return sum(ch.isdigit() for ch in value) >= 7
|
|
|
|
| 328 |
phone number at all: a character that isn't a digit or common phone punctuation, or too few digits.
|
| 329 |
Empty is plausible (a lead is allowed to have no phone at all) -- only a GIVEN garbage value should
|
| 330 |
block a save; see CreateLeadInput/UpdateLeadInput's own field_validators.
|
| 331 |
+
|
| 332 |
+
BUGFIX (2026-10-29): contact_phone has ALWAYS been allowed to carry several comma-joined numbers for
|
| 333 |
+
one lead -- see call_timezone()'s own `.split(",")[0]`, crud/contacts.py, lead_engine.py's
|
| 334 |
+
_all_phones(), and the frontend's PhoneNumberList, all of which already expect this. This function
|
| 335 |
+
didn't account for that at all (no comma in its character whitelist), so any lead with more than one
|
| 336 |
+
number on file -- a legitimate, pre-existing, everyday case -- failed validation and blocked Save the
|
| 337 |
+
moment entry (6)'s blocking field_validator shipped. Now checks each comma-separated piece on its own.
|
| 338 |
"""
|
| 339 |
value = (raw or "").strip()
|
| 340 |
if not value:
|
| 341 |
return True
|
| 342 |
+
pieces = [p.strip() for p in value.split(",") if p.strip()]
|
| 343 |
+
if not pieces:
|
| 344 |
+
return False
|
| 345 |
+
return all(_is_plausible_single_phone(p) for p in pieces)
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def _is_plausible_single_phone(value: str) -> bool:
|
| 349 |
if any(ch not in "0123456789+()-. " for ch in value):
|
| 350 |
return False
|
| 351 |
return sum(ch.isdigit() for ch in value) >= 7
|
docs.md
CHANGED
|
@@ -3,6 +3,89 @@
|
|
| 3 |
Running record of features shipped, bugs fixed, and how each was verified.
|
| 4 |
Newest entry first. See `CLAUDE.md` for the logging rule.
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
## 2026-10-28 (9)
|
| 7 |
|
| 8 |
### Lint cleanup: unused `MailtoButton` import
|
|
|
|
| 3 |
Running record of features shipped, bugs fixed, and how each was verified.
|
| 4 |
Newest entry first. See `CLAUDE.md` for the logging rule.
|
| 5 |
|
| 6 |
+
## 2026-10-29 (2)
|
| 7 |
+
|
| 8 |
+
### Bug fix: meeting booked from Callbacks/Pipeline/Meetings didn't count in Today's "Meetings" stat
|
| 9 |
+
|
| 10 |
+
User: "agar callbacks tab sy ya kahin sy meeting booked hoti hai woh meetings me count nhi hoti today me".
|
| 11 |
+
Root cause: Today/This-Week's "Meetings" stat (`app/crud/call_events.py`'s `_count_outcomes(... (
|
| 12 |
+
"meeting_booked",))`) only ever counts rows in the `call_events` table β written by
|
| 13 |
+
`apply_call_outcome()`/`book_followup_meeting()`. Cold Call Desk's battlecard correctly books a meeting
|
| 14 |
+
through `useCallOutcome()` (β `apply_call_outcome`, which writes a call_events row), so those always
|
| 15 |
+
counted. But two OTHER "book a meeting" buttons β found by grepping every frontend call site that sets
|
| 16 |
+
`pipeline_stage: "meeting_booked"` β used a plain `useUpdateLead()` PATCH instead: a raw field write that
|
| 17 |
+
never touches `call_events` at all. The lead still correctly moved to the Meetings calendar (that only
|
| 18 |
+
needs `meeting_at`), so this was invisible unless you were specifically watching the Today counter.
|
| 19 |
+
|
| 20 |
+
**Files touched** (both frontend-only β the backend's `/leads/{id}/outcome` endpoint already existed and
|
| 21 |
+
already logs correctly, this was purely a wrong-hook wiring bug):
|
| 22 |
+
- `outreach-frontend/components/pipeline/pipeline-view.tsx` β `PipelineActions`'s "Meeting Booked" button
|
| 23 |
+
(shared by `/pipeline` AND `/callbacks`, since Callbacks reuses this same component scoped differently)
|
| 24 |
+
now uses `useCallOutcome()` instead of `useUpdateLead()` for a first-time booking (the
|
| 25 |
+
already-correct `useBookFollowupMeeting()` path for `proposal_sent`-stage leads is untouched).
|
| 26 |
+
- `outreach-frontend/components/meetings/needs-followup-list.tsx` β the Meetings page's "Mark as Meeting
|
| 27 |
+
Booked" button (on a lead sitting at Needs Follow-up), same swap.
|
| 28 |
+
|
| 29 |
+
Both now go through the exact same `apply_call_outcome(outcome="meeting_booked")` path Cold Call Desk
|
| 30 |
+
already used, which does everything the old raw PATCH did (same `pipeline_stage`/`meeting_at`) PLUS logs
|
| 31 |
+
to `call_events` (counts in Today), appends a note, and clears `sent_to_desk_at` if the lead was still on
|
| 32 |
+
a desk. Checked for other instances of the same bug pattern β two other `meeting_at:`-setting call sites
|
| 33 |
+
remain (`add-lead-popover.tsx`, and the general multi-field save in `lead-edit-form.tsx`) but both are
|
| 34 |
+
bulk/initial data entry, not a "disposition" action, so left as-is; only dedicated "book a meeting" action
|
| 35 |
+
buttons were in scope.
|
| 36 |
+
|
| 37 |
+
**How verified**: `npx tsc --noEmit` (0 errors) and `npx eslint` on both files (0 warnings/errors). Both
|
| 38 |
+
mirrored to `E:\outreach-agent-frontend`, confirmed zero drift via `diff -q`.
|
| 39 |
+
|
| 40 |
+
**Status**: β οΈ Partial β code complete and statically verified, NOT yet live-tested. No backend restart
|
| 41 |
+
needed (frontend-only change, no backend file touched) β picked up on next page load. Live test: book a
|
| 42 |
+
meeting from the Callbacks tab (or Meetings page's "Mark as Meeting Booked"), then check Today's Meetings
|
| 43 |
+
count went up by one.
|
| 44 |
+
|
| 45 |
+
## 2026-10-29 (1)
|
| 46 |
+
|
| 47 |
+
### Bug fix: phone validation (entry 2026-10-28 (6)) broke the existing multi-number format
|
| 48 |
+
|
| 49 |
+
User screenshot: Contact Phone field with "+447786363281 ,+44 371 964 2113" (two numbers, the CRM's
|
| 50 |
+
existing comma-joined-multiple-numbers format β see `components/leads/phone-number-list.tsx`,
|
| 51 |
+
`lead_engine.py`'s `_all_phones()`, `call_timezone()`'s own `.split(",")[0]`) rejected as "Doesn't look
|
| 52 |
+
like a valid phone number", Save stuck disabled. Root cause: entry (6)'s `is_plausible_phone()` (both
|
| 53 |
+
backend and frontend) only ever validated a SINGLE number's characters β comma wasn't in the whitelist β
|
| 54 |
+
so any lead that legitimately carries more than one number broke the moment that blocking validator
|
| 55 |
+
shipped. This was a regression in a prior fix, not a pre-existing bug.
|
| 56 |
+
|
| 57 |
+
Fix: `is_plausible_phone()` now splits the input on comma and validates each piece independently (all
|
| 58 |
+
must pass), in both `outreach-backend/app/services/validation.py` and
|
| 59 |
+
`outreach-frontend/lib/validation/lead.ts`.
|
| 60 |
+
|
| 61 |
+
Also implemented the user's second ask in the same message: "agar koi add krna bhul jaye 2 3 numbers me
|
| 62 |
+
sy ek k bech me toh system wahan auto add kr dy" β if someone types/pastes two numbers back-to-back with
|
| 63 |
+
no separator at all, auto-insert one. New `autoSeparatePhoneNumbers()` in `lib/validation/lead.ts`,
|
| 64 |
+
scoped to the one unambiguous boundary this data actually has (every real number here starts with
|
| 65 |
+
"+<country code>"): a "+" immediately following another number's digits (no comma, only whitespace or
|
| 66 |
+
nothing between) gets a ", " inserted before it. Wired to `onBlur` on the Contact Phone field in both
|
| 67 |
+
`components/layout/add-lead-popover.tsx` and `components/leads/lead-edit-form.tsx` β reformats as soon as
|
| 68 |
+
the rep leaves the field, before `phoneInvalid` is ever computed against it. An already-comma-separated
|
| 69 |
+
input is left untouched (confirmed below).
|
| 70 |
+
|
| 71 |
+
**Files touched**: `outreach-backend/app/services/validation.py`,
|
| 72 |
+
`outreach-frontend/lib/validation/lead.ts`, `outreach-frontend/components/layout/add-lead-popover.tsx`,
|
| 73 |
+
`outreach-frontend/components/leads/lead-edit-form.tsx`.
|
| 74 |
+
|
| 75 |
+
**How verified**: `python3 -m py_compile` (backend, clean) plus a standalone interpreter check of the
|
| 76 |
+
split-validation logic against the exact reported value and several others (all correct, including
|
| 77 |
+
`""` β true and `"qw"` β false unchanged); `npx tsc --noEmit` (0 errors) and `npx eslint` (exit 0) on
|
| 78 |
+
all touched frontend files; a standalone Node check of both `isPlausiblePhone()` and
|
| 79 |
+
`autoSeparatePhoneNumbers()` together against 5 cases, including two numbers pasted with NO separator at
|
| 80 |
+
all and the user's own reported value verbatim β all passed. All 4 files mirrored to `E:\mz_chatbot` /
|
| 81 |
+
`E:\outreach-agent-frontend`, confirmed zero drift via `diff -q`.
|
| 82 |
+
|
| 83 |
+
**Status**: β οΈ Partial β code complete and verified statically + via standalone logic tests, NOT yet
|
| 84 |
+
live-tested in the running app. Needs a backend restart (`validation.py` changed); frontend picked up on
|
| 85 |
+
next page load. Live test: paste the user's exact reported value into Contact Phone on both Add Lead and
|
| 86 |
+
Edit Lead, confirm Save is enabled; also type two numbers with no separator and tab out, confirm a comma
|
| 87 |
+
gets inserted automatically.
|
| 88 |
+
|
| 89 |
## 2026-10-28 (9)
|
| 90 |
|
| 91 |
### Lint cleanup: unused `MailtoButton` import
|