feat(core): add address fields to contact service serialization and CRUD
This commit is contained in:
@@ -14,6 +14,9 @@ from app.models.company import Company
|
|||||||
from app.models.contact import CompanyContact, Contact
|
from app.models.contact import CompanyContact, Contact
|
||||||
|
|
||||||
|
|
||||||
|
ADDRESS_FIELDS = ("address_street", "address_city", "address_zip", "address_country", "address_state")
|
||||||
|
|
||||||
|
|
||||||
def _contact_to_dict(c: Contact, include_companies: bool = False) -> dict[str, Any]:
|
def _contact_to_dict(c: Contact, include_companies: bool = False) -> dict[str, Any]:
|
||||||
"""Serialize a Contact ORM object to dict."""
|
"""Serialize a Contact ORM object to dict."""
|
||||||
data: dict[str, Any] = {
|
data: dict[str, Any] = {
|
||||||
@@ -27,6 +30,11 @@ def _contact_to_dict(c: Contact, include_companies: bool = False) -> dict[str, A
|
|||||||
"department": c.department,
|
"department": c.department,
|
||||||
"linkedin_url": c.linkedin_url,
|
"linkedin_url": c.linkedin_url,
|
||||||
"notes": c.notes,
|
"notes": c.notes,
|
||||||
|
"address_street": c.address_street,
|
||||||
|
"address_city": c.address_city,
|
||||||
|
"address_zip": c.address_zip,
|
||||||
|
"address_country": c.address_country,
|
||||||
|
"address_state": c.address_state,
|
||||||
"created_at": c.created_at.isoformat() if c.created_at else None,
|
"created_at": c.created_at.isoformat() if c.created_at else None,
|
||||||
"updated_at": c.updated_at.isoformat() if c.updated_at else None,
|
"updated_at": c.updated_at.isoformat() if c.updated_at else None,
|
||||||
}
|
}
|
||||||
@@ -144,13 +152,17 @@ async def create_contact(
|
|||||||
department=data.get("department"),
|
department=data.get("department"),
|
||||||
linkedin_url=data.get("linkedin_url"),
|
linkedin_url=data.get("linkedin_url"),
|
||||||
notes=data.get("notes"),
|
notes=data.get("notes"),
|
||||||
|
address_street=data.get("address_street"),
|
||||||
|
address_city=data.get("address_city"),
|
||||||
|
address_zip=data.get("address_zip"),
|
||||||
|
address_country=data.get("address_country"),
|
||||||
|
address_state=data.get("address_state"),
|
||||||
created_by=user_id,
|
created_by=user_id,
|
||||||
updated_by=user_id,
|
updated_by=user_id,
|
||||||
)
|
)
|
||||||
db.add(contact)
|
db.add(contact)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
|
|
||||||
# Link to companies if company_ids provided
|
|
||||||
company_ids = data.get("company_ids")
|
company_ids = data.get("company_ids")
|
||||||
linked_companies = []
|
linked_companies = []
|
||||||
if company_ids:
|
if company_ids:
|
||||||
@@ -159,7 +171,6 @@ async def create_contact(
|
|||||||
cid = uuid.UUID(cid_str) if isinstance(cid_str, str) else cid_str
|
cid = uuid.UUID(cid_str) if isinstance(cid_str, str) else cid_str
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
continue
|
continue
|
||||||
# Verify company exists in tenant
|
|
||||||
comp_q = select(Company).where(
|
comp_q = select(Company).where(
|
||||||
Company.id == cid,
|
Company.id == cid,
|
||||||
Company.tenant_id == tenant_id,
|
Company.tenant_id == tenant_id,
|
||||||
@@ -213,17 +224,11 @@ async def update_contact(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
changes: dict[str, Any] = {}
|
changes: dict[str, Any] = {}
|
||||||
for field in (
|
all_fields = (
|
||||||
"first_name",
|
"first_name", "last_name", "email", "phone", "mobile",
|
||||||
"last_name",
|
"position", "department", "linkedin_url", "notes",
|
||||||
"email",
|
) + ADDRESS_FIELDS
|
||||||
"phone",
|
for field in all_fields:
|
||||||
"mobile",
|
|
||||||
"position",
|
|
||||||
"department",
|
|
||||||
"linkedin_url",
|
|
||||||
"notes",
|
|
||||||
):
|
|
||||||
if field in data and data[field] is not None:
|
if field in data and data[field] is not None:
|
||||||
old_val = getattr(contact, field)
|
old_val = getattr(contact, field)
|
||||||
changes[field] = {"old": old_val, "new": data[field]}
|
changes[field] = {"old": old_val, "new": data[field]}
|
||||||
@@ -258,12 +263,7 @@ async def soft_delete_contact(
|
|||||||
|
|
||||||
await db.flush()
|
await db.flush()
|
||||||
await log_audit(
|
await log_audit(
|
||||||
db,
|
db, tenant_id, user_id, "delete", "contact", contact_id,
|
||||||
tenant_id,
|
|
||||||
user_id,
|
|
||||||
"delete",
|
|
||||||
"contact",
|
|
||||||
contact_id,
|
|
||||||
changes={"first_name": contact.first_name, "last_name": contact.last_name},
|
changes={"first_name": contact.first_name, "last_name": contact.last_name},
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
@@ -285,10 +285,8 @@ async def gdpr_hard_delete_contact(
|
|||||||
if contact is None:
|
if contact is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Capture snapshot before deletion
|
|
||||||
snapshot = _contact_to_dict(contact)
|
snapshot = _contact_to_dict(contact)
|
||||||
|
|
||||||
# Delete N:M links first
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
delete(CompanyContact).where(
|
delete(CompanyContact).where(
|
||||||
CompanyContact.contact_id == contact_id,
|
CompanyContact.contact_id == contact_id,
|
||||||
@@ -296,11 +294,9 @@ async def gdpr_hard_delete_contact(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Physical delete
|
|
||||||
await db.delete(contact)
|
await db.delete(contact)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
|
|
||||||
# Deletion log (immutable snapshot)
|
|
||||||
await log_deletion(
|
await log_deletion(
|
||||||
db,
|
db,
|
||||||
tenant_id,
|
tenant_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user