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

View File

@@ -39,7 +39,7 @@ from .alpinebits_server import (
Version,
)
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 .conversion_service import ConversionService
from .csv_import import CSVImporter
@@ -1142,15 +1142,25 @@ async def handle_wix_form_test(
async def _process_csv_import_background(
csv_content: str,
filename: str,
hotel_code: str | None,
hotel_code: str,
session_maker: SessionMaker,
config: dict[str, Any],
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.
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:
# First, save the CSV file (in background)
@@ -1160,11 +1170,22 @@ async def _process_csv_import_background(
# Now process the CSV import
_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
db_session = await session_maker.create_session()
try:
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(
"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)
async def import_csv_endpoint(
request: Request,
background_tasks: BackgroundTasks,
hotel_code: str,
filename: str,
hotel_code: str | None = None,
credentials: tuple = Depends(validate_basic_auth),
db_session=Depends(get_async_session),
session_maker: SessionMaker = Depends(get_session_maker),
@@ -1196,16 +1217,18 @@ async def import_csv_endpoint(
- fbclid/gclid tracking IDs
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.
Supports gzip compression via Content-Encoding header.
Args:
hotel_code: Hotel code (mandatory) - used to get username for acknowledgements
filename: Name for the CSV file (used for logging)
hotel_code: Optional hotel code to override CSV values
credentials: Basic auth credentials
Example: PUT /api/admin/import-csv/reservations.csv
Example: PUT /api/admin/import-csv/39054_001/reservations.csv
"""
try:

View File

@@ -329,3 +329,8 @@ class Config:
# For backward compatibility
def load_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.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(
self,
first_name: str,
@@ -184,13 +199,13 @@ class CSVImporter:
return None
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]:
"""Import reservations from a CSV file.
Args:
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
pre_acknowledge: If True, pre-acknowledges all imported reservations
client_id: Client ID for pre-acknowledgement (required if pre_acknowledge=True)
@@ -432,16 +447,8 @@ class CSVImporter:
else:
submission_id = f"csv_import_{row_num}_{datetime.now().isoformat()}"
# Determine hotel code and name
final_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")
)
# Determine hotel code and name (from config)
final_hotel_code, final_hotel_name = self._get_hotel_info(hotel_code)
# Parse room type fields if available
room_type_code = str(row.get("room_type_code", "")).strip() or None