class CalendarSync: STATUS_MAP = { 'approved': 'confirmed', 'denied': 'tentative', 'declined': 'tentative', 'requested': 'tentative', 'canceled': 'cancelled', # This is what their API is 'cancelled': 'cancelled' # Just in case they fix it } def __init__(self): outer_config = Config() config = outer_config.get_config() try: token = config[Config.BAMBOO_SECTION]['token'] except KeyError: # No token - try getting it from username/password password = getpass() token = CalendarSync.get_api_key( config[Config.BAMBOO_SECTION]['company'], config[Config.BAMBOO_SECTION]['user'], password) outer_config.save_token(token) self.bamboohr_client = PyBambooHR( config[Config.BAMBOO_SECTION]['company'], token) self.gcal_client = GoogleCalendar( config[Config.GCAL_SECTION]['calendar_id']) self.employee_id = config[Config.BAMBOO_SECTION]['employee_id'] @staticmethod def get_api_key(company, user, password): logging.info('Obtaining API Key') payload = { 'user': user, 'password': password, 'applicationKey': '3fdb46cdade6088a442be908141eaaada89ff6c9' } url = 'https://api.bamboohr.com/api/gateway.php/%s/v1/login' % company r = requests.post(url, data=payload, headers={'Accept': "application/json"}) # I have no idea how long this key is valid for. Let's see when it fails if r.ok: return json.loads(r.text).get('key') else: raise Exception( 'Failed to get Access Token. Invalid username/password?') def get_time_off_requests(self): # Get events up to one year in the past start = (date.today() - timedelta(days=365)).isoformat() return self.bamboohr_client.get_time_off_requests( start_date=start, employee_id=self.employee_id) def update_calendar(self, time_off_requests): print('Updating/Creating %s items' % len(time_off_requests)) for booking in time_off_requests: event_id = 'emp' + booking['employeeId'] + 'id' + booking['id'] start = booking['start'] # Add an extra day, otherwise google will treat it as ending at the start of the last day end = (datetime.strptime(booking['end'], '%Y-%m-%d') + timedelta(days=1)).date().isoformat() status = booking['status']['status'] notes = 'Type: ' + booking['type']['name'] + '\n' try: notes += 'Notes:\n' + '\n'.join([ ' ' + str(k) + ': ' + str(v) for k, v in booking['notes'].items() ]) except AttributeError as e: pass summary = 'Time Booked off: ' + status.title() # Set the status to be what google calendars expents status = self.STATUS_MAP.get(status, 'tentative') logging.debug( 'Updating Booking: id: %s, start: %s, end: %s, status: %s, summary: %s, notes: %s', event_id, start, end, status, summary, notes) self.gcal_client.update_event(event_id, start, end, status, summary, notes)
class test_time_off(unittest.TestCase): # Used to store the cached instance of PyBambooHR bamboo = None def setUp(self): if self.bamboo is None: self.bamboo = PyBambooHR(subdomain='test', api_key='testingnotrealapikey') @httpretty.activate def test_get_time_off_requests(self): body = [{ "id": "1342", "employeeId": "4", "status": { "lastChanged": "2019-09-12", "lastChangedByUserId": "2369", "status": "approved" }, "name": "Charlotte Abbott", "start": "2019-05-30", "end": "2019-06-01", "created": "2019-09-11", "type": { "id": "78", "name": "Vacation", "icon": "palm-trees" }, "amount": { "unit": "hours", "amount": "24" }, "actions": { "view": True, "edit": True, "cancel": False, "approve": False, "deny": False, "bypass": False }, "dates": { "2019-05-30": "24" }, "notes": { "manager": "Home sick with the flu." } }] httpretty.register_uri( httpretty.GET, "https://api.bamboohr.com/api/gateway.php/test/v1/time_off/requests", body=dumps(body), content_type="application/json") response = self.bamboo.get_time_off_requests() self.assertIsNotNone(response) self.assertTrue(len(response) > 0) self.assertEquals(response[0]['id'], '1342') return @httpretty.activate def test_get_time_off_policies(self): body = [{ 'id': '70', 'timeOffTypeId': '77', 'name': 'Testing Manual Policy', 'effectiveDate': None, 'type': 'manual' }] httpretty.register_uri( httpretty.GET, "https://api.bamboohr.com/api/gateway.php/test/v1/meta/time_off/policies", body=dumps(body), content_type="application/json") response = self.bamboo.get_time_off_policies() self.assertIsNotNone(response) self.assertTrue(len(response) > 0) self.assertEquals(response[0]['id'], '70') return @httpretty.activate def test_get_time_off_types(self): body = { 'timeOffTypes': [{ 'id': '78', 'name': 'Vacation', 'units': 'hours', 'color': None, 'icon': 'palm-trees' }] } httpretty.register_uri( httpretty.GET, "https://api.bamboohr.com/api/gateway.php/test/v1/meta/time_off/types", body=dumps(body), content_type="application/json") response = self.bamboo.get_time_off_types() self.assertIsNotNone(response) self.assertTrue(len(response) > 0) self.assertEquals(response[0]['id'], '78') return @httpretty.activate def test_create_time_off_request(self): body = { 'id': '1675', 'employeeId': '111', 'start': '2040-02-01', 'end': '2040-02-02', 'created': '2019-12-24', 'status': { 'status': 'requested', 'lastChanged': '2019-12-24 02:29:45', 'lastChangedByUserId': '2479' }, 'name': 'xdd xdd', 'type': { 'id': '78', 'name': 'Vacation' }, 'amount': { 'unit': 'hours', 'amount': '2' }, 'notes': { 'employee': 'Going overseas with family', 'manager': 'Enjoy!' }, 'dates': { '2040-02-01': '1', '2040-02-02': '1' }, 'comments': [{ 'employeeId': '111', 'comment': 'Enjoy!', 'commentDate': '2019-12-24', 'commenterName': 'Test use' }], 'approvers': [{ 'userId': '2479', 'displayName': 'Test user', 'employeeId': '111', 'photoUrl': 'https://resources.bamboohr.com/employees/photos/initials.php?initials=testuser' }], 'actions': { 'view': True, 'edit': True, 'cancel': True, 'approve': True, 'deny': True, 'bypass': True }, 'policyType': 'discretionary', 'usedYearToDate': 0, 'balanceOnDateOfRequest': 0 } httpretty.register_uri( httpretty.PUT, "https://api.bamboohr.com/api/gateway.php/test/v1/employees/111/time_off/request", body=dumps(body), content_type="application/json") data = { 'status': 'requested', 'employee_id': '111', 'start': '2040-02-01', 'end': '2040-02-02', 'timeOffTypeId': '78', 'amount': '2', 'dates': [{ 'ymd': '2040-02-01', 'amount': '1' }, { 'ymd': '2040-02-02', 'amount': '1' }], 'notes': [{ 'type': 'employee', 'text': 'Going overseas with family' }, { 'type': 'manager', 'text': 'Enjoy!' }] } response = self.bamboo.create_time_off_request(data) self.assertIsNotNone(response) self.assertEquals(response['id'], '1675') return @httpretty.activate def test_update_time_off_request_status(self): body = { 'id': '1675', 'employeeId': '111', 'start': '2040-02-01', 'end': '2040-02-02', 'created': '2019-12-24', 'status': { 'status': 'declined', 'lastChanged': '2019-12-24 02:29:45', 'lastChangedByUserId': '2479' }, 'name': 'xdd xdd', 'type': { 'id': '78', 'name': 'Vacation' }, 'amount': { 'unit': 'hours', 'amount': '2' }, 'notes': { 'employee': 'Going overseas with family', 'manager': 'Enjoy!' }, 'dates': { '2040-02-01': '1', '2040-02-02': '1' }, 'comments': [{ 'employeeId': '111', 'comment': 'Enjoy!', 'commentDate': '2019-12-24', 'commenterName': 'Test use' }], 'approvers': [{ 'userId': '2479', 'displayName': 'Test user', 'employeeId': '111', 'photoUrl': 'https://resources.bamboohr.com/employees/photos/initials.php?initials=testuser' }], 'actions': { 'view': True, 'edit': True, 'cancel': True, 'approve': True, 'deny': True, 'bypass': True }, 'policyType': 'discretionary', 'usedYearToDate': 0, 'balanceOnDateOfRequest': 0 } httpretty.register_uri( httpretty.PUT, "https://api.bamboohr.com/api/gateway.php/test/v1/time_off/requests/1675/status", body=dumps(body), content_type="application/json") data = {'status': 'declined', 'note': 'Have fun!'} response = self.bamboo.update_time_off_request_status(body['id'], data) self.assertIsNotNone(response) self.assertTrue(response) return