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 json
|
||||||
import logging
|
import logging
|
||||||
|
|
@ -9,7 +9,6 @@ from typing import Optional, Dict, Set
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from database import SyncSessionLocal
|
from database import SyncSessionLocal
|
||||||
from services.newbook_client import NewbookClient
|
from services.newbook_client import NewbookClient
|
||||||
from services.resos_client import ResosClient
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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:
|
def load_gl_config(db) -> tuple:
|
||||||
"""
|
"""
|
||||||
Load GL code configuration for identifying breakfast/dinner items.
|
Load GL code configuration for identifying breakfast/dinner items.
|
||||||
|
|
@ -167,7 +159,7 @@ async def run_data_sync(
|
||||||
triggered_by: str = "scheduler"
|
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:
|
Args:
|
||||||
full_sync: If True, pulls all bookings. If False, only pulls changes since last sync.
|
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)
|
occ_to_date = date.today() + timedelta(days=365)
|
||||||
await sync_newbook_occupancy_report(occ_from_date, occ_to_date, triggered_by)
|
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")
|
logger.info("Data sync completed successfully")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Data sync failed: {e}")
|
logger.error(f"Data sync failed: {e}")
|
||||||
|
|
@ -546,236 +533,6 @@ async def sync_newbook_data(
|
||||||
db.close()
|
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(
|
async def sync_newbook_occupancy_report(
|
||||||
from_date: date,
|
from_date: date,
|
||||||
to_date: date,
|
to_date: date,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ from sqlalchemy import text
|
||||||
|
|
||||||
from jobs.data_sync import (
|
from jobs.data_sync import (
|
||||||
sync_newbook_data,
|
sync_newbook_data,
|
||||||
sync_resos_data,
|
|
||||||
sync_newbook_occupancy_report,
|
sync_newbook_occupancy_report,
|
||||||
sync_newbook_earned_revenue
|
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.fetch_current_rates import run_fetch_current_rates
|
||||||
from jobs.scrape_booking_rates import run_scheduled_booking_scrape_async
|
from jobs.scrape_booking_rates import run_scheduled_booking_scrape_async
|
||||||
from jobs.ai_insights import run_ai_insights_generation
|
from jobs.ai_insights import run_ai_insights_generation
|
||||||
|
from jobs.weather_sync import run_weather_sync
|
||||||
from database import SyncSessionLocal
|
from database import SyncSessionLocal
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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)")
|
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():
|
async def run_scheduled_occupancy_report_sync():
|
||||||
"""Wrapper to run occupancy report sync (uses dedicated occupancy enabled flag)"""
|
"""Wrapper to run occupancy report sync (uses dedicated occupancy enabled flag)"""
|
||||||
from datetime import date, timedelta
|
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}")
|
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
|
# Newbook occupancy report
|
||||||
occ_hour, occ_min = get_sync_time("newbook_occupancy", 5, 8)
|
occ_hour, occ_min = get_sync_time("newbook_occupancy", 5, 8)
|
||||||
scheduler.add_job(
|
scheduler.add_job(
|
||||||
|
|
@ -356,6 +332,17 @@ def start_scheduler():
|
||||||
)
|
)
|
||||||
logger.info(f" AI Insights scheduled for {ai_hour:02d}:{ai_min:02d}")
|
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()
|
scheduler.start()
|
||||||
logger.info("Scheduler started successfully")
|
logger.info("Scheduler started successfully")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,13 +59,11 @@ INSERT INTO system_config (config_key, description) VALUES
|
||||||
('newbook_username', 'Newbook Username'),
|
('newbook_username', 'Newbook Username'),
|
||||||
('newbook_password', 'Newbook Password'),
|
('newbook_password', 'Newbook Password'),
|
||||||
('newbook_region', 'Newbook Region Code'),
|
('newbook_region', 'Newbook Region Code'),
|
||||||
('resos_api_key', 'Resos API Key'),
|
|
||||||
('total_rooms', 'Total number of hotel rooms'),
|
('total_rooms', 'Total number of hotel rooms'),
|
||||||
('hotel_name', 'Hotel/Property Name'),
|
('hotel_name', 'Hotel/Property Name'),
|
||||||
('timezone', 'Local timezone (e.g., Europe/London)'),
|
('timezone', 'Local timezone (e.g., Europe/London)'),
|
||||||
('accommodation_vat_rate', 'VAT rate for accommodation (e.g., 0.20 for 20%)'),
|
('accommodation_vat_rate', 'VAT rate for accommodation (e.g., 0.20 for 20%)'),
|
||||||
('sync_newbook_enabled', 'Enable automatic Newbook sync (true/false)'),
|
('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_schedule_time', 'Time for daily sync (HH:MM format)'),
|
||||||
('sync_newbook_bookings_enabled', 'Enable automatic Newbook bookings data sync (true/false)'),
|
('sync_newbook_bookings_enabled', 'Enable automatic Newbook bookings data sync (true/false)'),
|
||||||
('sync_newbook_bookings_type', 'Newbook bookings sync type (incremental/full)'),
|
('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 = '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 = '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_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 = '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 = '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;
|
UPDATE system_config SET config_value = 'incremental' WHERE config_key = 'sync_newbook_bookings_type' AND config_value IS NULL;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue