def get(self, request, document_id, format=None):
        document = self._get_document(document_id)

        if (not document.upload):
            # Being asked to download a document that has no upload
            # is an invalid request
            return HttpResponseBadRequest('Specified document does not have an upload. It is not valid for download.')

        # Do the additional processing for PDF documents that has been signed
        if ('pdf' in document.upload.file_type.lower()
            and document.signature):
            web_request_service = WebRequestService()
            signature_service = SignatureService()

            # Get the PDF stream from the URL
            res = web_request_service.get(document.upload.S3)
            res.raise_for_status()
            pdf_stream = StringIO.StringIO(res.content)

            # Construct the response
            file_name = 'Signed_{}'.format(document.upload.file_name)
            response = HttpResponse(content_type='application/pdf')
            response['Content-Disposition'] = 'attachment; filename="{}"'.format(file_name)
   
            # Use the signature service to add signature page
            signature_service.append_signature_page(
                pdf_stream,
                document.user.id,
                document.updated_at,
                response
            )

            return response

        return HttpResponseRedirect(document.upload.S3)
Example #2
0
class ProjectService(object):

    hash_key_service = HashKeyService()
    request_service = WebRequestService()

    def get_projects_by_company(self, company_id, active_only=False):
        api_url = '{0}/company/{1}/projects'.format(
            API_URL_COI,
            self.hash_key_service.encode_key_with_environment(company_id))

        # Make the request and parse the response as json
        r = self.request_service.get(api_url)
        projects = r.json()

        if (active_only):
            filtered = []
            for project in projects:
                if (project['status'] == PROJECT_STATUS_ACTIVE):
                    filtered.append(project)
            projects = filtered

        view_models = []

        for project in projects:
            view_models.append(self._map_domain_to_view_model(project))

        return view_models

    def _map_domain_to_view_model(self, domain_model):
        return {
            'project_id': domain_model['_id'],
            'name': domain_model['name'],
            'status': domain_model['status']
        }
class TimeTrackingService(object):

    hash_key_service = HashKeyService()
    request_service = WebRequestService()

    def get_all_users_submitted_work_timesheet_by_week_start_date(
            self, week_start_date):
        users = []

        api_url = '{0}?start_date={1}&end_date={1}'.format(
            API_URL_WORK_TIMESHEET, week_start_date.isoformat())

        # Make the request and parse the response as json
        r = self.request_service.get(api_url)
        all_entries = r.json()

        for entry in all_entries:
            user_descriptor = entry['employee']['personDescriptor']
            user_id = int(
                self.hash_key_service.decode_key_with_environment(
                    user_descriptor))
            users.append(user_id)

        return users

    def get_company_users_submitted_work_timesheet_by_week_range(
            self, company_id, start_week_start_date, end_week_start_date):

        week_user_timesheets = {}
        api_url = '{0}api/v1/company/{1}/work_timesheets?start_date={2}&end_date={3}'.format(
            settings.TIME_TRACKING_SERVICE_URL,
            self.hash_key_service.encode_key_with_environment(company_id),
            start_week_start_date.isoformat(), end_week_start_date.isoformat())

        r = self.request_service.get(api_url)
        if r.status_code == 404:
            return week_user_timesheets

        all_entries = r.json()
        for entry in all_entries:
            user_descriptor = entry['employee']['personDescriptor']
            user_id = self.hash_key_service.decode_key_with_environment(
                user_descriptor)
            item = copy.deepcopy(entry)
            item['user_id'] = user_id

            # Group returned timesheet by work start date
            week_start_date_str = entry['weekStartDate']
            week_start_date = datetime.strptime(
                week_start_date_str, '%Y-%m-%dT%H:%M:%S.%fZ').date()
            if week_start_date in week_user_timesheets:
                week_user_timesheets[week_start_date].append(item)
            else:
                week_user_timesheets[week_start_date] = [item]

        return week_user_timesheets
    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
Example #5
0
class ContratorService(object):

    hash_key_service = HashKeyService()
    request_service = WebRequestService()

    def get_contractor_by_id(self, contractor_id):
        api_url = '{0}/contractor/{1}'.format(API_URL_COI, contractor_id)

        # Make the request and parse the response as json
        r = self.request_service.get(api_url)
        entry = r.json()

        return entry
class TimeOffService(object):

    hash_key_service = HashKeyService()
    request_service = WebRequestService()

    def get_company_users_time_off_records_by_date_range(
            self, company_id, start_date, end_date):
        user_records = []
        api_url = '{0}api/v1/company/{1}/timeoffs?start_date={2}&end_date={3}'.format(
            settings.TIME_TRACKING_SERVICE_URL,
            self.hash_key_service.encode_key_with_environment(company_id),
            start_date.isoformat(), end_date.isoformat())

        r = self.request_service.get(api_url)
        if r.status_code == 404:
            return user_records

        r.raise_for_status()

        all_entries = r.json()
        for entry in all_entries:
            user_records.append(TimeOffRecord(entry))

        # Sort the records by user ID
        sorted_records = sorted(
            user_records,
            key=lambda record:
            (record.requestor_user_id, record.start_date_time))

        return sorted_records

    def get_company_users_time_off_record_aggregates_by_date_range(
            self, company_id, start_date, end_date):
        mappings = {}

        all_records = self.get_company_users_time_off_records_by_date_range(
            company_id, start_date, end_date)
        for record in all_records:
            if record.requestor_user_id not in mappings:
                mappings[
                    record.requestor_user_id] = EmployeeTimeOffRecordAggregate(
                        record.requestor_user_id)
            mappings[record.requestor_user_id].add_record(record)

        unsorted = mappings.values()
        return sorted(unsorted, key=lambda aggregate: aggregate.user_id)
Example #7
0
class ActionTimeoffAccural(ActionBase):
    request_service = WebRequestService()

    API_URL_TIMEOFF_ACCURAL = '{0}{1}'.format(
        settings.TIME_TRACKING_SERVICE_URL,
        'api/v1/timeoff_quotas/execute_accrual'
    )

    def __init__(self):
        super(ActionTimeoffAccural, self).__init__()

    def execute(self, action_data):
        # Invoke the remote URL to trigger global timeoff accural
        r = self.request_service.get(self.API_URL_TIMEOFF_ACCURAL)

        if (r.status_code != 200):
            self.log.error("Action {} failed to complete.".format(self.__class__.__name__))
            return

        self.log.info("Action {} ran to completion.".format(self.__class__.__name__))
Example #8
0
class ActionCoiExpirationCheck(ActionBase):
    request_service = WebRequestService()

    API_URL_COI_EXPIRATION_CHECK = '{0}{1}'.format(
        settings.COI_SERVICE_URL,
        'api/v1/contractors/execute_insurance_validation')

    def __init__(self):
        super(ActionCoiExpirationCheck, self).__init__()

    def execute(self, action_data):

        # Invoke the remote URL to trigger global timeoff accural
        r = self.request_service.post(self.API_URL_COI_EXPIRATION_CHECK,
                                      action_data)

        if (r.status_code != 200):
            self.log.error("Action {} failed to complete.".format(
                self.__class__.__name__))
            return

        self.log.info("Action {} ran to completion.".format(
            self.__class__.__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