Пример #1
0
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
Пример #2
0
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)