Unification polish: LinkedIn in the grid inline contact editor (v0.1.0:54)

The fundraising grid's per-contact editor now has a LinkedIn URL field next to
name, email, title, and location. It threads through the grid contact object and
sanitize (which preserves contact-object fields), and _upsert_contact_from_fundraising
now reads and persists linkedin_url on both the update and insert paths — so a
LinkedIn entered in the grid lands on the linked contact record.

Test: test_grid_contact_link.py extended to assert LinkedIn entered in the grid
persists to the contact (idempotent). Frontend html.parser clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Keysat
2026-06-05 15:24:50 -05:00
parent 49d384a0fb
commit 300041a7ec
6 changed files with 50 additions and 14 deletions
+7 -4
View File
@@ -787,6 +787,7 @@ def _upsert_contact_from_fundraising(conn, investor_name, contact, actor_user_id
state = str(contact.get('state') or '').strip()
country = str(contact.get('country') or '').strip()
location_query = str(contact.get('location_query') or '').strip()
linkedin_url = str(contact.get('linkedin_url') or '').strip()
if not full_name and not email:
return None
first_name, last_name = _split_full_name(full_name)
@@ -830,20 +831,21 @@ def _upsert_contact_from_fundraising(conn, investor_name, contact, actor_user_id
next_state = state or str(existing['state'] or '')
next_country = country or str(existing['country'] or '')
next_location_query = location_query or str(existing['location_query'] or '')
next_linkedin = linkedin_url or str(existing['linkedin_url'] or '')
next_org = org_id or existing['organization_id']
conn.execute("""
UPDATE contacts
SET first_name = ?, last_name = ?, email = ?, title = ?,
organization_id = ?, source = ?, contact_type = 'investor', city = ?, state = ?, country = ?, location_query = ?, updated_at = ?
organization_id = ?, source = ?, contact_type = 'investor', city = ?, state = ?, country = ?, location_query = ?, linkedin_url = ?, updated_at = ?
WHERE id = ?
""", (next_first, next_last, next_email, next_title, next_org, next_source, next_city, next_state, next_country, next_location_query, 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, now(), existing['id']))
return existing['id']
contact_id = generate_id()
conn.execute("""
INSERT INTO contacts (
id, first_name, last_name, email, title, organization_id, source, contact_type, status, city, state, country, location_query, created_by, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, 'investor', 'active', ?, ?, ?, ?, ?, ?)
id, first_name, last_name, email, title, organization_id, source, contact_type, status, city, state, country, location_query, linkedin_url, created_by, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, 'investor', 'active', ?, ?, ?, ?, ?, ?, ?)
""", (
contact_id,
first_name or 'Unknown',
@@ -856,6 +858,7 @@ def _upsert_contact_from_fundraising(conn, investor_name, contact, actor_user_id
state,
country,
location_query,
linkedin_url,
actor_user_id,
now()
))
+5 -2
View File
@@ -44,7 +44,8 @@ def main():
],
"rows": [
{"id": "row-test-1", "investor_name": "Testco Capital",
"contacts": [{"name": "Jane Doe", "email": "jane@testco.com", "title": "Partner"}]},
"contacts": [{"name": "Jane Doe", "email": "jane@testco.com", "title": "Partner",
"linkedin_url": "https://linkedin.com/in/janedoe"}]},
],
}
server.sync_fundraising_relational(conn, server.sanitize_fundraising_grid(grid), [])
@@ -54,9 +55,11 @@ def main():
check(bool(fc and fc["contact_id"]), f"grid contact_id populated by sync (got {dict(fc) if fc else None})")
if fc and fc["contact_id"]:
ct = conn.execute("SELECT id, email FROM contacts WHERE id=?", (fc["contact_id"],)).fetchone()
ct = conn.execute("SELECT id, email, linkedin_url FROM contacts WHERE id=?", (fc["contact_id"],)).fetchone()
check(bool(ct and ct["email"] == "jane@testco.com"),
f"link points to the correct contacts row (got {dict(ct) if ct else None})")
check(bool(ct and ct["linkedin_url"] == "https://linkedin.com/in/janedoe"),
f"LinkedIn entered in the grid persists to the contact (got {ct['linkedin_url'] if ct else None})")
# Re-sync is idempotent: still exactly one linked contact for Jane.
server.sync_fundraising_relational(conn, server.sanitize_fundraising_grid(grid), [])