diff --git a/src/alpine_bits_python/csv_import.py b/src/alpine_bits_python/csv_import.py index 45b8775..9d4352e 100644 --- a/src/alpine_bits_python/csv_import.py +++ b/src/alpine_bits_python/csv_import.py @@ -184,7 +184,7 @@ class CSVImporter: return None async def import_csv_file( - self, csv_file_path: str, hotel_code: Optional[str] = None, dryrun: bool = False + self, csv_file_path: str, hotel_code: Optional[str] = None, dryrun: bool = False, pre_acknowledge: bool = False, client_id: Optional[str] = None, username: Optional[str] = None ) -> dict[str, Any]: """Import reservations from a CSV file. @@ -192,6 +192,9 @@ class CSVImporter: csv_file_path: Path to CSV file hotel_code: Optional hotel code to override CSV values dryrun: If True, parse and print first 10 rows as JSON without importing + pre_acknowledge: If True, pre-acknowledges all imported reservations + client_id: Client ID for pre-acknowledgement (required if pre_acknowledge=True) + username: Username for pre-acknowledgement (optional, but recommended) Returns: Dictionary with import statistics or parsed data (if dryrun=True) @@ -200,6 +203,9 @@ class CSVImporter: if not path.exists(): raise FileNotFoundError(f"CSV file not found: {csv_file_path}") + if pre_acknowledge and not client_id: + raise ValueError("client_id is required when pre_acknowledge=True") + # Start a transaction - will rollback on any exception await self.db_session.begin() @@ -272,6 +278,7 @@ class CSVImporter: "existing_customers": 0, "created_reservations": 0, "skipped_duplicates": 0, + "pre_acknowledged": 0, "errors": [], } @@ -353,10 +360,10 @@ class CSVImporter: "name_title": None, } - # Get or create customer - customer = await self._find_or_create_customer(customer_data) + # Get or create customer (without committing) + customer = await self._find_or_create_customer(customer_data, auto_commit=False) if customer.id is None: - await self.db_session.refresh(customer) + await self.db_session.flush() # Flush to get customer.id stats["created_customers"] += 1 else: stats["existing_customers"] += 1 @@ -463,13 +470,28 @@ class CSVImporter: room_classification_code=room_class_code, ) - # Create reservation if customer exists + # Create reservation if customer exists (without committing) if customer.id: - await self.reservation_service.create_reservation( - reservation, customer.id + db_reservation = await self.reservation_service.create_reservation( + reservation, customer.id, auto_commit=False ) stats["created_reservations"] += 1 _LOGGER.info("Created reservation for %s %s", first_name, last_name) + + # Pre-acknowledge if requested + if pre_acknowledge and db_reservation.md5_unique_id: + await self.reservation_service.record_acknowledgement( + client_id=client_id, + unique_id=db_reservation.md5_unique_id, + username=username, + auto_commit=False + ) + stats["pre_acknowledged"] += 1 + _LOGGER.debug( + "Pre-acknowledged reservation %s for client %s", + db_reservation.md5_unique_id, + username or client_id + ) else: raise ValueError("Failed to get or create customer") @@ -505,7 +527,7 @@ class CSVImporter: else: return None - async def _find_or_create_customer(self, customer_data: dict) -> Customer: + async def _find_or_create_customer(self, customer_data: dict, auto_commit: bool = True) -> Customer: """Find existing customer or create new one. Args: @@ -550,18 +572,18 @@ class CSVImporter: # Update customer data if needed try: existing_customer = await self.customer_service.update_customer( - existing, customer_data + existing, customer_data, auto_commit=auto_commit ) except Exception as e: - + print(customer_data) print("---") print(existing) - + raise - + return existing_customer # Create new customer - return await self.customer_service.create_customer(customer_data) + return await self.customer_service.create_customer(customer_data, auto_commit=auto_commit) diff --git a/src/alpine_bits_python/customer_service.py b/src/alpine_bits_python/customer_service.py index 7614e75..5fa8d4c 100644 --- a/src/alpine_bits_python/customer_service.py +++ b/src/alpine_bits_python/customer_service.py @@ -23,11 +23,12 @@ class CustomerService: def __init__(self, session: AsyncSession): self.session = session - async def create_customer(self, customer_data: dict) -> Customer: + async def create_customer(self, customer_data: dict, auto_commit: bool = True) -> Customer: """Create a new customer and automatically create its hashed version. Args: customer_data: Dictionary containing customer fields + auto_commit: If True, commits the transaction. If False, caller must commit. Returns: The created Customer instance (with hashed_version relationship populated) @@ -60,17 +61,19 @@ class CustomerService: hashed_customer.created_at = datetime.now(UTC) self.session.add(hashed_customer) - await self.session.commit() - await self.session.refresh(customer) + if auto_commit: + await self.session.commit() + await self.session.refresh(customer) return customer - async def update_customer(self, customer: Customer, update_data: dict) -> Customer: + async def update_customer(self, customer: Customer, update_data: dict, auto_commit: bool = True) -> Customer: """Update an existing customer and sync its hashed version. Args: customer: The customer to update update_data: Dictionary of fields to update + auto_commit: If True, commits the transaction. If False, caller must commit. Returns: The updated Customer instance @@ -151,8 +154,9 @@ class CustomerService: hashed_customer.created_at = datetime.now(UTC) self.session.add(hashed_customer) - await self.session.commit() - await self.session.refresh(customer) + if auto_commit: + await self.session.commit() + await self.session.refresh(customer) return customer @@ -171,7 +175,7 @@ class CustomerService: ) return result.scalar_one_or_none() - async def get_or_create_customer(self, customer_data: dict) -> Customer: + async def get_or_create_customer(self, customer_data: dict, auto_commit: bool = True) -> Customer: """Get existing customer or create new one if not found. Uses contact_id to identify existing customers if provided. @@ -179,6 +183,7 @@ class CustomerService: Args: customer_data: Dictionary containing customer fields (contact_id is optional) + auto_commit: If True, commits the transaction. If False, caller must commit. Returns: Existing or newly created Customer instance @@ -190,10 +195,10 @@ class CustomerService: existing = await self.get_customer_by_contact_id(contact_id) if existing: # Update existing customer - return await self.update_customer(existing, customer_data) + return await self.update_customer(existing, customer_data, auto_commit=auto_commit) # Create new customer (either no contact_id or customer doesn't exist) - return await self.create_customer(customer_data) + return await self.create_customer(customer_data, auto_commit=auto_commit) async def get_hashed_customer(self, customer_id: int) -> HashedCustomer | None: """Get the hashed version of a customer. diff --git a/src/alpine_bits_python/reservation_service.py b/src/alpine_bits_python/reservation_service.py index 6e83e14..0e48758 100644 --- a/src/alpine_bits_python/reservation_service.py +++ b/src/alpine_bits_python/reservation_service.py @@ -48,13 +48,14 @@ class ReservationService: return Reservation(**data) async def create_reservation( - self, reservation_data: ReservationData, customer_id: int + self, reservation_data: ReservationData, customer_id: int, auto_commit: bool = True ) -> Reservation: """Create a new reservation. Args: reservation_data: ReservationData containing reservation details customer_id: ID of the customer making the reservation + auto_commit: If True, commits the transaction. If False, caller must commit. Returns: Created Reservation instance @@ -63,8 +64,13 @@ class ReservationService: reservation_data, customer_id ) self.session.add(reservation) - await self.session.commit() - await self.session.refresh(reservation) + + if auto_commit: + await self.session.commit() + await self.session.refresh(reservation) + else: + await self.session.flush() # Flush to get the reservation.id + return reservation async def get_reservation_by_unique_id( @@ -220,7 +226,7 @@ class ReservationService: ] async def record_acknowledgement( - self, client_id: str, unique_id: str, username: Optional[str] = None + self, client_id: str, unique_id: str, username: Optional[str] = None, auto_commit: bool = True ) -> AckedRequest: """Record that a client has acknowledged a reservation. @@ -228,6 +234,7 @@ class ReservationService: client_id: The client ID unique_id: The unique_id of the reservation (md5_unique_id) username: The username of the client making the request (optional) + auto_commit: If True, commits the transaction. If False, caller must commit. Returns: Created AckedRequest instance @@ -239,8 +246,13 @@ class ReservationService: timestamp=datetime.now(UTC), ) self.session.add(acked) - await self.session.commit() - await self.session.refresh(acked) + + if auto_commit: + await self.session.commit() + await self.session.refresh(acked) + else: + await self.session.flush() # Flush to get the acked.id + return acked async def is_acknowledged(self, unique_id: str, username: Optional[str] = None, client_id: Optional[str] = None) -> bool: