def __init__(self):
        self.integration_provider_service = IntegrationProviderService()
        self.company_personnel_service = CompanyPersonnelService()

        self._data_service_registry = {}
        for integration_provider_type in INTEGRATION_SERVICE_TYPES:
            self._data_service_registry[integration_provider_type] = {}      

        self._register_data_service_classes()
    def get(self, request, pk, format=None):
        filter_status = request.QUERY_PARAMS.get('status', None)
        employees = CompanyUser.objects.select_related('user').filter(
            company=pk, company_user_type='employee')
        employee_count = len(employees)
        if filter_status:
            comp_personnel_service = CompanyPersonnelService()
            users_in_status = comp_personnel_service.get_company_employee_user_ids_with_status_in_time_range(
                pk, filter_status, datetime.date.today(), datetime.date.max)
            employee_count = len(users_in_status)

        return Response({'employees_count': employee_count})
class PersonInfoUpdatedEventCpDataSyncHandler(EventHandlerBase):
    def __init__(self):
        super(PersonInfoUpdatedEventCpDataSyncHandler,
              self).__init__(PersonInfoUpdatedEvent)
        self._cp_data_service = ConnectPayrollDataService()
        self._company_personnel_service = CompanyPersonnelService()

    def _internal_handle(self, event):
        person_model = Person.objects.get(pk=event.person_id)

        # We only attempt at syncing data if the person updated is
        # the employee, and not family members
        if (not person_model.relationship == SELF):
            return

        # We only proceed if we are looking at an employee, and not other roles
        if (not self._company_personnel_service.is_user_employee(
                person_model.user.id)):
            return

        # No-op if the employee (the company he/she belongs to) is not using
        # the integration service in context
        if (not self._cp_data_service.is_supported(person_model.user.id)):
            return

        self._cp_data_service.sync_employee_data_to_remote(
            person_model.user.id)
class DirectReportCountView(APIView):

    _personnel_service = CompanyPersonnelService()

    def get(self, request, comp_id, user_id, format=None):
        direct_reports_count = self._personnel_service.get_direct_report_count(comp_id, user_id)
        return Response({'count': direct_reports_count})
class DirectReportsView(APIView):

    _personnel_service = CompanyPersonnelService()

    def get(self, request, comp_id, user_id, format=None):
        direct_reports = self._personnel_service.get_direct_report_company_users(comp_id, user_id)
        serializer = CompanyUserSerializer(direct_reports, many=True)
        return Response({'user_roles':serializer.data})
    def __init__(self):
        super(ConnectPayrollDataService, self).__init__()
        self.view_model_factory = ReportViewModelFactory()
        self.web_request_service = WebRequestService()
        self.company_personnel_service = CompanyPersonnelService()
        self.integration_provider_service = IntegrationProviderService()

        # Retrieve the api token if available
        setting_service = SystemSettingsService()
        self._cp_api_auth_token = setting_service.get_setting_value_by_name(
            SYSTEM_SETTING_CPAPIAUTHTOKEN)

        # Also construct the API url, if available
        self._cp_api_url = None
        base_uri = setting_service.get_setting_value_by_name(
            SYSTEM_SETTING_CPAPIBASEURI)
        employee_route = setting_service.get_setting_value_by_name(
            SYSTEM_SETTING_CPAPIEMPLOYEEROUTE)
        if (base_uri and employee_route):
            self._cp_api_url = base_uri + employee_route
class EmployeeProfileUpdatedEventCpDataSyncHandler(EventHandlerBase):
    def __init__(self):
        super(EmployeeProfileUpdatedEventCpDataSyncHandler, self).__init__(EmployeeProfileUpdatedEvent)
        self._cp_data_service = ConnectPayrollDataService()
        self._company_personnel_service = CompanyPersonnelService()

    def _internal_handle(self, event):
        # We only proceed if we are looking at an employee, and not other roles
        if (not self._company_personnel_service.is_user_employee(event.user_id)):
            return
        
        # No-op if the employee (the company he/she belongs to) is not using 
        # the integration service in context
        if (not self._cp_data_service.is_supported(event.user_id)):
            return

        self._cp_data_service.sync_employee_data_to_remote(event.user_id)
 def __init__(self):
     super(PersonInfoUpdatedEventCpDataSyncHandler,
           self).__init__(PersonInfoUpdatedEvent)
     self._cp_data_service = ConnectPayrollDataService()
     self._company_personnel_service = CompanyPersonnelService()
