Accept contact.phone in the fundraising contact upsert (server half of card phone)

_upsert_contact_from_fundraising now reads contact.phone and writes contacts.phone on
both the insert and update paths, so a phone captured from a business card persists on
the canonical contact record. Phone stays contact-level (not a grid pill field),
matching how the team edits it. Validated by test_grid_add_investor.py.

This is the SERVER half of business-card phone capture, staged for the next s9pk
(version bump + build + install). The bot's phone extraction/card/payload lands in the
same deploy, so phone never shows on a card before the box can store it. NOT yet
built or installed to the box.
This commit is contained in:
Keysat
2026-06-20 11:10:34 -05:00
parent 8b2eb01a65
commit 92ab59de4e
2 changed files with 28 additions and 4 deletions
+7 -4
View File
@@ -825,6 +825,7 @@ def _upsert_contact_from_fundraising(conn, investor_name, contact, actor_user_id
country = str(contact.get('country') or '').strip() country = str(contact.get('country') or '').strip()
location_query = str(contact.get('location_query') or '').strip() location_query = str(contact.get('location_query') or '').strip()
linkedin_url = str(contact.get('linkedin_url') or '').strip() linkedin_url = str(contact.get('linkedin_url') or '').strip()
phone = str(contact.get('phone') or '').strip()
if not full_name and not email: if not full_name and not email:
return None return None
first_name, last_name = _split_full_name(full_name) first_name, last_name = _split_full_name(full_name)
@@ -869,20 +870,21 @@ def _upsert_contact_from_fundraising(conn, investor_name, contact, actor_user_id
next_country = country or str(existing['country'] or '') next_country = country or str(existing['country'] or '')
next_location_query = location_query or str(existing['location_query'] or '') next_location_query = location_query or str(existing['location_query'] or '')
next_linkedin = linkedin_url or str(existing['linkedin_url'] or '') next_linkedin = linkedin_url or str(existing['linkedin_url'] or '')
next_phone = phone or str(existing['phone'] or '')
next_org = org_id or existing['organization_id'] next_org = org_id or existing['organization_id']
conn.execute(""" conn.execute("""
UPDATE contacts UPDATE contacts
SET first_name = ?, last_name = ?, email = ?, title = ?, SET first_name = ?, last_name = ?, email = ?, title = ?,
organization_id = ?, source = ?, contact_type = 'investor', city = ?, state = ?, country = ?, location_query = ?, linkedin_url = ?, updated_at = ? organization_id = ?, source = ?, contact_type = 'investor', city = ?, state = ?, country = ?, location_query = ?, linkedin_url = ?, phone = ?, updated_at = ?
WHERE id = ? WHERE id = ?
""", (next_first, next_last, next_email, next_title, next_org, next_source, next_city, next_state, next_country, next_location_query, next_linkedin, now(), existing['id'])) """, (next_first, next_last, next_email, next_title, next_org, next_source, next_city, next_state, next_country, next_location_query, next_linkedin, next_phone, now(), existing['id']))
return existing['id'] return existing['id']
contact_id = generate_id() contact_id = generate_id()
conn.execute(""" conn.execute("""
INSERT INTO contacts ( INSERT INTO contacts (
id, first_name, last_name, email, title, organization_id, source, contact_type, status, city, state, country, location_query, linkedin_url, created_by, updated_at id, first_name, last_name, email, title, organization_id, source, contact_type, status, city, state, country, location_query, linkedin_url, phone, created_by, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, 'investor', 'active', ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, 'investor', 'active', ?, ?, ?, ?, ?, ?, ?, ?)
""", ( """, (
contact_id, contact_id,
first_name or 'Unknown', first_name or 'Unknown',
@@ -896,6 +898,7 @@ def _upsert_contact_from_fundraising(conn, investor_name, contact, actor_user_id
country, country,
location_query, location_query,
linkedin_url, linkedin_url,
phone,
actor_user_id, actor_user_id,
now() now()
)) ))
+21
View File
@@ -161,6 +161,27 @@ def main():
f"reminder linked to the new investor (got id={rem.get('investor_id')!r}, " f"reminder linked to the new investor (got id={rem.get('investor_id')!r}, "
f"name={rem.get('investor_name')!r})") f"name={rem.get('investor_name')!r})")
# ── card-intake contact fields land on the canonical contact (phone/city/linkedin) ──
# The Matrix card flow sends these on the contact dict; the upsert must persist them.
print("\n[contact fields: phone + city + linkedin persist on the contact]")
st, d = _req(port, "POST", "/api/fundraising/log-communication", token, {
"investor_name": "MARA Holdings", "create_investor_if_missing": True,
"contact": {"name": "Doug Mellinger", "email": "doug@mara.example",
"phone": "1.914.456.2146", "city": "New York",
"linkedin_url": "linkedin.com/in/dougmellinger"},
"type": "note", "body": "from a business card", "append_note": True,
})
check(st == 201, f"create with contact fields -> 201 (got {st})")
c = _db()
crow = c.execute("SELECT phone, city, linkedin_url FROM contacts WHERE lower(email) = ?",
("doug@mara.example",)).fetchone()
c.close()
check(crow is not None, "contact row exists")
check(bool(crow) and crow[0] == "1.914.456.2146", f"phone persisted (got {crow[0] if crow else None!r})")
check(bool(crow) and crow[1] == "New York", f"city persisted (got {crow[1] if crow else None!r})")
check(bool(crow) and crow[2] == "linkedin.com/in/dougmellinger",
f"linkedin persisted (got {crow[2] if crow else None!r})")
# ── unknown source_row_id is refused (guard) ── # ── unknown source_row_id is refused (guard) ──
print("\n[guard: reminder on an unknown source_row_id -> 404]") print("\n[guard: reminder on an unknown source_row_id -> 404]")
st, _ = _req(port, "POST", "/api/reminders", token, { st, _ = _req(port, "POST", "/api/reminders", token, {