Files
alpinebits_python/src/alpine_bits_python/customer_service.py

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