class CompanyUsersTimePunchCardWeeklyReportV2View(ExcelExportViewBase):

    date_time_service = DateTimeService()
    time_punch_card_service = TimePunchCardService()
    company_personnel_service = CompanyPersonnelService()

    def __init__(self):
        # List out instance variables that will be used
        # below
        self._company = None
        self._week_start_date = None
        self._week_end_date = None
        self._employee_list_cache = None
        self._blank_state_sheet_data_template = None

    def _build_employee_info_cache(self):
        self._employee_list_cache = []
        all_employees = self._get_all_employee_users_for_company(self._company.id)
        filtered_employee_user_ids = self.company_personnel_service.get_company_employee_user_ids_non_fully_terminated_in_time_range(
            self._company.id,
            self._week_start_date,
            self._week_end_date
        )
        for employee in all_employees:
            if (employee.id in filtered_employee_user_ids):
                self._employee_list_cache.append({
                    'user_id': employee.id,
                    'full_name': self._get_user_full_name(employee)
                })

    def _build_report_time_sheets_data(self):
        # The structure of the result would be a nested dictionary
        # result{ state: { card_type: { user_id: { weekday_index: hours } } } }
        all_state_sheets = {}

        # First read in all employee submitted cards
        all_cards = self._get_all_punch_cards()

        # Now setup blank/base sheet data for all states
        # encountered
        if (len(all_cards) <= 0):
            state = 'No State'
            all_state_sheets.setdefault(
                        state,
                        self._get_blank_sheet_data())
        else:
            for card in all_cards:
                state = card.state
                if (not state):
                    state = TIME_PUNCH_CARD_NON_SPECIFIED_STATE
                if (state not in all_state_sheets):
                    all_state_sheets.setdefault(
                        state,
                        self._get_blank_sheet_data())

        # Now "merge" user submitted data into the sheets
        # data
        for punch_card in all_cards:
            self._merge_punch_card_data_to_full_set(
                punch_card,
                all_state_sheets)

        return all_state_sheets

    def _get_all_punch_cards(self):
        return self.time_punch_card_service.get_company_users_time_punch_cards_by_date_range(
            self._company.id,
            self._week_start_date,
            self._week_end_date)

    def _get_blank_sheet_data(self):
        # Cache a copy as template and return deep copies of that
        # to save some computational power
        if (not self._blank_state_sheet_data_template):
            sheet_data = {}
            for card_type in CARD_TYPES:
                sheet_data.setdefault(
                    card_type,
                    self._get_blank_card_type_section_data(card_type))
            self._blank_state_sheet_data_template = sheet_data

        return deepcopy(self._blank_state_sheet_data_template)

    def _get_blank_card_type_section_data(self, card_type):
        card_type_section_data = {}

        card_type_behavior = CARD_TYPES[card_type]

        if card_type_behavior.get('PrePopulate', True):
            for user_info in self._employee_list_cache:
                card_type_section_data.setdefault(
                    user_info['user_id'],
                    self._get_employee_weekly_blank_data())

        return card_type_section_data

    def _get_employee_weekly_blank_data(self):
        blank_data = {}
        for isoweekday in range(7):
            blank_data.setdefault(isoweekday, 0.0)
        return blank_data

    def _merge_punch_card_data_to_full_set(self, punch_card, all_states_sheets_data):
        state_data = None
        if (punch_card.state): 
            state_data = all_states_sheets_data[punch_card.state]
        else:
            state_data = all_states_sheets_data[TIME_PUNCH_CARD_NON_SPECIFIED_STATE]
        card_type = punch_card.card_type
        if (card_type in CARD_TYPES):
            index_card_type = CARD_TYPES[card_type].get('MergeWith', card_type)
            card_type_data = state_data[index_card_type]
            if punch_card.user_id not in card_type_data:
                card_type_data.setdefault(
                    punch_card.user_id,
                    self._get_employee_weekly_blank_data())
            employee_weekly_data = card_type_data[punch_card.user_id]
            card_weekday_iso = punch_card.get_card_day_of_week_iso()
            if (CARD_TYPES[card_type].get('NoHours', True) or not punch_card.start or not punch_card.end):
                # Even if an employee filed 2 of such cards in one slot
                # Only count hours once
                if (employee_weekly_data[card_weekday_iso] <= 0):
                    employee_weekly_data[card_weekday_iso] = TIME_PUNCH_CARD_NO_HOURS_DEFAULT_HOURS
            else:
                # Accumulate the hours specified by the card
                hours = punch_card.get_punch_card_hours()
                employee_weekly_data[card_weekday_iso] += hours

    def _write_all_states_sheets(self, all_states_sheets_data):
        for state in all_states_sheets_data:
            self._write_state_sheet(state, all_states_sheets_data[state])

    def _write_state_sheet(self, state, state_sheet_data):
        self._start_work_sheet(state)
        self._write_sheet_headers(state)

        for card_type in CARD_TYPES:
            if CARD_TYPES[card_type].get('RenderRow', True):
                self._write_card_type_section(card_type, state_sheet_data[card_type])

    def _write_sheet_headers(self, state):
        self._write_cell('Company')
        self._write_cell(self._company.name)
        self._next_row()
        self._write_cell('Payroll Sheet')
        self._write_cell(self._week_start_date.strftime(DATE_FORMAT_STRING))
        self._write_cell(self._week_end_date.strftime(DATE_FORMAT_STRING))
        self._next_row()
        self._write_cell('State')
        self._write_cell(state)
        self._next_row()
        self._next_row()

    def _write_card_type_section(self, card_type, card_type_section_data):
        card_type_behavior = CARD_TYPES[card_type]
        self._write_cell(card_type_behavior['name'])
        self._next_row()
        self._write_card_type_section_headers()

        # Now write the data
        for user_info in self._employee_list_cache:
            user_id = user_info['user_id']
            if (user_id in card_type_section_data):
                self._write_employee_weekly_data(
                    user_info,
                    card_type_section_data[user_id])

        self._next_row()

    def _write_card_type_section_headers(self):
        self._write_cell('Employee Name')
        for i in range(7):
            date = self._week_start_date + timedelta(i)
            header_text = '{} - {}'.format(date.strftime('%A'), date.strftime(DATE_FORMAT_STRING))
            self._write_cell(header_text) 
        self._next_row()

    def _write_employee_weekly_data(self, user_info, employee_weekly_data):
        self._write_cell(user_info['full_name'])
        for weekdayiso in range(7):
            self._write_cell(employee_weekly_data[weekdayiso])
        self._next_row()

    '''
    Get the Weekly Time Punch Card report excel of a company's all employees
    '''
    @user_passes_test(company_employer)
    def get(self, request, pk, year, month, day, format=None):
        # Parse all information needed from URL
        self._company = self._get_company_info(pk)
        input_date = date(year=int(year), month=int(month), day=int(day))
        week_range = self.date_time_service.get_week_range_by_date(input_date)
        self._week_start_date = week_range[0]
        self._week_end_date = week_range[1]

        # First collect and cache company employees data
        self._build_employee_info_cache()

        # Now collect time punch data for the given week
        # and organize them to the right hierarchy
        all_states_sheets_data = self._build_report_time_sheets_data()

        # Now write out all data
        self._init()
        self._write_all_states_sheets(all_states_sheets_data)

        response = HttpResponse(content_type='application/vnd.ms-excel')
        response['Content-Disposition'] = (
            'attachment; filename={0}_employee_worktime_report_{1}.xls'
        ).format(self._company, self._week_start_date.strftime('%m_%d_%Y'))

        self._save(response)

        return response
