Compare commits
2 Commits
c1123c4ce8
...
b4522d2e2a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4522d2e2a | ||
|
|
104ac5fd6d |
1
examples/Reservierungen_bemelman_20251117_065035.xml
Normal file
1
examples/Reservierungen_bemelman_20251117_065035.xml
Normal file
File diff suppressed because one or more lines are too long
1
examples/Reservierungen_bemelman_20251117_230001.xml
Normal file
1
examples/Reservierungen_bemelman_20251117_230001.xml
Normal file
File diff suppressed because one or more lines are too long
@@ -3,7 +3,7 @@
|
|||||||
select sum(room.total_revenue::float)
|
select sum(room.total_revenue::float)
|
||||||
|
|
||||||
from alpinebits.conversions as con
|
from alpinebits.conversions as con
|
||||||
join alpinebits.room_reservations as room on room.conversion_id = con.id
|
join alpinebits.conversion_rooms as room on room.conversion_id = con.id
|
||||||
join alpinebits.reservations as res on res.id = con.reservation_id
|
join alpinebits.reservations as res on res.id = con.reservation_id
|
||||||
|
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ select res.created_at, con.reservation_date, res.start_date, room.arrival_date,r
|
|||||||
room.room_status
|
room.room_status
|
||||||
|
|
||||||
from alpinebits.conversions as con
|
from alpinebits.conversions as con
|
||||||
join alpinebits.room_reservations as room on room.conversion_id = con.id
|
join alpinebits.conversion_rooms as room on room.conversion_id = con.id
|
||||||
join alpinebits.reservations as res on res.id = con.reservation_id
|
join alpinebits.reservations as res on res.id = con.reservation_id
|
||||||
|
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ select res.created_at, con.reservation_date, res.start_date, room.arrival_date,r
|
|||||||
select round(sum(room.total_revenue::numeric)::numeric, 3), con.advertising_medium
|
select round(sum(room.total_revenue::numeric)::numeric, 3), con.advertising_medium
|
||||||
|
|
||||||
from alpinebits.conversions as con
|
from alpinebits.conversions as con
|
||||||
join alpinebits.room_reservations as room on room.conversion_id = con.id
|
join alpinebits.conversion_rooms as room on room.conversion_id = con.id
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,27 +1170,41 @@ 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
|
||||||
async with session_maker() as db_session:
|
db_session = await session_maker.create_session()
|
||||||
|
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
|
||||||
)
|
)
|
||||||
|
finally:
|
||||||
|
await db_session.close()
|
||||||
except Exception:
|
except Exception:
|
||||||
_LOGGER.exception(
|
_LOGGER.exception(
|
||||||
"Error processing CSV import in background for %s", filename
|
"Error processing CSV import in background for %s", filename
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@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),
|
||||||
@@ -1193,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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user