179 lines
6.1 KiB
Python
179 lines
6.1 KiB
Python
"""Customer service layer for handling customer and hashed customer operations."""
|
|
|
|
from datetime import UTC, datetime
|
|
from typing import Optional
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from .db import Customer, HashedCustomer
|
|
|
|
|
|
class CustomerService:
|
|
"""Service for managing customers and their hashed versions.
|
|
|
|
Automatically maintains hashed customer data whenever customers are
|
|
created or updated, ensuring data is always in sync for Meta Conversion API.
|
|
"""
|
|
|
|
def __init__(self, session: AsyncSession):
|
|
self.session = session
|
|
|
|
async def create_customer(self, customer_data: dict) -> Customer:
|
|
"""Create a new customer and automatically create its hashed version.
|
|
|
|
Args:
|
|
customer_data: Dictionary containing customer fields
|
|
|
|
Returns:
|
|
The created Customer instance (with hashed_version relationship populated)
|
|
"""
|
|
# Create the customer
|
|
customer = Customer(**customer_data)
|
|
self.session.add(customer)
|
|
await self.session.flush() # Flush to get the customer.id
|
|
|
|
# Create hashed version
|
|
hashed_customer = customer.create_hashed_customer()
|
|
hashed_customer.created_at = datetime.now(UTC)
|
|
self.session.add(hashed_customer)
|
|
|
|
await self.session.commit()
|
|
await self.session.refresh(customer)
|
|
|
|
return customer
|
|
|
|
async def update_customer(
|
|
self, customer: Customer, update_data: dict
|
|
) -> Customer:
|
|
"""Update an existing customer and sync its hashed version.
|
|
|
|
Args:
|
|
customer: The customer to update
|
|
update_data: Dictionary of fields to update
|
|
|
|
Returns:
|
|
The updated Customer instance
|
|
"""
|
|
# Update customer fields
|
|
for key, value in update_data.items():
|
|
if hasattr(customer, key):
|
|
setattr(customer, key, value)
|
|
|
|
# Update or create hashed version
|
|
result = await self.session.execute(
|
|
select(HashedCustomer).where(
|
|
HashedCustomer.customer_id == customer.id
|
|
)
|
|
)
|
|
hashed_customer = result.scalar_one_or_none()
|
|
|
|
if hashed_customer:
|
|
# Update existing hashed customer
|
|
new_hashed = customer.create_hashed_customer()
|
|
hashed_customer.hashed_email = new_hashed.hashed_email
|
|
hashed_customer.hashed_phone = new_hashed.hashed_phone
|
|
hashed_customer.hashed_given_name = new_hashed.hashed_given_name
|
|
hashed_customer.hashed_surname = new_hashed.hashed_surname
|
|
hashed_customer.hashed_city = new_hashed.hashed_city
|
|
hashed_customer.hashed_postal_code = new_hashed.hashed_postal_code
|
|
hashed_customer.hashed_country_code = new_hashed.hashed_country_code
|
|
hashed_customer.hashed_gender = new_hashed.hashed_gender
|
|
hashed_customer.hashed_birth_date = new_hashed.hashed_birth_date
|
|
else:
|
|
# Create new hashed customer if it doesn't exist
|
|
hashed_customer = customer.create_hashed_customer()
|
|
hashed_customer.created_at = datetime.now(UTC)
|
|
self.session.add(hashed_customer)
|
|
|
|
await self.session.commit()
|
|
await self.session.refresh(customer)
|
|
|
|
return customer
|
|
|
|
async def get_customer_by_contact_id(
|
|
self, contact_id: str
|
|
) -> Optional[Customer]:
|
|
"""Get a customer by contact_id.
|
|
|
|
Args:
|
|
contact_id: The contact_id to search for
|
|
|
|
Returns:
|
|
Customer instance if found, None otherwise
|
|
"""
|
|
result = await self.session.execute(
|
|
select(Customer).where(Customer.contact_id == contact_id)
|
|
)
|
|
return result.scalar_one_or_none()
|
|
|
|
async def get_or_create_customer(self, customer_data: dict) -> Customer:
|
|
"""Get existing customer or create new one if not found.
|
|
|
|
Uses contact_id to identify existing customers if provided.
|
|
|
|
Args:
|
|
customer_data: Dictionary containing customer fields
|
|
(contact_id is optional)
|
|
|
|
Returns:
|
|
Existing or newly created Customer instance
|
|
"""
|
|
contact_id = customer_data.get("contact_id")
|
|
|
|
if contact_id:
|
|
existing = await self.get_customer_by_contact_id(contact_id)
|
|
if existing:
|
|
# Update existing customer
|
|
return await self.update_customer(existing, customer_data)
|
|
|
|
# Create new customer (either no contact_id or customer doesn't exist)
|
|
return await self.create_customer(customer_data)
|
|
|
|
async def get_hashed_customer(
|
|
self, customer_id: int
|
|
) -> Optional[HashedCustomer]:
|
|
"""Get the hashed version of a customer.
|
|
|
|
Args:
|
|
customer_id: The customer ID
|
|
|
|
Returns:
|
|
HashedCustomer instance if found, None otherwise
|
|
"""
|
|
result = await self.session.execute(
|
|
select(HashedCustomer).where(
|
|
HashedCustomer.customer_id == customer_id
|
|
)
|
|
)
|
|
return result.scalar_one_or_none()
|
|
|
|
async def hash_existing_customers(self) -> int:
|
|
"""Hash all existing customers that don't have a hashed version yet.
|
|
|
|
This is useful for backfilling hashed data for customers created
|
|
before the hashing system was implemented.
|
|
|
|
Returns:
|
|
Number of customers that were hashed
|
|
"""
|
|
# Get all customers
|
|
result = await self.session.execute(select(Customer))
|
|
customers = result.scalars().all()
|
|
|
|
hashed_count = 0
|
|
for customer in customers:
|
|
# Check if this customer already has a hashed version
|
|
existing_hashed = await self.get_hashed_customer(customer.id)
|
|
if not existing_hashed:
|
|
# Create hashed version
|
|
hashed_customer = customer.create_hashed_customer()
|
|
hashed_customer.created_at = datetime.now(UTC)
|
|
self.session.add(hashed_customer)
|
|
hashed_count += 1
|
|
|
|
if hashed_count > 0:
|
|
await self.session.commit()
|
|
|
|
return hashed_count
|