def prepare_deals(new_deals): bitrix_deals = get_bitrix_data(_deal_list, params={'select': ['UF_*', '*']}) to_add = [] to_remove = [] to_update = [] id_key = deal_fields_mapping.get('id booking (source)') for deal in new_deals: btx_deal = find_dict_in_list(lst=bitrix_deals, key=id_key, value=deal[id_key]) if btx_deal: # a workaround for difference of precisions btx_deal['OPPORTUNITY'] = None if not btx_deal[ 'OPPORTUNITY'] else float(btx_deal['OPPORTUNITY']) if are_differ(deal, btx_deal, Cfg.get('btx_deal_mutable_fields')): deal['ID'] = btx_deal['ID'] to_update.append(deal) bitrix_deals.remove(btx_deal) else: to_add.append(deal) for old_deal in bitrix_deals: to_remove.append(old_deal['ID']) return to_add, to_update, to_remove
def advanced_request(target, ids=None, fields=None, params={}): # e.g. target is account and url will be accounts_base_url url_base = Cfg.get('bks_' + target + 's_base_url') params['per_page'] = Cfg.get('bks_x_per_page') if ids: params['id'] = ids if fields: params['fields'] = fields req = request_data(url_base, params) data = req[target + 's'] total = int(req['meta']['X-Total-Pages']) if total > 1: params['per_page'] = req['meta']['X-Per-Page'] for i in range(2, total + 1): params['page'] = i data += request_data(url_base, params)[target + 's'] return data
def upload_products(to_add, to_update, to_delete): logging.info('Updating Bitrix products...') if Cfg.get('btx_remove_old_rows'): delete_bitrix_fields(_product_remove, to_delete, 'products') update_bitrix_fields(_product_update, to_update, 'products') add_bitrix_fields(_product_add, to_add, 'products') logging.info('Updated: {}'.format(len(to_update))) logging.info('Added: {}'.format(len(to_add))) logging.info('Deleted: {}'.format(len(to_delete))) logging.info( 'Note: The items above may not been delete if remove_old_rows flag is false' )
def upload_deals(to_add, to_update, to_delete): get_contact_ids() get_product_ids() logging.info('Processing Bitrix deals...') if Cfg.get('btx_remove_old_rows'): delete_bitrix_fields(_deal_remove, to_delete, 'deals') update_bitrix_fields(_deal_update, to_update, 'deals') add_bitrix_fields(_deal_add, to_add, 'deals', add_contacts_to_deals) logging.info('Updated: {}'.format(len(to_update))) logging.info('Added: {}'.format(len(to_add))) logging.info('Deleted: {}'.format(len(to_delete))) logging.info( 'Note: The items above may not been delete if remove_old_rows flag is false' )
def get_products_from_db(db_data): products = [] pm = product_fields_mapping for rental in db_data['rentals']: products.append({ 'NAME': rental['name'], 'SECTION_ID': Cfg.get('btx_product_section_id'), pm['rental_id']: rental['id'], pm['street']: rental['address1'], pm['city']: rental['city'], 'DESCRIPTION': '{}, {} {}'.format(rental['contact_name'], rental['address1'], rental['city']) }) return products
def get_stage(start_at, end_at, status): # Deal stage: is something like status. # By default it is "booked". # If the reservation is 32days ahead or less we change it to :"payed". # If start_at is today, then we change it to "Arrival". # If start_at<today<end_at then value is "staying", and if end_at=today then "departing" stage_id = STAGE.BOOKED if status == 'Booked' else STAGE.CANCELED if status == 'Canceled' else None reserve_days_ahead = (start_at - datetime.now()).days try: if reserve_days_ahead == 0: stage_id = STAGE.ARRIVED elif start_at <= datetime.now() <= end_at: stage_id = STAGE.STAY elif datetime.now() > end_at: stage_id = STAGE.DEPARTED elif 0 < reserve_days_ahead <= Cfg.get('btx_payed_status_interval'): stage_id = STAGE.PAYED except Exception as e: logging.info('Problem when trying to count stage_id: {}'.format( str(e))) return stage_id
def are_differ_products(bookingsync_record, bitrix_record): for _key, val1 in bookingsync_record.items(): if _key not in Cfg.get('btx_product_mutable_fields'): continue val2 = bitrix_record[_key] if not val1 and not val2: continue if type(val2) == dict: if str(val1) != str(val2['value']): return True elif str(val1) != str(val2): try: logging.info('for {} key, differ {} and {}'.format( _key, val1, val2)) except UnicodeEncodeError: logging.info('for {} key, something is differ') return True return False
def prepare_contacts(new_clients): bitrix_contacts = get_bitrix_data( _contact_list, params={'select': ['*', 'UF_*', 'PHONE', 'EMAIL']}) to_add = [] to_remove = [] to_update = [] for client in new_clients: btx_contact = find_dict_in_list(bitrix_contacts, contact_client_id_key, client['ID']) if btx_contact: if 'PHONE' not in btx_contact: btx_contact['PHONE'] = [{'VALUE': '', 'VALUE_TYPE': 'OTHER'}] if 'EMAIL' not in btx_contact: btx_contact['EMAIL'] = [{'VALUE': '', 'VALUE_TYPE': 'OTHER'}] assert type(btx_contact) == dict if are_differ(client, btx_contact, Cfg.get('btx_contact_mutable_fields')): for i, p in enumerate(client['PHONE']): p['ID'] = btx_contact['PHONE'][i]['ID'] for i, p in enumerate(client['EMAIL']): p['ID'] = btx_contact['EMAIL'][i]['ID'] client['ID'] = btx_contact['ID'] to_update.append(client) bitrix_contacts.remove(btx_contact) else: to_add.append(client) for old_deal in bitrix_contacts: to_remove.append(old_deal['ID']) return to_add, to_update, to_remove
def run_bitrix(): logging.info('Uploading bitrix data...') db = MySQL(host=Cfg.get('db_host'), port=int(Cfg.get('db_port')), user=Cfg.get('db_user'), password=Cfg.get('db_password'), db=Cfg.get('db_name')) db_data = get_db_data(db, Cfg.get('db_tables'), charset='utf8') rentals = get_products_from_db(db_data) to_add, to_update, to_delete = prepare_products(rentals) upload_products(to_add, to_update, to_delete) clients = get_clients_from_db(db_data) to_add, to_update, to_delete = prepare_contacts(clients) upload_contacts(to_add, to_update, to_delete) deals = get_deals_from_db(db_data) to_add, to_update, to_delete = prepare_deals(deals) upload_deals(to_add, to_update, to_delete)
def run_bookingsync(): t_total = time.time() logging.info('Obtaining data from bookingsync...') t_bfe = time.time() bookings_fee = advanced_request('bookings_fee', params={ 'from': '20111101', 'status': 'booked,unavailable,tentative' }) logging.info('Obtained Bookings fee data in {} sec'.format(time.time() - t_bfe)) t_bkg = time.time() bookings = advanced_request('booking', params={ 'from': '20111101', 'status': 'booked,unavailable,tentative', 'include_canceled': True }) logging.info('Obtained Bookings data in {} sec'.format(time.time() - t_bkg)) t_cl = time.time() clients = advanced_request('client', params={'from': '20111101'}) logging.info('Obtained Clients data in {} sec'.format(time.time() - t_cl)) t_ren = time.time() rentals = advanced_request('rental', params={'from': '20111101'}) logging.info('Obtained Rentals data in {} sec'.format(time.time() - t_ren)) t_prc = time.time() data = { 'clients': get_clients(clients), 'rentals': get_rentals(rentals), 'bookings': get_bookings(bookings), 'bookings_fee': get_bookings_fee(bookings_fee) } data['bookings_split'] = get_bookings_splitted(data['bookings_fee'], data['bookings'], data['rentals']) for b in data['bookings']: if b['client_id'] and not find_dict_in_list(data['clients'], 'id', b['client_id']): # logging.info('Invalid foreign key client_id {}'.format(b['client_id'])) b['client_id'] = None if b['rental_id'] and not find_dict_in_list(data['rentals'], 'id', b['rental_id']): # logging.info('Invalid foreign key renal_id {}'.format(b['rental_id'])) b['rental_id'] = None for bf in data['bookings_fee']: if bf['booking_id'] and not find_dict_in_list(data['bookings'], 'id', bf['booking_id']): # logging.info('Invalid foreign key booking_id {}'.format(bf['booking_id'])) bf['booking_id'] = None logging.info('Processed obtained data in {} sec'.format(time.time() - t_prc)) logging.info('Completed in {} second.'.format(time.time() - t_total)) db = MySQL(host=Cfg.get('db_host'), port=int(Cfg.get('db_port')), user=Cfg.get('db_user'), password=Cfg.get('db_password'), db=Cfg.get('db_name')) write_data_to_db(db, data, Cfg.get('db_tables'))
def get_bookings(bookings): my_bookings = [] t_ren = time.time() sources = advanced_request('source', params={ 'from': '20111101', 'fields': 'id,name' }) logging.info('Obtained Sources data in {} sec'.format(time.time() - t_ren)) t_ren = time.time() comments = advanced_request('booking_comment', params={ 'from': '20111101', 'fields': 'id,content' }) logging.info('Obtained Comments data in {} sec'.format(time.time() - t_ren)) t_ren = time.time() accounts = request_data(Cfg.get('bks_accounts_base_url'), params={'fields': 'id,business_name'}) logging.info('Obtained Accounts data in {} sec'.format(time.time() - t_ren)) for b in bookings: if b['status'] == 'Canceled': if (to_datetime(b['start_at']).date() - to_datetime(b['canceled_at']).date() ).days > Cfg.get('btx_payed_status_interval'): continue my_booking = { 'id': int(b['id']), 'client_id': to_int(b['links']['client']), 'rental_id': to_int(b['links']['rental']), 'start_at': to_datetime(b['start_at']), 'end_at': to_datetime(b['end_at']), 'created_at': to_datetime(b['created_at']), 'canceled_at': to_datetime(b['canceled_at']), 'balance_due_at': to_datetime(b['balance_due_at']), 'tentative_expires_at': to_datetime(b['tentative_expires_at']), 'updated_at': to_datetime(b['updated_at']), 'contract_updated_at': to_datetime(b['contract_updated_at']), 'status': b['status'], 'reference': b['reference'], 'booked': b['booked'], 'unavailable': b['unavailable'], 'initial_price': to_float(b['initial_price']), 'initial_rental_price': to_float(b['initial_rental_price']), 'channel_price': to_float(b['channel_price']), 'discount': b['discount'], 'final_rental_price': to_float(b['final_rental_price']), 'final_price': to_float(b['final_price']), 'paid_amount': to_float(b['paid_amount']), 'currency': b['currency'], 'notes': b['notes'], 'damage_deposit': to_float(b['damage_deposit']), 'charge_damage_deposit_on_arrival': to_float(b['charge_damage_deposit_on_arrival']), 'adults': to_int(b['adults']), 'children': to_int(b['children']), 'bookings_payments_count': to_int(b['bookings_payments_count']), 'review_requests_count': to_int(b['review_requests_count']), 'locked': b['locked'], 'cancelation_reason': b['cancelation_reason'], 'expected_checkin_time': b['expected_checkin_time'], 'expected_checkout_time': b['expected_checkout_time'], 'payment_url': b['payment_url'], 'rental_payback_to_owner': to_float(b['rental_payback_to_owner']), 'final_payback_to_owner': to_float(b['final_payback_to_owner']), 'commission': to_float(b['commission']), 'door_key_code': b['door_key_code'], 'payment_left_to_collect': to_float(b['payment_left_to_collect']), 'owned_by_app': b['owned_by_app'], 'account_id': to_int(b['links']['account']), 'probability_win': None, 'source': b['links']['source'] if b['links']['source'] else 'Praguestars.com' } if my_booking['source']: source = find_dict_in_list(sources, 'id', my_booking['source']) if source: my_booking['source'] = source['name'] if my_booking['start_at']: days_interval = (my_booking['start_at'] - datetime.now()).days my_booking['probability_win'] = to_int( Cfg.get_interval_prob(days_interval)) # get comments comment_str = '' for comment_id in b['links']['booking_comments']: comment = find_dict_in_list(comments, 'id', comment_id) if comment: comment_str += comment['content'] + '\n' my_booking['comments'] = comment_str my_bookings.append(my_booking) # get account name for b in my_bookings: for a in accounts['accounts']: if len(a) > 0 and a['id'] == b['account_id']: b['account'] = a['business_name'] return my_bookings
from bookingsync_utils import * from mysql_wrapper import MySQL from utilities import Cfg from utilities import find_all_dicts_in_list from utilities import find_dict_in_list from utilities import is_number from utilities import to_float from utilities import to_int from utilities import write_data_to_db dir_path = os.path.dirname(os.path.realpath(__file__)) sys.path.append(os.path.join(dir_path, "drivers")) _json_file = dir_path + os.sep + '..' + os.sep + Cfg.get('bks_token_file') _client_id = Cfg.get('bks_client_id') _client_secret = Cfg.get('bks_client_secret') _redirect_uri = Cfg.get('bks_redirect_uri') _native_date_format = '%Y-%m-%dT%H:%M:%SZ' with open(_json_file, 'r') as f: _token = json.load(f) def update_token(): global _token req = requests.post('https://www.bookingsync.com/oauth/token', params={ 'client_id': _client_id, 'client_secret': _client_secret,
import time from datetime import datetime from datetime import date from multidimensional_urlencode import urlencode import requests import tqdm from mysql_wrapper import MySQL from utilities import Cfg from utilities import find_dict_in_list from utilities import get_db_data dir_path = os.path.dirname(os.path.realpath(__file__)) _json_file = dir_path + os.sep + '..' + os.sep + Cfg.get('btx_token_file') _client_id = Cfg.get('btx_client_id') _client_secret = Cfg.get('btx_client_secret') _redirect_uri = 'blabla' _main_url = 'https://praguestars.bitrix24.com/rest/METHOD' _contact_fields = 'crm.contact.fields' _contact_list = 'crm.contact.list' _contact_remove = 'crm.contact.delete' _contact_update = 'crm.contact.update' _contact_add = 'crm.contact.add' _deal_add_contact = 'crm.deal.contact.add' _deal_add_product = 'crm.deal.productrows.set' _deal_fields = 'crm.deal.fields' _deal_list = 'crm.deal.list'
from datetime import timedelta from utilities import Cfg import logging import re from utilities import to_float fee_names = Cfg.get('fee_mapping') a_day = timedelta(days=1) def is_same_month(end, start): return start.month == end.month and start.year == end.year def get_fee_name(fee): for loc in fee['name'].keys(): if fee['name'][loc]: try: return fee_names[fee['name'][loc].lower()] except (KeyError, ValueError): continue logging.warning('No tax name in: '.format(fee)) return '' def get_fees_for_splitted_booking(booking_split, fees, bkg, portion=1): # initialize fee fields for fee_name in set(fee_names.values()): booking_split[fee_name] = '' # try to get fee info from booking comments, as some booking_fee data is not available via api