Remove old Resos sync path (resos_bookings table)

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 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-13 21:07:47 +00:00
parent 1a0b18f06e
commit fc15bc5d9a
3 changed files with 14 additions and 273 deletions

View file

@ -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,