Beispiel #10
0
 def __init__(self):
     super(AdvantagePayrollDataService, self).__init__()
     self.company_personnel_service = CompanyPersonnelService()
     self.integration_provider_service = IntegrationProviderService()
Beispiel #11
0
class AdvantagePayrollDataService(IntegrationProviderDataServiceBase):

    def __init__(self):
        super(AdvantagePayrollDataService, self).__init__()
        self.company_personnel_service = CompanyPersonnelService()
        self.integration_provider_service = IntegrationProviderService()

    def _integration_service_type(self):
        return INTEGRATION_SERVICE_TYPE_PAYROLL

    def _integration_provider_name(self):
        return INTEGRATION_PAYROLL_ADVANTAGE_PAYROLL

    def _internal_generate_and_record_external_employee_number(self, employee_user_id):
        # First check whether the said employee already have a number
        # If so, this is an exception state, log it, and skip the operation
        employee_number = self.integration_provider_service.get_employee_integration_provider_external_id(
            employee_user_id,
            self._integration_service_type(),
            self._integration_provider_name())
        if (employee_number):
            logging.error('Invalid Operation: Try to generate external ID for employee (User ID={0}) already has one!'.format(employee_user_id))
            return

        company_id = self.company_personnel_service.get_company_id_by_employee_user_id(employee_user_id)
        next_employee_number = self._get_next_external_employee_number(company_id)
        # Now save the next usable external employee number to the profile
        # of the specified employee
        self._set_employee_external_id(
            employee_user_id,
            self._integration_service_type(),
            self._integration_provider_name(),
            next_employee_number
        )

    def _get_next_external_employee_number(self, company_id):
        employee_number_seed_str = self.integration_provider_service.get_company_integration_provider_employee_external_id_seed(
            company_id,
            self._integration_service_type(),
            self._integration_provider_name())
        employee_number_seed = 0
        if (employee_number_seed_str):
            try: 
                employee_number_seed = int(employee_number_seed_str)
            except ValueError:
                logging.exception('Encountered malformed external employee number seed: "{0}"'.format(employee_number_seed_str))

        max_employee_number = self._get_max_external_employee_number(company_id)
        return max(employee_number_seed, max_employee_number) + 1

    def _get_max_external_employee_number(self, company_id):
        all_employee_data = CompanyUserIntegrationProvider.objects.filter(
                company_user__company=company_id,
                integration_provider__service_type=self._integration_service_type(),
                integration_provider__name=self._integration_provider_name())

        employee_numbers = []
        for employee_data in all_employee_data:
            if (employee_data.company_user_external_id):
                # For AP, it is assumed that all employee numbers 
                # are positive integers. Though just build some 
                # fault tolerence here, to log the exception and
                # let it go, to not block all employee creations
                # if for some reason there is a malformed ID in 
                # system.
                try: 
                    employee_number = int(employee_data.company_user_external_id)
                    employee_numbers.append(employee_number)
                except ValueError:
                    logging.exception('Encountered malformed external employee number: "{0}"'.format(employee_data.company_user_external_id))

        if (len(employee_numbers) > 0):
            return max(employee_numbers)

        return 0
