180 lines
5.9 KiB
Ruby
180 lines
5.9 KiB
Ruby
module Captain
|
|
module Reservations
|
|
class SyncService
|
|
PLUG_PLAY_API_BASE = 'https://oxpi.com.br/api/PlugPlay/api/Reserva'
|
|
|
|
def initialize(unit)
|
|
@unit = unit
|
|
@account = unit.account
|
|
@inbox = unit.inbox # Assuming unit is linked to an inbox, or we fallback
|
|
end
|
|
|
|
def perform
|
|
return unless @unit.reservations_sync_enabled?
|
|
return unless @unit.plug_play_id.present? && @unit.plug_play_token.present?
|
|
|
|
page = 1
|
|
loop do
|
|
reservations_data = fetch_page(page)
|
|
break if reservations_data.empty?
|
|
|
|
reservations_data.each do |reservation_data|
|
|
process_reservation(reservation_data)
|
|
end
|
|
|
|
page += 1
|
|
# Safety break to avoid infinite loops in case of API issues
|
|
break if page > 50
|
|
end
|
|
|
|
@unit.update(last_synced_at: Time.current)
|
|
end
|
|
|
|
private
|
|
|
|
def fetch_page(page)
|
|
url = "#{PLUG_PLAY_API_BASE}?exibicao=0&pagina=#{page}"
|
|
response = HTTParty.get(url, headers: headers)
|
|
|
|
if response.success?
|
|
begin
|
|
JSON.parse(response.body)
|
|
rescue StandardError
|
|
[]
|
|
end
|
|
else
|
|
Rails.logger.error "PlugPlay Sync Error: #{response.code} - #{response.body}"
|
|
[]
|
|
end
|
|
end
|
|
|
|
def headers
|
|
{
|
|
'PLUG-PLAY-ID' => @unit.plug_play_id,
|
|
'PLUG-PLAY-TOKEN' => @unit.plug_play_token,
|
|
'Content-Type' => 'application/json'
|
|
}
|
|
end
|
|
|
|
def process_reservation(data)
|
|
external_id = data['id']
|
|
return if external_id.blank?
|
|
|
|
reservation = @unit.captain_reservations.find_or_initialize_by(integracao_id: external_id)
|
|
|
|
# Resolve Contact
|
|
contact = find_or_create_contact(data)
|
|
|
|
# Map Attributes
|
|
reservation.account = @account
|
|
reservation.inbox = @inbox || @account.inboxes.first # Fallback if unit has no inbox
|
|
reservation.contact = contact
|
|
reservation.contact_inbox = contact.contact_inboxes.find_by(inbox: reservation.inbox)
|
|
|
|
# If contact_inbox missing (new contact created without association to this inbox), create it
|
|
if reservation.contact_inbox.nil?
|
|
reservation.contact_inbox = ContactInbox.create!(contact: contact, inbox: reservation.inbox, source_id: contact.id)
|
|
end
|
|
|
|
reservation.suite_identifier = data['suiteRef']
|
|
reservation.check_in_at = parse_date(data['dataInicio']) # Format: 2026-01-22T00:00:00
|
|
reservation.check_out_at = parse_date(data['saidaPrevistaOuNegociada'])
|
|
|
|
if reservation.suite_identifier.blank? || reservation.check_in_at.blank? || reservation.check_out_at.blank?
|
|
Rails.logger.warn "PlugPlay Sync Skip: missing suite/dates for reservation #{external_id}"
|
|
return
|
|
end
|
|
|
|
reservation.total_amount = data['totalAPagar']
|
|
|
|
# Status Mapping
|
|
reservation.status = map_status(data)
|
|
|
|
reservation.metadata ||= {}
|
|
reservation.metadata['raw_plug_play_data'] = data
|
|
reservation.metadata['guest_name'] = data['nome']
|
|
reservation.metadata['guest_email'] = data['email']
|
|
reservation.metadata['guest_phone'] = data['telefone']
|
|
reservation.metadata['notes'] = data['observacoes']
|
|
reservation.metadata['source_tag'] = @unit.reservation_source_tag if @unit.reservation_source_tag.present?
|
|
|
|
reservation.save!
|
|
rescue StandardError => e
|
|
if e.is_a?(ActiveRecord::RecordInvalid) && e.record
|
|
Rails.logger.error "Error syncing reservation #{data['id']}: #{e.record.errors.full_messages.join(', ')}"
|
|
Rails.logger.error "Reservation attrs: unit_id=#{@unit.id} inbox_id=#{reservation&.inbox_id} contact_id=#{reservation&.contact_id} contact_inbox_id=#{reservation&.contact_inbox_id} suite=#{reservation&.suite_identifier} check_in=#{reservation&.check_in_at} check_out=#{reservation&.check_out_at} status=#{reservation&.status}"
|
|
else
|
|
Rails.logger.error "Error syncing reservation #{data['id']}: #{e.message}"
|
|
end
|
|
end
|
|
|
|
def find_or_create_contact(data)
|
|
phone = normalize_phone_number(data['telefone'])
|
|
email = data['email']
|
|
name = data['nome']
|
|
|
|
contact = nil
|
|
|
|
# Try finding by phone
|
|
contact = @account.contacts.find_by_phone_number(phone) if phone.present?
|
|
|
|
# Try finding by email
|
|
contact = @account.contacts.find_by(email: email) if contact.nil? && email.present?
|
|
|
|
# Create if not found
|
|
if contact.nil?
|
|
contact = @account.contacts.create!(
|
|
name: name,
|
|
email: email,
|
|
phone_number: phone
|
|
)
|
|
end
|
|
|
|
contact
|
|
end
|
|
|
|
def normalize_phone_number(raw_phone)
|
|
digits = raw_phone.to_s.gsub(/[^\d]/, '')
|
|
return nil if digits.blank?
|
|
|
|
digits = "55#{digits}" if digits.length == 10 || digits.length == 11
|
|
|
|
return nil if digits.length < 10 || digits.length > 15
|
|
|
|
"+#{digits}"
|
|
end
|
|
|
|
def parse_date(date_string)
|
|
return nil if date_string.blank?
|
|
|
|
Time.zone.parse(date_string)
|
|
rescue StandardError
|
|
nil
|
|
end
|
|
|
|
def map_status(data)
|
|
# MVP Logic based on dates and 'cancelada'
|
|
return :cancelled if data['cancelada'] == true
|
|
|
|
check_in = parse_date(data['dataInicio'])
|
|
check_out = parse_date(data['saidaPrevistaOuNegociada'])
|
|
now = Time.current
|
|
|
|
return :scheduled unless check_in && check_out
|
|
|
|
if check_in.to_date == now.to_date
|
|
:scheduled # Or 'awaiting_checkin' if we want to be more specific, but MVP 'scheduled' is usually 'Entrada'
|
|
elsif now >= check_in && now < check_out
|
|
:active # 'Hospedada'
|
|
elsif now >= check_out
|
|
:completed # 'Saída' / checkout done
|
|
elsif now < check_in
|
|
:scheduled
|
|
else
|
|
:scheduled # Default
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|