def get_projects(): """Since the current project function in PyToggl doesn't work, I made my own hacky version. Gets all projects assigned to a client and returns it as a list""" toggl = Toggl() toggl.setAPIKey(TOGGL_TOKEN) clients = toggl.getClients() toggl_projects = [] for client in clients: projects = toggl.getClientProjects(client["id"]) if projects != None: for project in projects: toggl_projects.append(project) return toggl_projects
def main(args): pp = pprint.PrettyPrinter(indent=4) # create a Toggl object and set our API key toggl_account = Toggl() toggl_account.setAPIKey(args.toggl_key) toggl_tz_str = toggl_account.request( "https://www.toggl.com/api/v8/me")['data']['timezone'] toggl_tz = pytz.timezone(toggl_tz_str) # figure out what ranges to sync for if args.days: edate = datetime.today().replace( hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1) sdate = edate - timedelta(days=abs(args.days) + 1) elif args.daterange: dates = [dateparser.parse(x) for x in args.daterange] if len(dates) < 2: dates.append(datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)) dates = sorted(dates) sdate = dates[0] edate = dates[1] + timedelta(days=1) else: edate = datetime.today().replace( hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1) sdate = edate + timedelta(days=-365) sdate_aw = toggl_tz.localize(sdate) edate_aw = toggl_tz.localize(edate) # do some fancy date windowing required for retrieving tasks from toggl toggl_dateranges = [] chunks = (edate - sdate).days // 180 partials = (edate - sdate).days % 180 for i in range((edate - sdate).days // 180): toggl_dateranges.append([ sdate + timedelta(days=i * 180), sdate + timedelta(days=(i + 1) * 180 - 1) ]) if partials: toggl_dateranges.append([ sdate + timedelta(days=chunks * 180), sdate + timedelta(days=chunks * 180 + partials) ]) toggl_dateranges = [[toggl_tz.localize(dr[0]), toggl_tz.localize(dr[1])] for dr in toggl_dateranges] # collect toggl entries toggl_entries = [] toggl_clients = toggl_account.getClients() toggl_workspaces = toggl_account.getWorkspaces() toggl_projects = [] for client in toggl_clients: toggl_projects = toggl_projects + toggl_account.getClientProjects( client['id']) for wid in [w['id'] for w in toggl_workspaces]: for dr in toggl_dateranges: entries_left = 1 page = 1 while entries_left > 0: entries = toggl_account.request( Endpoints.REPORT_DETAILED, { 'workspace_id': wid, 'user_agent': 'https://github.com/a3ng7n/timesheet-sync', 'page': page, 'since': dr[0], 'until': dr[1] }) toggl_entries = entries['data'] + toggl_entries entries_left = entries['total_count'] - entries[ 'per_page'] if page == 1 else entries_left - entries[ 'per_page'] page += 1 task_names = [{ 'id': str(x['pid']) + x['description'], 'pid': x['pid'], 'description': x['description'] } for x in toggl_entries] toggl_task_names = list({x['id']: x for x in task_names}.values()) toggl_task_names = sorted(toggl_task_names, key=lambda k: k['pid'] if k['pid'] else 0) for i, t in enumerate(toggl_task_names): t['id'] = i # collect harvest entries harvest_account = harvest.Harvest(uri=args.harvest_url, account_id=args.harvest_account_id, personal_token=args.harvest_key) try: harvest_user_id = [ x['user']['id'] for x in harvest_account.users if x['user']['email'] == args.harvest_email ].pop() except IndexError: print("Could not find user with email address: {0}".format( args.harvest_email)) raise tasks = [] harvest_entries = [] harvest_clients = harvest_account.clients() for client in harvest_clients: harvest_projects = harvest_account.projects_for_client( client['client']['id']) for project in harvest_projects: some_entries = harvest_account.timesheets_for_project( project['project']['id'], start_date=sdate_aw.isoformat(), end_date=edate_aw.isoformat()) for e in some_entries: e['day_entry']['client_id'] = [ y['project']['client_id'] for y in harvest_projects if y['project']['id'] == e['day_entry']['project_id'] ].pop() harvest_entries = harvest_entries + some_entries tasks = tasks + [{ **x['task_assignment'], 'client_id': client['client']['id'] } for x in harvest_account.get_all_tasks_from_project( project['project']['id'])] task_names = [{**x, 'id': str(x['client_id'])\ + str(x['project_id'])\ + str(x['task_id'])} for x in tasks] harvest_task_names = list({x['id']: x for x in task_names}.values()) harvest_task_names = sorted(harvest_task_names, key=lambda k: k['client_id']) for i, t in enumerate(harvest_task_names): t['id'] = i task_association = task_association_config(toggl_account, toggl_task_names, harvest_account, harvest_task_names) # organize toggl entries by dates worked delta = edate - sdate dates = [sdate + timedelta(days=i) for i in range(delta.days + 1)] combined_entries_dict = {} for date in dates: # collect entries from either platform on the given date from_toggl = [ x for x in toggl_entries if ((dateutil.parser.parse(x['start']).astimezone(toggl_tz) > toggl_tz.localize(date)) and ( dateutil.parser.parse(x['start']).astimezone(toggl_tz) <= toggl_tz.localize(date) + timedelta(days=1))) ] from_harvest = [ x['day_entry'] for x in harvest_entries if dateutil.parser.parse(x['day_entry']['spent_at']).astimezone( toggl_tz) == toggl_tz.localize(date) ] if from_toggl or from_harvest: combined_entries_dict[date] = { 'toggl': { 'raw': from_toggl, 'tasks': {} }, 'harvest': { 'raw': from_harvest, 'tasks': {} } } # organize raw entries into unique tasks, and total time for that day for platform in combined_entries_dict[date].keys(): for entry in combined_entries_dict[date][platform]['raw']: if platform == 'toggl': if entry['pid'] not in combined_entries_dict[date][ platform]['tasks'].keys(): combined_entries_dict[date][platform]['tasks'][ entry['pid']] = {} try: combined_entries_dict[date][platform]['tasks'][ entry['pid']][entry[ 'description']] += entry['dur'] / 3600000 except KeyError: combined_entries_dict[date][platform]['tasks'][ entry['pid']][entry[ 'description']] = entry['dur'] / 3600000 else: try: combined_entries_dict[date][platform]['tasks'][ entry['notes']] += entry['hours'] except KeyError: combined_entries_dict[date][platform]['tasks'][ entry['notes']] = entry['hours'] # add data to harvest add_to_harvest = [] for date, entry in combined_entries_dict.items(): if entry['toggl']['tasks'] and not entry['harvest']['tasks']: for pid in entry['toggl']['tasks'].keys(): for task in entry['toggl']['tasks'][pid].keys(): for hidpair in list( zip( task_association[pid][task] ['harvest_project_id'], task_association[pid] [task]['harvest_task_id'])): add_to_harvest.append({ 'project_id': hidpair[0], 'task_id': hidpair[1], 'spent_at': date.date().isoformat(), 'hours': round(entry['toggl']['tasks'][pid][task], 2), 'notes': task }) print("The following Toggl entries will be added to Harvest:") pp.pprint(add_to_harvest) if input("""Add the entries noted above to harvest? (y/n)""").lower() in ( 'y', 'yes'): for entry in add_to_harvest: pp.pprint( harvest_account.add_for_user(user_id=harvest_user_id, data=entry)) else: print('aborted') exit(1) print('done!') exit(0)