class CompanyIntegrationProviderDataService(object):
    _logger = LoggingService()

    def __init__(self):
        self.integration_provider_service = IntegrationProviderService()
        self.company_personnel_service = CompanyPersonnelService()

        self._data_service_registry = {}
        for integration_provider_type in INTEGRATION_SERVICE_TYPES:
            self._data_service_registry[integration_provider_type] = {}      

        self._register_data_service_classes()

    def _register_data_service_classes(self):
        # Register all data services
        self._data_service_registry[INTEGRATION_SERVICE_TYPE_PAYROLL][INTEGRATION_PAYROLL_CONNECT_PAYROLL] = ConnectPayrollDataService
        self._data_service_registry[INTEGRATION_SERVICE_TYPE_PAYROLL][INTEGRATION_PAYROLL_ADVANTAGE_PAYROLL] = AdvantagePayrollDataService

    def sync_employee_data_to_remote(self, employee_user_id):
        # Enumerate through all the integration providers associated
        # with the company, identify all registered data services, 
        # and invoke them to sync with the remote
        company_id = self.company_personnel_service.get_company_id_by_employee_user_id(employee_user_id)
        if (not company_id):
            return
        return self._enumerate_company_data_services(company_id, lambda data_service: data_service.sync_employee_data_to_remote(employee_user_id))

    def generate_and_record_external_employee_number(self, employee_user_id):
        company_id = self.company_personnel_service.get_company_id_by_employee_user_id(employee_user_id)
        if (not company_id):
            raise ValueError('The given employee user ID "{0}" is not properly linked to a valid company.'.format(employee_user_id))
        return self._enumerate_company_data_services(company_id, lambda data_service: data_service.generate_and_record_external_employee_number(employee_user_id))

    def _enumerate_company_data_services(self, company_id, data_service_action):
        # Record failed actions and report back to caller
        failed_data_service_records = []

        company_integration_providers = self.integration_provider_service.get_company_integration_providers(company_id)
        for service_type in company_integration_providers:
            company_service_type_provider = company_integration_providers[service_type]
            if (company_service_type_provider):
                provider_name = company_service_type_provider['integration_provider']['name']
                # Now we have the service type and the provider name, check whether
                # we have a registered data service class
                if (service_type in self._data_service_registry):
                    service_type_data_services = self._data_service_registry[service_type]
                    if (provider_name in service_type_data_services):
                        # create an instance of the date service, and invoke the action
                        data_service = service_type_data_services[provider_name]()

                        # Collect the current data service specs into a record instance
                        # for logging and anormaly reporting to upstream
                        service_action_record = IntegrationDataServiceActionRecord(
                            company_id=company_id,
                            service_type=service_type,
                            provider_name=provider_name,
                            service_action=data_service_action
                        )

                        try:
                            data_service_action(data_service)
                            self._logger.error('Successfully executed integration data action')
                            self._logger.info(service_action_record)
                        except Exception as e:
                            self._logger.error('Failed to complete integration data action')
                            self._logger.info(service_action_record)
                            failed_data_service_records.append(service_action_record)
                else:
                    self._logger.warning('Unsupported integration service type encoutered: "{0}"'.format(service_type))

        return failed_data_service_records
