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:
parent
1a0b18f06e
commit
fc15bc5d9a
3 changed files with 14 additions and 273 deletions
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue