Csv import now works with preacknowlegdments

This commit is contained in:
Jonas Linter
2025-11-18 19:25:52 +01:00
parent 104ac5fd6d
commit b4522d2e2a
5 changed files with 57 additions and 20 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -39,7 +39,7 @@ from .alpinebits_server import (
Version, Version,
) )
from .auth import generate_unique_id, validate_api_key from .auth import generate_unique_id, validate_api_key
from .config_loader import load_config from .config_loader import load_config, get_username_for_hotel
from .const import CONF_GOOGLE_ACCOUNT, CONF_HOTEL_ID, CONF_META_ACCOUNT, HttpStatusCode from .const import CONF_GOOGLE_ACCOUNT, CONF_HOTEL_ID, CONF_META_ACCOUNT, HttpStatusCode
from .conversion_service import ConversionService from .conversion_service import ConversionService
from .csv_import import CSVImporter from .csv_import import CSVImporter
@@ -1142,15 +1142,25 @@ async def handle_wix_form_test(
async def _process_csv_import_background( async def _process_csv_import_background(
csv_content: str, csv_content: str,
filename: str, filename: str,
hotel_code: str | None, hotel_code: str,
session_maker: SessionMaker, session_maker: SessionMaker,
config: dict[str, Any], config: dict[str, Any],
log_filename: Path, log_filename: Path,
): ):
"""Background task to process CSV import. """Background task to process CSV import with automatic acknowledgement.
This runs in a separate asyncio task after the HTTP response is sent. This runs in a separate asyncio task after the HTTP response is sent.
Handles both file saving and database processing. Handles both file saving and database processing.
All imported reservations are automatically acknowledged using the username
associated with the hotel_code from the config.
Args:
csv_content: CSV content as string
filename: Original filename
hotel_code: Hotel code (mandatory) - used to get username for acknowledgements
session_maker: SessionMaker for creating database sessions
config: Application configuration
log_filename: Path to save the CSV file
""" """
try: try:
# First, save the CSV file (in background) # First, save the CSV file (in background)
@@ -1160,11 +1170,22 @@ async def _process_csv_import_background(
# Now process the CSV import # Now process the CSV import
_LOGGER.info("Starting database processing of %s", filename) _LOGGER.info("Starting database processing of %s", filename)
# Get username for acknowledgements from config
username = get_username_for_hotel(config, hotel_code)
# Create a new session for this background task # Create a new session for this background task
db_session = await session_maker.create_session() db_session = await session_maker.create_session()
try: try:
importer = CSVImporter(db_session, config) importer = CSVImporter(db_session, config)
stats = await importer.import_csv_file(str(log_filename), hotel_code, dryrun=False) # Import with pre-acknowledgement enabled
stats = await importer.import_csv_file(
str(log_filename),
hotel_code,
dryrun=False,
pre_acknowledge=True,
client_id=hotel_code,
username=username
)
_LOGGER.info( _LOGGER.info(
"CSV import complete for %s: %s", filename, stats "CSV import complete for %s: %s", filename, stats
@@ -1177,13 +1198,13 @@ async def _process_csv_import_background(
) )
@api_router.put("/admin/import-csv/{filename:path}") @api_router.put("/admin/import-csv/{hotel_code}/{filename:path}")
@limiter.limit(BURST_RATE_LIMIT) @limiter.limit(BURST_RATE_LIMIT)
async def import_csv_endpoint( async def import_csv_endpoint(
request: Request, request: Request,
background_tasks: BackgroundTasks, background_tasks: BackgroundTasks,
hotel_code: str,
filename: str, filename: str,
hotel_code: str | None = None,
credentials: tuple = Depends(validate_basic_auth), credentials: tuple = Depends(validate_basic_auth),
db_session=Depends(get_async_session), db_session=Depends(get_async_session),
session_maker: SessionMaker = Depends(get_session_maker), session_maker: SessionMaker = Depends(get_session_maker),
@@ -1196,16 +1217,18 @@ async def import_csv_endpoint(
- fbclid/gclid tracking IDs - fbclid/gclid tracking IDs
Returns immediately with 202 Accepted while processing continues in background. Returns immediately with 202 Accepted while processing continues in background.
All imported reservations are automatically acknowledged using the username
associated with the hotel_code in the config.
Requires basic authentication and saves CSV files to log directory. Requires basic authentication and saves CSV files to log directory.
Supports gzip compression via Content-Encoding header. Supports gzip compression via Content-Encoding header.
Args: Args:
hotel_code: Hotel code (mandatory) - used to get username for acknowledgements
filename: Name for the CSV file (used for logging) filename: Name for the CSV file (used for logging)
hotel_code: Optional hotel code to override CSV values
credentials: Basic auth credentials credentials: Basic auth credentials
Example: PUT /api/admin/import-csv/reservations.csv Example: PUT /api/admin/import-csv/39054_001/reservations.csv
""" """
try: try:

View File

@@ -329,3 +329,8 @@ class Config:
# For backward compatibility # For backward compatibility
def load_config(): def load_config():
return Config().config return Config().config
def get_username_for_hotel(config: dict, hotel_code: str) -> str:
"""Get the username associated with a hotel_code from config."""
return next(h.get("username") for h in config.get("alpine_bits_auth", []) if h.get("hotel_id") == hotel_code)

View File

@@ -116,6 +116,21 @@ class CSVImporter:
self.customer_service = CustomerService(db_session) self.customer_service = CustomerService(db_session)
self.reservation_service = ReservationService(db_session) self.reservation_service = ReservationService(db_session)
def _get_hotel_info(self, hotel_code: str) -> tuple[str, str]:
"""Get hotel name from config by hotel_code.
Args:
hotel_code: Hotel code to look up
Returns:
Tuple of (hotel_code, hotel_name) from config
"""
for hotel in self.config.get("alpine_bits_auth", []):
if hotel.get("hotel_id") == hotel_code:
return hotel_code, hotel.get("hotel_name", "")
# Fallback to default if not found
return hotel_code, self.config.get("default_hotel_name", "Frangart Inn")
async def find_duplicate_reservation( async def find_duplicate_reservation(
self, self,
first_name: str, first_name: str,
@@ -184,13 +199,13 @@ class CSVImporter:
return None return None
async def import_csv_file( async def import_csv_file(
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 self, csv_file_path: str, hotel_code: str, dryrun: bool = False, pre_acknowledge: bool = False, client_id: Optional[str] = None, username: Optional[str] = None
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Import reservations from a CSV file. """Import reservations from a CSV file.
Args: Args:
csv_file_path: Path to CSV file csv_file_path: Path to CSV file
hotel_code: Optional hotel code to override CSV values hotel_code: Hotel code (mandatory) - used to look up hotel name from config
dryrun: If True, parse and print first 10 rows as JSON without importing dryrun: If True, parse and print first 10 rows as JSON without importing
pre_acknowledge: If True, pre-acknowledges all imported reservations pre_acknowledge: If True, pre-acknowledges all imported reservations
client_id: Client ID for pre-acknowledgement (required if pre_acknowledge=True) client_id: Client ID for pre-acknowledgement (required if pre_acknowledge=True)
@@ -432,16 +447,8 @@ class CSVImporter:
else: else:
submission_id = f"csv_import_{row_num}_{datetime.now().isoformat()}" submission_id = f"csv_import_{row_num}_{datetime.now().isoformat()}"
# Determine hotel code and name # Determine hotel code and name (from config)
final_hotel_code = ( final_hotel_code, final_hotel_name = self._get_hotel_info(hotel_code)
hotel_code
or str(row.get("hotel_id", "")).strip()
or self.config.get("default_hotel_code", "123")
)
final_hotel_name = (
str(row.get("hotel_name", "")).strip()
or self.config.get("default_hotel_name", "Frangart Inn")
)
# Parse room type fields if available # Parse room type fields if available
room_type_code = str(row.get("room_type_code", "")).strip() or None room_type_code = str(row.get("room_type_code", "")).strip() or None