Example #1
0
    def test_get_list_of_week_start_dates_in_range__one_week(self):
        service = DateTimeService()

        start_date = date(2016, 5, 8)
        end_date = date(2016, 5, 8)

        result = service.get_list_of_week_start_dates_in_range(
            start_date, end_date)

        self.assertEqual(type(result), list)
        self.assertEqual(len(result), 1)

        self.assertEqual(result[0], date(2016, 5, 8))
Example #2
0
    def test_get_list_of_week_start_dates_in_range__two_weeks_at_end(self):
        service = DateTimeService()

        start_date = date(2016, 5, 14)
        end_date = date(2016, 5, 21)

        result = service.get_list_of_week_start_dates_in_range(
            start_date, end_date)

        self.assertEqual(type(result), list)
        self.assertEqual(len(result), 2)

        self.assertEqual(result[0], date(2016, 5, 8))
        self.assertEqual(result[1], date(2016, 5, 15))
Example #3
0
    def test_get_list_of_week_start_dates_in_range__three_weeks_at_middel(
            self):
        service = DateTimeService()

        start_date = date(2016, 5, 10)
        end_date = date(2016, 5, 25)

        result = service.get_list_of_week_start_dates_in_range(
            start_date, end_date)

        self.assertEqual(type(result), list)
        self.assertEqual(len(result), 3)

        self.assertEqual(result[0], date(2016, 5, 8))
        self.assertEqual(result[1], date(2016, 5, 15))
        self.assertEqual(result[2], date(2016, 5, 22))
Example #4
0
    def get(self, request, pk,
            from_year, from_month, from_day,
            to_year, to_month, to_day, format=None):
        comp = self._get_company_info(pk)
        week_start_date = date(year=int(from_year), month=int(from_month), day=int(from_day))
        end_week_start_date = date(year=int(to_year), month=int(to_month), day=int(to_day))
        book = xlwt.Workbook(encoding='utf8')
        sheet = book.add_sheet('Timesheet')
        time_tracking_service = TimeTrackingService()
        submitted_sheets = time_tracking_service.get_company_users_submitted_work_timesheet_by_week_range(
            comp.id,
            week_start_date,
            end_week_start_date)

        self._write_headers(sheet)

        row_num = 1

        # Now enumerate every single week within the specified
        # time range and delegate to the writing method to
        # determine what to write out
        # i.e. even if the retrieved submitted time sheets do
        # not contain data for a week, we should still attempt
        # to run through that week, and let the logic to decide
        # what to write. e.g. default weekly total for fulltime
        # employees, and empty for part time workers, etc.
        date_time_service = DateTimeService()
        week_start_dates = date_time_service.get_list_of_week_start_dates_in_range(week_start_date, end_week_start_date)
        for week_start_date in week_start_dates:
            timesheet = submitted_sheets.get(week_start_date, [])
            row_num = self._write_company(row_num, comp, week_start_date, sheet, timesheet)

        response = HttpResponse(content_type='application/vnd.ms-excel')

        # Need company name:

        response['Content-Disposition'] = (
            'attachment; filename={0}_employee_worktime_report_{1}.xls'
        ).format(comp, week_start_date.strftime('%m_%d_%Y'))
        book.save(response)
        return response
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
Example #6
0
class TimeOffRecord(object):
    hash_key_service = HashKeyService()
    date_time_service = DateTimeService()

    def __init__(self, time_off_domain_model):
        if (time_off_domain_model is None):
            raise ValueError('Must pass valid time off domain model.')

        # List out instance variables
        self.requestor_user_id = None
        self.requestor_user_info = None
        self.approver_user_id = None
        self.approver_user_info = None
        self.start_date_time = None
        self.duration = None
        self.status = None
        self.decision_timestamp = None
        self.record_type = None
        self.request_timestamp = None

        # Parse requestor info
        requestor_user_descriptor = time_off_domain_model['requestor'][
            'personDescriptor']
        self.requestor_user_id = int(
            self.hash_key_service.decode_key_with_environment(
                requestor_user_descriptor))
        if (self.requestor_user_id):
            user_model = User.objects.get(pk=self.requestor_user_id)
            self.requestor_user_info = UserInfo(user_model)

        # Parse approver info
        if ('approver' in time_off_domain_model
                and time_off_domain_model['approver']):
            approver_user_descriptor = time_off_domain_model['approver'][
                'personDescriptor']
            self.approver_user_id = int(
                self.hash_key_service.decode_key_with_environment(
                    approver_user_descriptor))
            if (self.approver_user_id):
                user_model = User.objects.get(pk=self.approver_user_id)
                self.approver_user_info = UserInfo(user_model)

        # Parse record type
        self.record_type = time_off_domain_model['type']

        # Parse Duration
        self.duration = float(time_off_domain_model['duration'])

        # Parse status
        self.status = time_off_domain_model['status']

        # Parse all dates and times to objects
        self.start_date_time = self.date_time_service.parse_date_time(
            time_off_domain_model['startDateTime'])
        self.request_timestamp = self.date_time_service.parse_date_time(
            time_off_domain_model['requestTimestamp'])

        if ('decisionTimestamp' in time_off_domain_model):
            decision_time_str = time_off_domain_model['decisionTimestamp']
            self.decision_timestamp = self.date_time_service.parse_date_time(
                decision_time_str)

    @property
    def requestor_full_name(self):
        if (self.requestor_user_info is None):
            return None
        return self.requestor_user_info.full_name

    @property
    def approver_full_name(self):
        if (self.approver_user_info is None):
            return None
        return self.approver_user_info.full_name
