From fc15bc5d9a98d4923998c87abc57566a55435389 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Mon, 13 Jul 2026 21:07:47 +0000 Subject: [PATCH] Remove old Resos sync path (resos_bookings table) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidates to single sync path: resos_bookings_sync.py → resos_bookings_data table. Removes sync_resos_data(), duplicate load_resos_custom_field_mappings(), resos_api_key and sync_resos_enabled seed rows, and the dead resos_sync scheduler job. Co-Authored-By: Claude Sonnet 4.6 --- backend/jobs/data_sync.py | 247 +------------------------------------- backend/scheduler.py | 37 ++---- backend/schema.sql | 3 - 3 files changed, 14 insertions(+), 273 deletions(-) diff --git a/backend/jobs/data_sync.py b/backend/jobs/data_sync.py index 8c20f3d..6a1dc6e 100644 --- a/backend/jobs/data_sync.py +++ b/backend/jobs/data_sync.py @@ -1,5 +1,5 @@ """ -Data sync job - pulls data from Newbook and Resos APIs +Data sync job - pulls data from Newbook API """ import json import logging @@ -9,7 +9,6 @@ from typing import Optional, Dict, Set from sqlalchemy import text from database import SyncSessionLocal from services.newbook_client import NewbookClient -from services.resos_client import ResosClient logger = logging.getLogger(__name__) @@ -75,13 +74,6 @@ def load_newbook_credentials(db) -> dict: } -def load_resos_credentials(db) -> dict: - """Load Resos API credentials from central Settings service.""" - from services.central_settings import get_resos_credentials_sync - creds = get_resos_credentials_sync() - return creds or {'api_key': None} - - def load_gl_config(db) -> tuple: """ Load GL code configuration for identifying breakfast/dinner items. @@ -167,7 +159,7 @@ async def run_data_sync( triggered_by: str = "scheduler" ): """ - Main data sync job - runs Newbook bookings, Newbook occupancy report, and Resos sync. + Main data sync job - runs Newbook bookings and Newbook occupancy report. Args: full_sync: If True, pulls all bookings. If False, only pulls changes since last sync. @@ -186,11 +178,6 @@ async def run_data_sync( occ_to_date = date.today() + timedelta(days=365) await sync_newbook_occupancy_report(occ_from_date, occ_to_date, triggered_by) - # Sync Resos data (still uses date range for now) - from_date = date.today() - timedelta(days=7) - to_date = date.today() + timedelta(days=365) - await sync_resos_data(from_date, to_date, triggered_by) - logger.info("Data sync completed successfully") except Exception as e: logger.error(f"Data sync failed: {e}") @@ -546,236 +533,6 @@ async def sync_newbook_data( db.close() -def load_resos_custom_field_mappings(db) -> Dict[str, dict]: - """ - Load custom field mappings from resos_custom_field_mapping table. - - Returns dict: {field_id: {"maps_to": "hotel_guest", "value_for_true": "Yes"}} - """ - result = db.execute(text(""" - SELECT field_id, maps_to, value_for_true - FROM resos_custom_field_mapping - WHERE maps_to != 'ignore' - """)) - mappings = {} - for row in result.fetchall(): - mappings[row.field_id] = { - "maps_to": row.maps_to, - "value_for_true": row.value_for_true - } - return mappings - - -async def sync_resos_data( - from_date: date, - to_date: date, - triggered_by: str = "scheduler" -): - """ - Sync restaurant bookings from Resos. - """ - logger.info(f"Starting Resos sync from {from_date} to {to_date}") - - db = next(iter([SyncSessionLocal()])) - - try: - # Log sync start - db.execute( - text(""" - INSERT INTO sync_log (sync_type, source, started_at, status, date_from, date_to, triggered_by) - VALUES ('bookings', 'resos', NOW(), 'running', :from_date, :to_date, :triggered_by) - """), - {"from_date": from_date, "to_date": to_date, "triggered_by": triggered_by} - ) - db.commit() - - # Load Resos credentials from database - resos_creds = load_resos_credentials(db) - if not resos_creds.get('api_key'): - raise Exception("Resos API key not configured in database") - - # Load custom field mappings from database - cf_mappings = load_resos_custom_field_mappings(db) - logger.info(f"Loaded {len(cf_mappings)} Resos custom field mappings") - - async with ResosClient(api_key=resos_creds['api_key']) as client: - # Test connection - if not await client.test_connection(): - raise Exception("Resos connection failed") - - # Fetch bookings - bookings = await client.get_bookings(from_date, to_date) - logger.info(f"Fetched {len(bookings)} bookings from Resos") - - records_created = 0 - - for booking in bookings: - # Parse guest info - guest = booking.get("guest", {}) - resos_id = booking.get("_id") - booking_date = booking.get("date") - status = booking.get("status") - - # Extract custom fields using configured mappings - custom_fields = booking.get("customFields", []) - is_hotel_guest = None - is_dbb = None - is_package = None - hotel_booking_number = None - allergies = None - - for cf in custom_fields: - # Get field ID - Resos may use 'id' or '_id' - field_id = cf.get("id") or cf.get("_id") or cf.get("fieldId") - # For radio/checkbox fields, use multipleChoiceValueName (human-readable label) - # Fall back to value field for text fields - field_value_label = cf.get("multipleChoiceValueName") or cf.get("value") - field_value = cf.get("value") - - # Check if this field has a configured mapping - if field_id and field_id in cf_mappings: - mapping = cf_mappings[field_id] - maps_to = mapping["maps_to"] - value_for_true = mapping.get("value_for_true") - - # Debug logging for matched mappings (first 5 records only) - if records_created < 5: - logger.info(f"Matched mapping: field_id={field_id}, maps_to={maps_to}, label={field_value_label}, value_for_true={value_for_true}") - - if maps_to == "hotel_guest": - # For boolean fields, check label against value_for_true - if value_for_true: - is_hotel_guest = str(field_value_label) == str(value_for_true) - else: - is_hotel_guest = str(field_value_label).lower() in ("yes", "true", "1") - elif maps_to == "dbb": - if value_for_true: - is_dbb = str(field_value_label) == str(value_for_true) - else: - is_dbb = str(field_value_label).lower() in ("yes", "true", "1") - elif maps_to == "package": - if value_for_true: - is_package = str(field_value) == str(value_for_true) - else: - is_package = str(field_value).lower() in ("yes", "true", "1") - elif maps_to == "booking_number": - hotel_booking_number = str(field_value) if field_value else None - elif maps_to == "allergies": - allergies = str(field_value) if field_value else None - - # Fallback to keyword matching if no mapping configured - elif not cf_mappings: - field_name = cf.get("name", "").lower() - if "hotel" in field_name and "guest" in field_name: - is_hotel_guest = str(field_value).lower() in ("yes", "true", "1") - elif "dbb" in field_name or "dinner bed breakfast" in field_name: - is_dbb = str(field_value).lower() in ("yes", "true", "1") - elif "package" in field_name: - is_package = str(field_value).lower() in ("yes", "true", "1") - elif "booking" in field_name and "number" in field_name: - hotel_booking_number = str(field_value) if field_value else None - elif "allerg" in field_name: - allergies = str(field_value) if field_value else None - - # Upsert booking with full data - db.execute( - text(""" - INSERT INTO resos_bookings ( - resos_id, booking_date, booking_time, covers, - status, source, opening_hour_id, table_name, table_area, - is_hotel_guest, is_dbb, is_package, hotel_booking_number, allergies, - notes, fetched_at - ) VALUES ( - :resos_id, :booking_date, :booking_time, :covers, - :status, :source, :opening_hour_id, :table_name, :table_area, - :is_hotel_guest, :is_dbb, :is_package, :hotel_booking_number, :allergies, - :notes, NOW() - ) - ON CONFLICT (resos_id) DO UPDATE SET - status = :status, - covers = :covers, - is_hotel_guest = COALESCE(:is_hotel_guest, resos_bookings.is_hotel_guest), - is_dbb = COALESCE(:is_dbb, resos_bookings.is_dbb), - is_package = COALESCE(:is_package, resos_bookings.is_package), - fetched_at = NOW() - """), - { - "resos_id": resos_id, - "booking_date": booking_date, - "booking_time": booking.get("time"), - "covers": booking.get("people"), - "status": status, - "source": booking.get("source"), - "opening_hour_id": booking.get("openingHourId"), - "table_name": booking.get("tables", [{}])[0].get("name") if booking.get("tables") else None, - "table_area": booking.get("tables", [{}])[0].get("area", {}).get("name") if booking.get("tables") else None, - "is_hotel_guest": is_hotel_guest, - "is_dbb": is_dbb, - "is_package": is_package, - "hotel_booking_number": hotel_booking_number, - "allergies": allergies, - "notes": str(booking.get("restaurantNotes", [])) - } - ) - records_created += 1 - - # Queue date for aggregation - if booking_date: - db.execute( - text(""" - INSERT INTO aggregation_queue (date, source, reason, booking_id) - VALUES (:date, 'resos', :reason, :booking_id) - ON CONFLICT (date, source, booking_id) DO UPDATE SET - queued_at = NOW(), - aggregated_at = NULL - """), - { - "date": booking_date, - "reason": f"booking_{status.lower() if status else 'modified'}", - "booking_id": str(resos_id) - } - ) - - db.commit() - - # Update sync log - db.execute( - text(""" - UPDATE sync_log - SET completed_at = NOW(), status = 'success', - records_fetched = :fetched, records_created = :created - WHERE id = ( - SELECT id FROM sync_log - WHERE source = 'resos' AND status = 'running' - ORDER BY started_at DESC LIMIT 1 - ) - """), - {"fetched": len(bookings), "created": records_created} - ) - db.commit() - - logger.info(f"Resos sync completed: {records_created} records processed") - - except Exception as e: - logger.error(f"Resos sync failed: {e}") - db.execute( - text(""" - UPDATE sync_log - SET completed_at = NOW(), status = 'failed', error_message = :error - WHERE id = ( - SELECT id FROM sync_log - WHERE source = 'resos' AND status = 'running' - ORDER BY started_at DESC LIMIT 1 - ) - """), - {"error": str(e)} - ) - db.commit() - raise - finally: - db.close() - - async def sync_newbook_occupancy_report( from_date: date, to_date: date, diff --git a/backend/scheduler.py b/backend/scheduler.py index 3eb2647..3401ac2 100644 --- a/backend/scheduler.py +++ b/backend/scheduler.py @@ -8,7 +8,6 @@ from sqlalchemy import text from jobs.data_sync import ( sync_newbook_data, - sync_resos_data, sync_newbook_occupancy_report, sync_newbook_earned_revenue ) @@ -23,6 +22,7 @@ from jobs.weekly_forecast_snapshot import run_weekly_forecast_snapshot from jobs.fetch_current_rates import run_fetch_current_rates from jobs.scrape_booking_rates import run_scheduled_booking_scrape_async from jobs.ai_insights import run_ai_insights_generation +from jobs.weather_sync import run_weather_sync from database import SyncSessionLocal logger = logging.getLogger(__name__) @@ -108,15 +108,6 @@ async def run_scheduled_resos_bookings_sync(): logger.info("Scheduled Resos bookings sync skipped (disabled in settings)") -async def run_scheduled_resos_sync(): - """Wrapper to check if Resos sync is enabled before running""" - if is_sync_enabled("resos"): - logger.info("Running scheduled Resos sync") - await sync_resos_data(triggered_by="scheduler") - else: - logger.info("Scheduled Resos sync skipped (disabled in settings)") - - async def run_scheduled_occupancy_report_sync(): """Wrapper to run occupancy report sync (uses dedicated occupancy enabled flag)""" from datetime import date, timedelta @@ -181,21 +172,6 @@ def reschedule_sync_jobs(): ) logger.info(f" Resos bookings sync scheduled for {rsb_hour:02d}:{rsb_min:02d}") - # Resos sync - uses general sync_schedule_time for now - rs_time = get_config_value("sync_schedule_time", "05:05") - try: - rs_hour, rs_min = int(rs_time.split(':')[0]), int(rs_time.split(':')[1]) - except: - rs_hour, rs_min = 5, 5 - scheduler.add_job( - run_scheduled_resos_sync, - CronTrigger(hour=rs_hour, minute=rs_min), - id="resos_sync", - name=f"Daily Resos Sync ({rs_hour:02d}:{rs_min:02d})", - replace_existing=True - ) - logger.info(f" Resos sync scheduled for {rs_hour:02d}:{rs_min:02d}") - # Newbook occupancy report occ_hour, occ_min = get_sync_time("newbook_occupancy", 5, 8) scheduler.add_job( @@ -356,6 +332,17 @@ def start_scheduler(): ) logger.info(f" AI Insights scheduled for {ai_hour:02d}:{ai_min:02d}") + # Weather sync - Daily at 05:15 (last 7 days, catches ERA5 corrections) + wx_hour, wx_min = get_sync_time("weather", 5, 15) + scheduler.add_job( + run_weather_sync, + CronTrigger(hour=wx_hour, minute=wx_min), + id="weather_sync", + name=f"Daily Weather Sync ({wx_hour:02d}:{wx_min:02d})", + replace_existing=True + ) + logger.info(f" Weather sync scheduled for {wx_hour:02d}:{wx_min:02d}") + scheduler.start() logger.info("Scheduler started successfully") diff --git a/backend/schema.sql b/backend/schema.sql index 7d7297d..aab5d8f 100644 --- a/backend/schema.sql +++ b/backend/schema.sql @@ -59,13 +59,11 @@ INSERT INTO system_config (config_key, description) VALUES ('newbook_username', 'Newbook Username'), ('newbook_password', 'Newbook Password'), ('newbook_region', 'Newbook Region Code'), -('resos_api_key', 'Resos API Key'), ('total_rooms', 'Total number of hotel rooms'), ('hotel_name', 'Hotel/Property Name'), ('timezone', 'Local timezone (e.g., Europe/London)'), ('accommodation_vat_rate', 'VAT rate for accommodation (e.g., 0.20 for 20%)'), ('sync_newbook_enabled', 'Enable automatic Newbook sync (true/false)'), -('sync_resos_enabled', 'Enable automatic Resos sync (true/false)'), ('sync_schedule_time', 'Time for daily sync (HH:MM format)'), ('sync_newbook_bookings_enabled', 'Enable automatic Newbook bookings data sync (true/false)'), ('sync_newbook_bookings_type', 'Newbook bookings sync type (incremental/full)'), @@ -85,7 +83,6 @@ UPDATE system_config SET config_value = '80' WHERE config_key = 'total_rooms' AN UPDATE system_config SET config_value = 'Europe/London' WHERE config_key = 'timezone' AND config_value IS NULL; UPDATE system_config SET config_value = '0.20' WHERE config_key = 'accommodation_vat_rate' AND config_value IS NULL; UPDATE system_config SET config_value = 'false' WHERE config_key = 'sync_newbook_enabled' AND config_value IS NULL; -UPDATE system_config SET config_value = 'false' WHERE config_key = 'sync_resos_enabled' AND config_value IS NULL; UPDATE system_config SET config_value = '05:00' WHERE config_key = 'sync_schedule_time' AND config_value IS NULL; UPDATE system_config SET config_value = 'false' WHERE config_key = 'sync_newbook_bookings_enabled' AND config_value IS NULL; UPDATE system_config SET config_value = 'incremental' WHERE config_key = 'sync_newbook_bookings_type' AND config_value IS NULL;