Beispiel #13
0
class IntegrationProviderService(object):
    company_personnel_service = CompanyPersonnelService()

    def get_company_integration_providers(self, company_id):
        result = {}

        company_providers = CompanyIntegrationProvider.objects.filter(
            company_id=company_id)

        for service_type in INTEGRATION_SERVICE_TYPES:
            result[service_type] = None

        for company_provider in company_providers:
            serialized = CompanyIntegrationProviderSerializer(company_provider)
            result[company_provider.integration_provider.
                   service_type] = serialized.data

        return result

    def get_company_integration_provider_external_id(self, company_id,
                                                     service_type,
                                                     provider_name):
        company_integration_provider = self._get_company_integration_provider(
            company_id, service_type, provider_name)
        if not company_integration_provider:
            return None
        return company_integration_provider['company_external_id']

    def get_company_integration_provider_employee_external_id_seed(
            self, company_id, service_type, provider_name):
        company_integration_provider = self._get_company_integration_provider(
            company_id, service_type, provider_name)
        if not company_integration_provider:
            return None
        return company_integration_provider['employee_external_id_seed']

    def _get_company_integration_provider(self, company_id, service_type,
                                          provider_name):
        company_integration_providers = self.get_company_integration_providers(
            company_id)
        integration_provider = company_integration_providers[service_type]
        if (not integration_provider):
            return None
        company_provider_name = integration_provider['integration_provider'][
            'name']
        if (company_provider_name != provider_name):
            return None
        return integration_provider

    def is_integration_service_available_to_company(self, company_id,
                                                    service_type,
                                                    provider_name):
        company_integration_providers = self.get_company_integration_providers(
            company_id)
        if (not service_type in company_integration_providers):
            return False
        company_service_type_provider = company_integration_providers[
            service_type]
        if (not company_service_type_provider):
            return False
        found_provider_name = company_service_type_provider[
            'integration_provider']['name']
        if (found_provider_name != provider_name):
            return False
        return True

    def is_integration_service_available_to_employee(self, employee_user_id,
                                                     service_type,
                                                     provider_name):
        company_id = self.company_personnel_service.get_company_id_by_employee_user_id(
            employee_user_id)
        if (not company_id):
            raise ValueError(
                'Did not find associated company for user {0}'.format(
                    employee_user_id))
        return self.is_integration_service_available_to_company(
            company_id, service_type, provider_name)

    def get_employee_integration_provider_external_id(self, employee_user_id,
                                                      service_type,
                                                      provider_name):
        company_user_integration_provider = self._get_employee_integration_provider_model(
            employee_user_id, service_type, provider_name)
        if (company_user_integration_provider):
            return company_user_integration_provider.company_user_external_id
        return None

    def set_employee_integration_provider_external_id(self, employee_user_id,
                                                      service_type,
                                                      provider_name,
                                                      external_id):
        company_user_integration_provider = self._get_employee_integration_provider_model(
            employee_user_id, service_type, provider_name)
        if (company_user_integration_provider):
            # update the existing record
            company_user_integration_provider.company_user_external_id = external_id
            company_user_integration_provider.save()
        else:
            # create a new record
            company_user = self._get_company_user_model(employee_user_id)
            integration_provider = self._get_integration_provider_model(
                service_type, provider_name)
            CompanyUserIntegrationProvider.objects.create(
                company_user=company_user,
                integration_provider=integration_provider,
                company_user_external_id=external_id)

    def _get_employee_integration_provider_model(self, employee_user_id,
                                                 service_type, provider_name):
        try:
            return CompanyUserIntegrationProvider.objects.get(
                company_user__user=employee_user_id,
                integration_provider__service_type=service_type,
                integration_provider__name=provider_name)
        except CompanyUserIntegrationProvider.DoesNotExist:
            return None

    def _get_company_user_model(self, employee_user_id):
        return CompanyUser.objects.get(user=employee_user_id,
                                       company_user_type=USER_TYPE_EMPLOYEE)

    def _get_integration_provider_model(self, service_type, provider_name):
        return IntegrationProvider.objects.get(service_type=service_type,
                                               name=provider_name)