Example #7
0
 def _get_last_week_start_date(self):
     date_time_service = DateTimeService()
     return date_time_service.get_last_week_range_by_date(date.today())[0]
Example #8
0
class TimePunchCard(object):
    hash_key_service = HashKeyService()
    date_time_service = DateTimeService()

    def __init__(self, punch_card_domain_model):
        if (punch_card_domain_model is None):
            raise ValueError('Must pass valid time punch card domain model.')

        # List out instance variables
        self.user_id = None
        self.user_info = None
        self.date = None
        self.start = None
        self.end = None
        self.state = None
        self.card_type = None
        self.in_progress = None
        self.system_stopped = None

        # Parse out user ID
        user_descriptor = punch_card_domain_model['employee'][
            'personDescriptor']
        self.user_id = int(
            self.hash_key_service.decode_key_with_environment(user_descriptor))

        # Parse card type
        self.card_type = punch_card_domain_model.get('recordType')

        # Parse all dates and times to objects
        self.date = self.date_time_service.parse_date_time(
            punch_card_domain_model['date'])

        start_str = punch_card_domain_model.get('start')
        if (start_str):
            self.start = self.date_time_service.parse_date_time(start_str)

        end_str = punch_card_domain_model.get('end')
        if (end_str):
            self.end = self.date_time_service.parse_date_time(end_str)

        # Parse attributes
        attributes = punch_card_domain_model.get('attributes')
        if (attributes):
            for attribute in attributes:
                # For now only cares about state
                if attribute['name'] == PUNCH_CARD_ATTRIBUTE_TYPE_STATE:
                    self.state = attribute['value']
                    break

        in_progress_str = punch_card_domain_model.get('inProgress')
        if (in_progress_str):
            self.in_progress = bool(in_progress_str)

        system_stopped_str = punch_card_domain_model.get('systemStopped')
        if (system_stopped_str):
            self.system_stopped = bool(system_stopped_str)

        # Support lasy-evaluated validation
        self._validation_issues = None

    def get_punch_card_hours(self):
        raw_hours = self.__get_raw_card_hours()
        if self.card_type == PUNCH_CARD_TYPE_BREAK_TIME:
            return -raw_hours
        return raw_hours

    def __get_raw_card_hours(self):
        if (self.start is not None and self.end is not None):
            card_hours = self.date_time_service.get_time_diff_in_hours(
                self.start, self.end, 2)
            return card_hours
        return 0.0

    def get_card_day_of_week_iso(self):
        return self.date.isoweekday() % 7

    @property
    def validation_issues(self):
        if (self._validation_issues is None):
            self._validation_issues = self._validate()

        return self._validation_issues

    def is_valid(self):
        issues = self.validation_issues
        blocking_issue = next(
            (issue for issue in issues
             if issue.level > TimeCardValidationIssue.LEVEL_WARNING), None)
        return blocking_issue is None

    def _validate(self):
        validation_issues = []

        card_hours = self.__get_raw_card_hours()

        # 1. Unclosed timecard (clocked in, but not out)
        if ((self.start is not None and self.end is None)
                or (self.in_progress)):
            validation_issues.append(
                TimeCardValidationIssue(
                    TimeCardValidationIssue.LEVEL_ERROR,
                    '[Unclosed Card] Clocked in, but not out, by midnight.'))

        # 2. Negative hours
        if (card_hours < 0.0):
            validation_issues.append(
                TimeCardValidationIssue(
                    TimeCardValidationIssue.LEVEL_ERROR,
                    '[Invalid Card Balance] Card with negative accounted hours.'
                ))

        # 3. Long working hours, such as over 10 hours work
        if (card_hours >= 10.0):
            validation_issues.append(
                TimeCardValidationIssue(
                    TimeCardValidationIssue.LEVEL_WARNING,
                    '[Unusual Card Balance] Card with more than 10 hours.'))

        if (self.system_stopped):
            validation_issues.append(
                TimeCardValidationIssue(
                    TimeCardValidationIssue.LEVEL_ERROR,
                    '[System Closed Card] Card stopped accruing hours by system. Please validate!'
                ))

        return validation_issues
Example #9
0
 def __init__(self):
     self._date_time_service = DateTimeService()
Example #10
0
class CompanyPersonnelService(object):
    def __init__(self):
        self._date_time_service = DateTimeService()

    def is_user_employee(self, user_id):
        try:
            employee = CompanyUser.objects.get(
                user=user_id, company_user_type=USER_TYPE_EMPLOYEE)
            return True
        except CompanyUser.DoesNotExist:
            return False

    def get_company_id_by_employee_user_id(self, employee_user_id):
        employees = CompanyUser.objects.filter(
            user=employee_user_id, company_user_type=USER_TYPE_EMPLOYEE)
        if (len(employees) > 0):
            return employees[0].company_id
        return None

    ''' Get all employees who is not fully terminated during the given
        time range. i.e. the employee has other employment statuses in
        the time range than 'Terminated'.
        This is to serve a common use case of where reports want to
        include only those employees that are at least partially relate
        to the company in the view port time range.
    '''

    def get_company_employee_user_ids_non_fully_terminated_in_time_range(
            self, company_id, time_range_start, time_range_end):
        all_employee_mappings = self._get_company_employee_user_ids_to_employment_statuses_map(
            company_id, time_range_start, time_range_end)
        filtered_user_ids = []

        for user_id in all_employee_mappings:
            for status in all_employee_mappings[user_id]:
                if (not status == EMPLOYMENT_STATUS_TERMINATED):
                    filtered_user_ids.append(user_id)
                    break

        return filtered_user_ids

    ''' Get all employees who are part of the specified employment status as of now
        This is to serve a common use case of where we want to know which
        employees are of the specified employment status
    '''

    def get_company_employee_user_ids_currently_with_status(
            self, company_id, employment_status):
        return get_company_employee_user_ids_with_status_in_time_range(
            company_id, employment_status, datetime.date.today(),
            datetime.date.today())

    ''' Get all employees who are part of the specified employment status as of the
        specified time range.
        This is to serve a common use case of where we want to know which
        employees are of the specified employment status within the time range defined
    '''

    def get_company_employee_user_ids_with_status_in_time_range(
            self, company_id, employment_status, time_range_start,
            time_range_end):

        all_employee_mappings = self._get_company_employee_user_ids_to_employment_statuses_map(
            company_id, time_range_start, time_range_end)

        employees_with_status = []
        for user_id in all_employee_mappings:
            for status in all_employee_mappings[user_id]:
                if employment_status == status:
                    employees_with_status.append(user_id)
                    break

        return employees_with_status

    ''' Get a mapping, where each pair represents a employee 
        mapped to a list that contains all employement statuses
        that were at least partially "on" during the given time
        range. 
        E.g. if an employee was working for the first 2 days in
        the time range, but got employment terminated since day 3,
        then this employee would map to a list that contains 2
        statuses: Active and Terminated.
    '''

    def _get_company_employee_user_ids_to_employment_statuses_map(
            self, company_id, time_range_start, time_range_end):
        result = {}
        all_employees = CompanyUser.objects.filter(
            company=company_id, company_user_type=USER_TYPE_EMPLOYEE)
        for employee in all_employees:
            # populate with empty values
            result[employee.user_id] = []

        all_employee_profiles = EmployeeProfile.objects.filter(
            company=company_id)
        for employee_profile in all_employee_profiles:
            employee_user_id = employee_profile.person.user.id
            if (employee_user_id in result):
                result[
                    employee_user_id] = self._get_employment_statuses_in_time_range(
                        employee_profile, time_range_start, time_range_end)
            else:
                raise Exception(
                    'Found company employee profile for user that does not relate to the company! Offending user_id is: {}'
                    .format(employee_user_id))

        return result

    def _get_employment_statuses_in_time_range(self, employee_profile,
                                               time_range_start,
                                               time_range_end):
        result = []

        # [TODO]: We currently don't model a complete trail of
        #       employment status changes, and hence we are
        #       currently only able to infer some information
        #       from employee's start and end employment dates

        # Conditions
        #  * If employment start date after time range, result => [Prospective]
        #  * If employment end date prior to time range, result => [Terminated]
        #  * If employment start date prior to time range and end date after => [Active]
        #  * If employment start date within time range, result to include/add [Prospective, Active]
        #  * If employment end date within time range, result to include/add [Active, Terminated]
        if ((employee_profile.start_date or datetime.date.min) >
                time_range_end):
            result = [EMPLOYMENT_STATUS_PROSPECTIVE]
        elif (employee_profile.end_date
              and employee_profile.end_date < time_range_start):
            result = [EMPLOYMENT_STATUS_TERMINATED]
        elif ((employee_profile.start_date
               or datetime.date.min) <= time_range_start
              and (not employee_profile.end_date
                   or employee_profile.end_date >= time_range_end)):
            result = [EMPLOYMENT_STATUS_ACTIVE]

        if (self._date_time_service.is_time_in_range(
                employee_profile.start_date or datetime.date.min,
                time_range_start, time_range_end)):
            self._ensure_value_in_list(result, EMPLOYMENT_STATUS_PROSPECTIVE)
            self._ensure_value_in_list(result, EMPLOYMENT_STATUS_ACTIVE)

        if (employee_profile.end_date
                and self._date_time_service.is_time_in_range(
                    employee_profile.end_date, time_range_start,
                    time_range_end)):
            self._ensure_value_in_list(result, EMPLOYMENT_STATUS_ACTIVE)
            self._ensure_value_in_list(result, EMPLOYMENT_STATUS_TERMINATED)

        return result

    def _ensure_value_in_list(self, input_list, value):
        if (not value):
            return
        if (value in input_list):
            return

        input_list.append(value)

    def _get_direct_report_profiles(self, company_id, manager_user_id):
        if not company_id or not manager_user_id:
            return []
        try:
            manager_employee_profile = EmployeeProfile.objects.get(
                person__user=manager_user_id,
                person__relationship=SELF,
                company=company_id)
        except EmployeeProfile.DoesNotExist:
            return []
        direct_report_profiles = EmployeeProfile.objects.filter(
            manager=manager_employee_profile,
            employment_status=EMPLOYMENT_STATUS_ACTIVE,
            company=company_id)
        return direct_report_profiles

    ''' Get the list of direct report company_users from the input manager user.
        Company_id must be provided to make sure the correct company data is queried.
    '''

    def get_direct_report_company_users(self, company_id, manager_user_id):
        direct_report_profiles = self._get_direct_report_profiles(
            company_id, manager_user_id)
        if not direct_report_profiles:
            return []
        direct_report_users = [
            profile.person.user for profile in direct_report_profiles
        ]
        return CompanyUser.objects.filter(company=company_id,
                                          company_user_type=USER_TYPE_EMPLOYEE,
                                          user__in=direct_report_users)

    ''' Get the number of direct reports from the input manager user.
        Company_id must be provided to make sure the correct company data is queried.
    '''

    def get_direct_report_count(self, company_id, manager_user_id):
        direct_report_profiles = self._get_direct_report_profiles(
            company_id, manager_user_id)
        if not direct_report_profiles:
            return 0
        return direct_report_profiles.count()