class ConnectPayrollDataService(IntegrationProviderDataServiceBase):
    _logger = LoggingService()

    def __init__(self):
        super(ConnectPayrollDataService, self).__init__()
        self.view_model_factory = ReportViewModelFactory()
        self.web_request_service = WebRequestService()
        self.company_personnel_service = CompanyPersonnelService()
        self.integration_provider_service = IntegrationProviderService()

        # Retrieve the api token if available
        setting_service = SystemSettingsService()
        self._cp_api_auth_token = setting_service.get_setting_value_by_name(
            SYSTEM_SETTING_CPAPIAUTHTOKEN)

        # Also construct the API url, if available
        self._cp_api_url = None
        base_uri = setting_service.get_setting_value_by_name(
            SYSTEM_SETTING_CPAPIBASEURI)
        employee_route = setting_service.get_setting_value_by_name(
            SYSTEM_SETTING_CPAPIEMPLOYEEROUTE)
        if (base_uri and employee_route):
            self._cp_api_url = base_uri + employee_route

    def _integration_service_type(self):
        return INTEGRATION_SERVICE_TYPE_PAYROLL

    def _integration_provider_name(self):
        return INTEGRATION_PAYROLL_CONNECT_PAYROLL

    def _internal_generate_and_record_external_employee_number(
            self, employee_user_id):
        # First check whether the said employee already have a number
        # If so, this is an exception state, log it, and skip the operation
        employee_number = self.integration_provider_service.get_employee_integration_provider_external_id(
            employee_user_id, self._integration_service_type(),
            self._integration_provider_name())
        if (employee_number):
            logging.error(
                'Invalid Operation: Try to generate external ID for employee (User ID={0}) already has one!'
                .format(employee_user_id))
            return

        company_id = self.company_personnel_service.get_company_id_by_employee_user_id(
            employee_user_id)
        next_employee_number = self._get_next_external_employee_number(
            company_id)
        # Now save the next usable external employee number to the profile
        # of the specified employee
        self._set_employee_external_id(employee_user_id,
                                       self._integration_service_type(),
                                       self._integration_provider_name(),
                                       next_employee_number)

    def _get_next_external_employee_number(self, company_id):
        return 0

    def _internal_sync_employee_data_to_remote(self, employee_user_id):
        # If the Connect Payroll API's auth token is not specified
        # in the environment, consider this feature to be off, and
        # skip all together.
        if (not self._cp_api_auth_token):
            return

        if (not self._cp_api_url):
            return

        # Also check whether the employee belong to a company with
        # the right setup with the remote system. And also skip if
        # this is not the case
        external_company_id = self._get_cp_client_code_by_employee(
            employee_user_id)
        if (not external_company_id):
            return

        try:
            # Populate the data object from the current state of the employee in WBM system
            # Also apply client(WBM) side validation on the data, based on understanding of
            # documentation from ConnectPay
            employee_data_dto = self._get_employee_data_dto(
                employee_user_id, external_company_id)
            issue_list = self._validate_employee_data_dto(employee_data_dto)

            if (issue_list and len(issue_list) > 0):
                raise RuntimeError(
                    'There are problems collecting complete data required to sync to ConnectPay API for employee "{0}"'
                    .format(employee_user_id), issue_list)

            if (employee_data_dto.payrollId):
                # Already exists in CP system, update
                self._logger.info('Updating Employee CP ID: ' +
                                  employee_data_dto.payrollId)
                self._logger.info(employee_data_dto)
                self._update_employee_data_to_remote(employee_data_dto)
            else:
                # Does not yet exist in CP system, new employee addition, create
                self._logger.info(
                    'Creating new employee record on CP system ...')
                self._logger.info(employee_data_dto)
                payroll_id = self._create_employee_data_to_remote(
                    employee_data_dto)
                self._logger.info(
                    'Created Employee CP ID: {0}'.format(payroll_id))

                # Sync the cp ID from the response
                self._set_employee_external_id(
                    employee_user_id, self._integration_service_type(),
                    self._integration_provider_name(), payroll_id)
        except Exception as e:
            self._logger.error(traceback.format_exc())
            raise

    def _get_employee_data_dto(self, employee_user_id, external_company_id):
        # First populate the CP identifiers
        dto = ConnectPayrollEmployeeDto()
        dto.payrollId = self._get_employee_external_id(
            employee_user_id, self._integration_service_type(),
            self._integration_provider_name())
        dto.companyId = external_company_id

        # Now populate other data
        company_id = self.company_personnel_service.get_company_id_by_employee_user_id(
            employee_user_id)
        company_info = self.view_model_factory.get_company_info(company_id)
        person_info = self.view_model_factory.get_employee_person_info(
            employee_user_id)

        # Employee basic data
        dto.ssn = person_info.ssn
        dto.firstName = person_info.first_name
        dto.lastName = person_info.last_name
        dto.dob = self._get_date_string(person_info.birth_date)
        dto.gender = person_info.gender
        dto.address1 = person_info.address1
        dto.address2 = person_info.address2
        dto.city = person_info.city
        dto.country = person_info.country
        dto.state = person_info.state
        dto.zip = person_info.zipcode
        dto.email = person_info.email
        if (len(person_info.phones) > 0):
            dto.phone = person_info.phones[0]['number']

        # Employment data
        employee_profile_info = self.view_model_factory.get_employee_employment_profile_data(
            employee_user_id, company_info.company_id)

        if (employee_profile_info):
            dto.jobTitle = employee_profile_info.job_title
            dto.fullTime = employee_profile_info.is_full_time_employee()
            dto.hireDate = self._get_date_string(
                employee_profile_info.hire_date)
            dto.originalHireDate = self._get_date_string(
                employee_profile_info.hire_date)
            # [TODO]: Needs specification on employee status values
            dto.employeeStatus = '3'
            dto.terminationDate = self._get_date_string(
                employee_profile_info.end_date)

            # Salary data
            dto.payEffectiveDate = self._get_date_string(
                employee_profile_info.compensation_effective_date)
            dto.annualBaseSalary = self._get_decimal_string(
                employee_profile_info.annual_salary)
            dto.baseHourlyRate = self._get_decimal_string(
                employee_profile_info.current_hourly_rate)
            dto.hoursPerWeek = self._get_decimal_string(
                employee_profile_info.projected_hours_per_week)

        # Other
        employee_i9_info = self.view_model_factory.get_employee_i9_data(
            employee_user_id)
        if (employee_i9_info):
            self.usCitizen = employee_i9_info.citizen_data is not None

        return dto

    def _validate_employee_data_dto(self, employee_data_dto):
        issue_list = []

        # System Data
        _DataValidator(employee_data_dto, 'companyId', issue_list) \
            .with_value_exists_check() \
            .with_value_length_check(6, 6) \
            .validate()

        _DataValidator(employee_data_dto, 'payrollId', issue_list) \
            .with_value_valid_integer_check() \
            .validate()

        # Employee bio data and basic info
        _DataValidator(employee_data_dto, 'ssn', issue_list) \
            .with_value_exists_check() \
            .with_value_length_check(9, 9) \
            .validate()

        _DataValidator(employee_data_dto, 'firstName', issue_list) \
            .with_value_exists_check() \
            .with_value_length_check(1, 20) \
            .validate()

        _DataValidator(employee_data_dto, 'middleName', issue_list) \
            .with_value_length_check(0, 20) \
            .validate()

        _DataValidator(employee_data_dto, 'lastName', issue_list) \
            .with_value_exists_check() \
            .with_value_length_check(1, 20) \
            .validate()

        _DataValidator(employee_data_dto, 'dob', issue_list) \
            .with_value_exists_check() \
            .with_value_valid_datetime_check() \
            .validate()

        _DataValidator(employee_data_dto, 'gender', issue_list) \
            .with_value_exists_check() \
            .with_value_in_list_check(['M', 'F']) \
            .validate()

        _DataValidator(employee_data_dto, 'address1', issue_list) \
            .with_value_length_check(0, 30) \
            .validate()

        _DataValidator(employee_data_dto, 'address2', issue_list) \
            .with_value_length_check(0, 30) \
            .validate()

        _DataValidator(employee_data_dto, 'city', issue_list) \
            .with_value_length_check(0, 28) \
            .validate()

        _DataValidator(employee_data_dto, 'state', issue_list) \
            .with_value_exists_check() \
            .with_value_length_check(2, 2) \
            .validate()

        _DataValidator(employee_data_dto, 'zip', issue_list) \
            .with_value_exists_check() \
            .with_value_length_check(5, 10) \
            .validate()

        _DataValidator(employee_data_dto, 'country', issue_list) \
            .with_value_length_check(0, 30) \
            .validate()

        _DataValidator(employee_data_dto, 'email', issue_list) \
            .with_value_exists_check() \
            .with_value_length_check(1, 250) \
            .validate()

        _DataValidator(employee_data_dto, 'phone', issue_list) \
            .with_value_length_check(0, 14) \
            .validate()

        _DataValidator(employee_data_dto, 'phone', issue_list) \
            .with_value_length_check(0, 14) \
            .validate()

        # Employment data
        _DataValidator(employee_data_dto, 'department', issue_list) \
            .with_value_valid_integer_check() \
            .validate()

        _DataValidator(employee_data_dto, 'division', issue_list) \
            .with_value_valid_integer_check() \
            .validate()

        _DataValidator(employee_data_dto, 'union', issue_list) \
            .with_value_type_boolean_check() \
            .validate()

        _DataValidator(employee_data_dto, 'jobTitle', issue_list) \
            .with_value_length_check(0, 30) \
            .validate()

        _DataValidator(employee_data_dto, 'fullTime', issue_list) \
            .with_value_type_boolean_check() \
            .validate()

        _DataValidator(employee_data_dto, 'seasonal', issue_list) \
            .with_value_type_boolean_check() \
            .validate()

        _DataValidator(employee_data_dto, 'hireDate', issue_list) \
            .with_value_exists_check() \
            .with_value_valid_datetime_check() \
            .validate()

        _DataValidator(employee_data_dto, 'originalHireDate', issue_list) \
            .with_value_valid_datetime_check() \
            .validate()

        _DataValidator(employee_data_dto, 'terminationDate', issue_list) \
            .with_value_valid_datetime_check() \
            .validate()

        self.employeeStatus = None

        # Salary data
        _DataValidator(employee_data_dto, 'payEffectiveDate', issue_list) \
            .with_value_valid_datetime_check() \
            .validate()

        _DataValidator(employee_data_dto, 'annualBaseSalary', issue_list) \
            .with_value_valid_decimal_check() \
            .validate()

        _DataValidator(employee_data_dto, 'baseHourlyRate', issue_list) \
            .with_value_valid_decimal_check() \
            .validate()

        return issue_list

    def _update_employee_data_to_remote(self, employee_data_dto):
        data = employee_data_dto.__dict__

        response = self.web_request_service.put(
            self._cp_api_url,
            data_object=data,
            auth_token=self._cp_api_auth_token)

        response.raise_for_status()

    def _create_employee_data_to_remote(self, employee_data_dto):
        data = employee_data_dto.__dict__

        response = self.web_request_service.post(
            self._cp_api_url,
            data_object=data,
            auth_token=self._cp_api_auth_token)

        response.raise_for_status()

        # Also, we really only expect here the below based on CP API behavior
        # * HTTP 200
        # * body contains the resultant ID created
        # So throw if we receive anything else
        if (response.status_code != 200):
            raise RuntimeError(
                'POST to ConnectPay Employee API resulted in a non-200 status: "{0}"'
                .format(response.status_code), response)

        if (not response.text):
            raise RuntimeError(
                'POST to ConnectPay Employee API resulted in empty body, and hence was not able to receive new employee ID.'
            )

        return response.text

    def _get_cp_client_code_by_employee(self, employee_user_id):
        company_id = self.company_personnel_service.get_company_id_by_employee_user_id(
            employee_user_id)

        if (company_id):
            return self.integration_provider_service.get_company_integration_provider_external_id(
                company_id, self._integration_service_type(),
                self._integration_provider_name())

        return None

    def _get_date_string(self, date):
        if date:
            try:
                return date.isoformat()
            except:
                return None
        else:
            return None

    def _get_decimal_string(self, input_value):
        if isinstance(input_value, decimal.Decimal):
            return str(input_value)

        return input_value