class EmployeeDailyPunchCardAggregate(object):
    date_time_service = DateTimeService()

    def __init__(self, employee_user_id, date):

        # List out instance variables
        self.user_id = employee_user_id
        self._user_info = None
        self._user_info_initialized = False
        self.date = date
        self._time_punch_cards = []

        # Support lasy-evaluated validation
        self._validation_issues = None

    @property
    def user_info(self):
        if (not self._user_info_initialized):
            if (self.user_id):
                user_model = User.objects.get(pk=self.user_id)
                self._user_info = UserInfo(user_model)
            self._user_info_initialized = True
        return self._user_info

    @property
    def validation_issues(self):
        if (self._validation_issues is None):
            self._validation_issues = self._validate()

        return self._validation_issues

    def _validate(self):
        validation_issues = []

        [
            validation_issues.extend(card.validation_issues)
            for card in self._time_punch_cards
        ]

        # Aggregate specific validations
        hours = self.get_total_hours()

        ## 1.Long working hours in a day, such as over 10 hours work
        if (hours >= 10.0):
            validation_issues.append(
                TimeCardValidationIssue(
                    TimeCardValidationIssue.LEVEL_WARNING,
                    '[Unusual Daily Balance] More than 10 total hours filed in a day.'
                ))

        return validation_issues

    def get_total_hours(self):
        return sum(card.get_punch_card_hours()
                   for card in self._time_punch_cards)

    @property
    def employee_full_name(self):
        if (self.user_info is None):
            return None
        return self.user_info.full_name

    def add_card(self, time_punch_card):
        self._time_punch_cards.append(time_punch_card)