( time() + ( 3650 * 86400 ) ) ) { //Make sure date is after 01-Jan-2000 and before 10 years in the future. $epoch = TTDate::getTime(); } $start_date = TTDate::getBeginWeekEpoch( $epoch, $this->getCurrentUserPreferenceObject()->getStartWeekDay() ); $end_date = TTDate::getEndWeekEpoch( $epoch, $this->getCurrentUserPreferenceObject()->getStartWeekDay() ); $retarr = [ 'base_date' => $epoch, 'start_date' => $start_date, 'end_date' => $end_date, 'base_display_date' => TTDate::getAPIDate( 'DATE', $epoch ), 'start_display_date' => TTDate::getAPIDate( 'DATE', $start_date ), 'end_display_date' => TTDate::getAPIDate( 'DATE', $end_date ), ]; return $retarr; } /** * Get all data for displaying the timesheet. * @param string $user_id UUID * @param int $base_date EPOCH * @param bool $data * @return array|bool */ function getTimeSheetData( $user_id, $base_date = null, $data = null ) { if ( $user_id == '' || TTUUID::isUUID( $user_id ) == false ) { //This isn't really permission issue, but in cases where the user can't see any employees timesheets, we want to display an error to them at least. //return $this->returnHandler( FALSE ); return $this->getPermissionObject()->PermissionDenied(); } $user_id = TTUUID::castUUID( $user_id ); if ( $base_date == '' ) { return $this->returnHandler( false ); } $profile_start = microtime( true ); if ( !$this->getPermissionObject()->Check( 'punch', 'enabled' ) || !( $this->getPermissionObject()->Check( 'punch', 'view' ) || $this->getPermissionObject()->Check( 'punch', 'view_child' ) || $this->getPermissionObject()->Check( 'punch', 'view_own' ) ) ) { return $this->getPermissionObject()->PermissionDenied(); } //Get Permission Hierarchy Children first, as this can be used for viewing, or editing. //Check for ===FALSE on permission_children_ids, as that means their are no children assigned to them and they don't have view all permissions. $data['filter_data']['permission_children_ids'] = $this->getPermissionObject()->getPermissionChildren( 'punch', 'view' ); if ( $data['filter_data']['permission_children_ids'] === false || ( is_array( $data['filter_data']['permission_children_ids'] ) && !in_array( $user_id, $data['filter_data']['permission_children_ids'] ) ) ) { return $this->getPermissionObject()->PermissionDenied(); } // //Get timesheet start/end dates. // $timesheet_dates = $this->getTimesheetDates( $base_date ); //Include all dates within the timesheet range. $timesheet_dates['pay_period_date_map'] = []; //Add array containing date => pay_period_id pairs. // //Get PayPeriod information // $pplf = TTnew( 'PayPeriodListFactory' ); /** @var PayPeriodListFactory $pplf */ $pplf->StartTransaction(); //Make sure we all pay periods that fall within the start/end date, so we can properly display the timesheet range at the top. $primary_pay_period_id = TTUUID::getZeroID(); $pay_period_ids = []; $pplf->getByUserIdAndOverlapStartDateAndEndDate( $user_id, $timesheet_dates['start_date'], $timesheet_dates['end_date'] ); if ( $pplf->getRecordCount() > 0 ) { foreach ( $pplf as $pp_obj ) { $pay_period_ids[] = $pp_obj->getId(); if ( $pp_obj->getStartDate() <= $timesheet_dates['base_date'] && $pp_obj->getEndDate() >= $timesheet_dates['base_date'] ) { $primary_pay_period_id = $pp_obj->getId(); } $timesheet_dates['pay_period_date_map'] += (array)$pp_obj->getPayPeriodDates( $timesheet_dates['start_date'], $timesheet_dates['end_date'], true ); } unset( $pp_obj ); } //Debug::Text('Pay Periods: '. $pplf->getRecordCount() .' Primary Pay Period: '. $primary_pay_period_id, __FILE__, __LINE__, __METHOD__, 10); //Debug::Arr($timesheet_dates, 'TimeSheet Dates: ', __FILE__, __LINE__, __METHOD__, 10); // //Get punches // $punch_data = []; $filter_data = $this->initializeFilterAndPager( [ 'filter_data' => [ 'start_date' => $timesheet_dates['start_date'], 'end_date' => $timesheet_dates['end_date'], 'user_id' => $user_id ] ], true ); //Carry over timesheet filter options. if ( isset( $data['filter_data']['branch_id'] ) ) { $filter_data['filter_data']['branch_id'] = $data['filter_data']['branch_id']; } if ( isset( $data['filter_data']['department_id'] ) ) { $filter_data['filter_data']['department_id'] = $data['filter_data']['department_id']; } if ( isset( $data['filter_data']['job_id'] ) ) { $filter_data['filter_data']['job_id'] = $data['filter_data']['job_id']; } if ( isset( $data['filter_data']['job_item_id'] ) ) { $filter_data['filter_data']['job_item_id'] = $data['filter_data']['job_item_id']; } $plf = TTnew( 'PunchListFactory' ); /** @var PunchListFactory $plf */ $plf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCurrentCompanyObject()->getId(), $filter_data['filter_data'], $filter_data['filter_items_per_page'], $filter_data['filter_page'], null, [ 'b.pay_period_id' => 'asc', 'b.user_id' => 'asc', 'a.time_stamp' => 'asc', 'a.punch_control_id' => 'asc', 'a.status_id' => 'asc' ] ); //Order is critical to the timesheet layout. Debug::Text( 'Punch Record Count: ' . $plf->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10 ); if ( $plf->getRecordCount() > 0 ) { //Reduces data transfer by about half. $punch_columns = [ 'id' => true, 'user_id' => true, 'transfer' => true, 'type_id' => true, 'type' => true, 'status_id' => true, 'status' => true, 'time_stamp' => true, 'raw_time_stamp' => true, 'punch_date' => true, 'punch_time' => true, 'actual_time_stamp' => true, //Required for hiding duplicates when there is a pending punch from job queue. 'punch_control_id' => true, 'longitude' => true, 'latitude' => true, 'position_accuracy' => true, 'date_stamp' => true, 'pay_period_id' => true, 'note' => true, 'tainted' => true, 'has_image' => true, 'branch_id' => true, 'department_id' => true, 'job_id' => true, 'job_item_id' => true, ]; foreach ( $plf as $p_obj ) { //$punch_data[] = $p_obj->getObjectAsArray( NULL, $data['filter_data']['permission_children_ids'] ); //Don't need to pass permission_children_ids, as Flex uses is_owner/is_child from the timesheet user record instead, not the punch record. $punch_data[] = $p_obj->getObjectAsArray( $punch_columns ); } } $meal_and_break_total_data = PunchFactory::calcMealAndBreakTotalTime( $punch_data ); if ( $meal_and_break_total_data === false ) { $meal_and_break_total_data = []; } //Get Wage Permission Hierarchy Children first, as this can be used for viewing, or editing. $wage_permission_children_ids = $this->getPermissionObject()->getPermissionChildren( 'wage', 'view' ); // //Get total time for day/pay period // $user_date_total_data = []; $absence_user_date_total_data = []; $udt_filter_data = $this->initializeFilterAndPager( [ 'filter_data' => [ 'start_date' => $timesheet_dates['start_date'], 'end_date' => $timesheet_dates['end_date'], 'user_id' => $user_id ] ], true ); //Carry over timesheet filter options. if ( isset( $data['filter_data']['branch_id'] ) ) { $udt_filter_data['filter_data']['branch_id'] = $data['filter_data']['branch_id']; } if ( isset( $data['filter_data']['department_id'] ) ) { $udt_filter_data['filter_data']['department_id'] = $data['filter_data']['department_id']; } if ( isset( $data['filter_data']['job_id'] ) ) { $udt_filter_data['filter_data']['job_id'] = $data['filter_data']['job_id']; } if ( isset( $data['filter_data']['job_item_id'] ) ) { $udt_filter_data['filter_data']['job_item_id'] = $data['filter_data']['job_item_id']; } $udtlf = TTnew( 'UserDateTotalListFactory' ); /** @var UserDateTotalListFactory $udtlf */ $udtlf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCurrentCompanyObject()->getId(), $udt_filter_data['filter_data'], $udt_filter_data['filter_items_per_page'], $udt_filter_data['filter_page'], null, $udt_filter_data['filter_sort'] ); Debug::Text( 'User Date Total Record Count: ' . $udtlf->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10 ); if ( $udtlf->getRecordCount() > 0 ) { //Specifying the columns is about a 30% speed up for large timesheets. $udt_columns = [ 'id' => true, 'user_id' => true, 'date_stamp' => true, //'status_id' => TRUE, //'type_id' => TRUE, 'object_type_id' => true, 'src_object_id' => true, 'pay_code_id' => true, 'policy_name' => true, 'name' => true, 'branch_id' => true, 'branch' => true, 'department_id' => true, 'department' => true, 'job_id' => true, 'job' => true, 'job_item_id' => true, 'job_item' => true, 'total_time' => true, 'pay_period_id' => true, 'override' => true, 'note' => true, ]; if ( $this->getPermissionObject()->isPermissionChild( $user_id, $wage_permission_children_ids ) ) { $udt_columns['total_time_amount'] = true; $udt_columns['hourly_rate'] = true; } foreach ( $udtlf as $udt_obj ) { //Don't need to pass permission_children_ids, as Flex uses is_owner/is_child from the timesheet user record instead, not the punch record. //$user_date_total = $udt_obj->getObjectAsArray( NULL, $data['filter_data']['permission_children_ids'] ); $user_date_total = $udt_obj->getObjectAsArray( $udt_columns ); $user_date_total_data[] = $user_date_total; //Extract just absence records so we can send those to the user, rather than all UDT rows as only absences are used. if ( $user_date_total['object_type_id'] == 50 ) { $absence_user_date_total_data[] = $user_date_total; } //Get all pay periods that have total time assigned to them. $timesheet_dates['pay_period_date_map'][$user_date_total['date_stamp']] = $pay_period_ids[] = $user_date_total['pay_period_id']; //Adjust primary pay period if the pay period schedules were changed mid-way through perhaps. if ( $timesheet_dates['base_display_date'] == $user_date_total['date_stamp'] && $timesheet_dates['pay_period_date_map'][$user_date_total['date_stamp']] != $primary_pay_period_id ) { $primary_pay_period_id = $user_date_total['pay_period_id']; Debug::Text( 'Changing primary pay period to: ' . $primary_pay_period_id, __FILE__, __LINE__, __METHOD__, 10 ); } } unset( $user_date_total ); } //Debug::Arr( $timesheet_dates['pay_period_date_map'], 'Date/Pay Period IDs. Primary Pay Period ID: ' . $primary_pay_period_id, __FILE__, __LINE__, __METHOD__, 10 ); $accumulated_user_date_total_data = UserDateTotalFactory::calcAccumulatedTime( $user_date_total_data ); if ( $accumulated_user_date_total_data === false ) { $accumulated_user_date_total_data = []; } unset( $user_date_total_data ); //Get data for all pay periods $pay_period_data = []; $pplf->getByIDList( $pay_period_ids ); if ( $pplf->getRecordCount() > 0 ) { $pp_columns = [ 'id' => true, 'status_id' => true, //'status' => TRUE, 'type_id' => true, //'type' => TRUE, //'pay_period_schedule_id' => TRUE, 'start_date' => true, 'end_date' => true, 'transaction_date' => true, ]; foreach ( $pplf as $pp_obj ) { $pay_period_data[$pp_obj->getId()] = $pp_obj->getObjectAsArray( $pp_columns ); $pay_period_data[$pp_obj->getId()]['timesheet_verify_type_id'] = $pp_obj->getTimeSheetVerifyType(); $pay_period_data[$pp_obj->getId()]['start_date_epoch'] = $pp_obj->getStartDate(); $pay_period_data[$pp_obj->getId()]['end_date_epoch'] = $pp_obj->getEndDate(); } } unset( $pp_obj ); //Fill in payperiod gaps in timesheet primarily for migrating from one schedule to another or for new hires in middle of a pay period $calendar_dates = TTDate::getCalendarArray( $timesheet_dates['start_date'], $timesheet_dates['end_date'], 0, false ); foreach ( $calendar_dates as $tmp_date ) { if ( !isset( $timesheet_dates['pay_period_date_map'][TTDate::getDate( 'DATE', $tmp_date['epoch'] )] ) ) { foreach ( $pay_period_data as $tmp_pp_data ) { if ( $tmp_date['epoch'] >= $tmp_pp_data['start_date_epoch'] && $tmp_date['epoch'] <= $tmp_pp_data['end_date_epoch'] ) { $timesheet_dates['pay_period_date_map'][TTDate::getDate( 'DATE', $tmp_date['epoch'] )] = $tmp_pp_data['id']; } } } } ksort( $timesheet_dates['pay_period_date_map'] ); unset( $calendar_dates, $tmp_date, $tmp_pp_data ); $pp_user_date_total_data = []; $pay_period_accumulated_user_date_total_data = []; if ( isset( $primary_pay_period_id ) && TTUUID::isUUID( $primary_pay_period_id ) && $primary_pay_period_id != TTUUID::getZeroID() && $primary_pay_period_id != TTUUID::getNotExistID() ) { $pp_udt_filter_data = $this->initializeFilterAndPager( [ 'filter_data' => [ 'pay_period_id' => $primary_pay_period_id, 'user_id' => $user_id ] ], true ); //Carry over timesheet filter options. if ( isset( $data['filter_data']['branch_id'] ) ) { $pp_udt_filter_data['filter_data']['branch_id'] = $data['filter_data']['branch_id']; } if ( isset( $data['filter_data']['department_id'] ) ) { $pp_udt_filter_data['filter_data']['department_id'] = $data['filter_data']['department_id']; } if ( isset( $data['filter_data']['job_id'] ) ) { $pp_udt_filter_data['filter_data']['job_id'] = $data['filter_data']['job_id']; } if ( isset( $data['filter_data']['job_item_id'] ) ) { $pp_udt_filter_data['filter_data']['job_item_id'] = $data['filter_data']['job_item_id']; } $udtlf = TTnew( 'UserDateTotalListFactory' ); /** @var UserDateTotalListFactory $udtlf */ $udtlf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCurrentCompanyObject()->getId(), $pp_udt_filter_data['filter_data'], $pp_udt_filter_data['filter_items_per_page'], $pp_udt_filter_data['filter_page'], null, $pp_udt_filter_data['filter_sort'] ); Debug::Text( 'PP User Date Total Record Count: ' . $udtlf->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10 ); if ( $udtlf->getRecordCount() > 0 ) { //Specifying the columns is about a 30% speed up for large timesheets. //This is only needed for calcAccumulatedTime(). $udt_columns = [ 'object_type_id' => true, 'date_stamp' => true, 'name' => true, 'pay_code_id' => true, 'total_time' => true, 'branch_id' => true, 'branch' => true, 'department_id' => true, 'department' => true, 'job_id' => true, 'job' => true, 'job_item_id' => true, 'job_item' => true, ]; if ( $this->getPermissionObject()->isPermissionChild( $user_id, $wage_permission_children_ids ) ) { $udt_columns['total_time_amount'] = true; $udt_columns['hourly_rate'] = true; } foreach ( $udtlf as $udt_obj ) { $pp_user_date_total_data[] = $udt_obj->getObjectAsArray( $udt_columns ); } $pay_period_accumulated_user_date_total_data = UserDateTotalFactory::calcAccumulatedTime( $pp_user_date_total_data, false ); if ( isset( $pay_period_accumulated_user_date_total_data['total'] ) ) { $pay_period_accumulated_user_date_total_data = $pay_period_accumulated_user_date_total_data['total']; } else { $pay_period_accumulated_user_date_total_data = []; } } } unset( $pp_user_date_total_data ); // //Get Exception data, use the same filter data as punches. // $exception_data = []; $elf = TTnew( 'ExceptionListFactory' ); /** @var ExceptionListFactory $elf */ $elf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCurrentCompanyObject()->getId(), $filter_data['filter_data'], $filter_data['filter_items_per_page'], $filter_data['filter_page'], null, $filter_data['filter_sort'] ); Debug::Text( 'Exception Record Count: ' . $elf->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10 ); if ( $elf->getRecordCount() > 0 ) { //Reduces data transfer. $exception_columns = [ 'id' => true, 'date_stamp' => true, 'exception_policy_id' => true, 'punch_control_id' => true, 'punch_id' => true, 'type_id' => true, 'type' => true, 'severity_id' => true, 'severity' => true, 'exception_color' => true, 'exception_background_color' => true, 'exception_policy_type_id' => true, 'exception_policy_type' => true, 'pay_period_id' => true, ]; foreach ( $elf as $e_obj ) { $exception_data[] = $e_obj->getObjectAsArray( $exception_columns ); } } unset( $elf, $e_obj, $exception_columns ); // //Get request data, so authorized/pending can be shown in a request row for each day. //If there are two requests for both authorized and pending, the pending is displayed. // $request_data = []; $rlf = TTnew( 'RequestListFactory' ); /** @var RequestListFactory $rlf */ $rlf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCurrentCompanyObject()->getId(), $filter_data['filter_data'], $filter_data['filter_items_per_page'], $filter_data['filter_page'], null, $filter_data['filter_sort'] ); Debug::Text( 'Request Record Count: ' . $rlf->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10 ); if ( $rlf->getRecordCount() > 0 ) { $request_columns = [ 'id' => true, 'user_id' => true, 'type_id' => true, 'status_id' => true, 'status' => true, 'date_stamp' => true, 'authorized' => true, ]; foreach ( $rlf as $r_obj ) { $request_data[] = $r_obj->getObjectAsArray( $request_columns ); } } unset( $rlf, $r_obj, $request_columns ); // //Get timesheet verification information. // $timesheet_verify_data = []; if ( isset( $primary_pay_period_id ) && TTUUID::isUUID( $primary_pay_period_id ) && $primary_pay_period_id != TTUUID::getZeroID() && $primary_pay_period_id != TTUUID::getNotExistID() ) { $pptsvlf = TTnew( 'PayPeriodTimeSheetVerifyListFactory' ); /** @var PayPeriodTimeSheetVerifyListFactory $pptsvlf */ $pptsvlf->getByPayPeriodIdAndUserId( $primary_pay_period_id, $user_id ); if ( $pptsvlf->getRecordCount() > 0 ) { $pptsv_obj = $pptsvlf->getCurrent(); $pptsv_obj->setCurrentUser( $this->getCurrentUserObject()->getId() ); } else { $pptsv_obj = $pptsvlf; $pptsv_obj->setCurrentUser( $this->getCurrentUserObject()->getId() ); $pptsv_obj->setUser( $user_id ); $pptsv_obj->setPayPeriod( $primary_pay_period_id ); //$pptsv_obj->setStatus( 45 ); //Pending Verification } $verification_window_dates = $pptsv_obj->getVerificationWindowDates(); if ( is_array( $verification_window_dates ) ) { $verification_window_dates['start'] = TTDate::getAPIDate( 'DATE', $verification_window_dates['start'] ); $verification_window_dates['end'] = TTDate::getAPIDate( 'DATE', $verification_window_dates['end'] ); } $timesheet_verify_data = [ 'id' => $pptsv_obj->getId(), 'user_verified' => $pptsv_obj->getUserVerified(), 'user_verified_date' => $pptsv_obj->getUserVerifiedDate(), 'status_id' => $pptsv_obj->getStatus(), 'status' => Option::getByKey( $pptsv_obj->getStatus(), $pptsv_obj->getOptions( 'status' ) ), 'pay_period_id' => $pptsv_obj->getPayPeriod(), 'user_id' => $pptsv_obj->getUser(), 'authorized' => $pptsv_obj->getAuthorized(), 'is_hierarchy_superior' => $pptsv_obj->isHierarchySuperior(), 'display_verify_button' => $pptsv_obj->displayVerifyButton(), 'verification_box_color' => $pptsv_obj->getVerificationBoxColor(), 'verification_status_display' => $pptsv_obj->getVerificationStatusDisplay(), 'previous_pay_period_verification_display' => $pptsv_obj->displayPreviousPayPeriodVerificationNotice(), 'verification_confirmation_message' => $pptsv_obj->getVerificationConfirmationMessage(), 'verification_window_dates' => $verification_window_dates, 'created_date' => $pptsv_obj->getCreatedDate(), 'created_by' => $pptsv_obj->getCreatedBy(), 'updated_date' => $pptsv_obj->getUpdatedDate(), 'updated_by' => $pptsv_obj->getUpdatedBy(), //'deleted_date' => $pptsv_obj->getDeletedDate(), //'deleted_by' => $pptsv_obj->getDeletedBy() ]; unset( $pptsvlf, $pptsv_obj, $verification_window_dates ); if ( isset( $pay_period_data[$primary_pay_period_id] ) ) { $timesheet_verify_data['pay_period_verify_type_id'] = $pay_period_data[$primary_pay_period_id]['timesheet_verify_type_id']; } } // //Get holiday data. // $holiday_data = []; $hlf = TTnew( 'HolidayListFactory' ); /** @var HolidayListFactory $hlf */ $hlf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCurrentCompanyObject()->getId(), [ 'start_date' => $timesheet_dates['start_date'], 'end_date' => $timesheet_dates['end_date'], 'user_id' => $user_id ], $filter_data['filter_items_per_page'], $filter_data['filter_page'], null, $filter_data['filter_sort'] ); Debug::Text( 'Holiday Record Count: ' . $hlf->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10 ); if ( $hlf->getRecordCount() > 0 ) { $holiday_columns = [ 'id' => true, 'holiday_policy_id' => true, 'date_stamp' => true, 'name' => true, ]; foreach ( $hlf as $h_obj ) { $holiday_data[] = $h_obj->getObjectAsArray( $holiday_columns ); } } unset( $hlf, $h_obj, $holiday_columns ); $pplf->CommitTransaction(); $retarr = [ 'timesheet_dates' => $timesheet_dates, 'pay_period_data' => $pay_period_data, 'punch_data' => $punch_data, 'holiday_data' => $holiday_data, 'user_date_total_data' => $absence_user_date_total_data, //Currently just absence records, as those are the only ones used. 'accumulated_user_date_total_data' => $accumulated_user_date_total_data, 'pay_period_accumulated_user_date_total_data' => $pay_period_accumulated_user_date_total_data, 'meal_and_break_total_data' => $meal_and_break_total_data, 'exception_data' => $exception_data, 'request_data' => $request_data, 'timesheet_verify_data' => $timesheet_verify_data, ]; //Debug::Arr($retarr, 'TimeSheet Data: ', __FILE__, __LINE__, __METHOD__, 10); Debug::Text( 'TimeSheet Data: User ID:' . $user_id . ' Base Date: ' . $base_date . ' in: ' . ( microtime( true ) - $profile_start ) . 's', __FILE__, __LINE__, __METHOD__, 10 ); return $this->returnHandler( $retarr ); } /** * Get all data for displaying the timesheet. * @param string $user_id UUID * @param int $base_date EPOCH * @return array */ function getTimeSheetTotalData( $user_id, $base_date = null ) { $retarr = []; $timesheet_data = $this->stripReturnHandler( $this->getTimeSheetData( $user_id, $base_date ) ); if ( is_array( $timesheet_data ) ) { $retarr = [ 'timesheet_dates' => $timesheet_data['timesheet_dates'], 'pay_period_data' => $timesheet_data['pay_period_data'], 'accumulated_user_date_total_data' => $timesheet_data['accumulated_user_date_total_data'], 'pay_period_accumulated_user_date_total_data' => $timesheet_data['pay_period_accumulated_user_date_total_data'], 'timesheet_verify_data' => $timesheet_data['timesheet_verify_data'], ]; } //Debug::Arr($retarr, 'TimeSheet Total Data: ', __FILE__, __LINE__, __METHOD__, 10); return $this->returnHandler( $retarr ); } private function _isUserEligibleToRecalculate( $u_obj, $ulf, $recalculate_company, $start_date, $end_date ) { //Ignore terminated employees when recalculating company. However allow all employees to be recalculated if they are selected individually. if ( ( $u_obj->getStatus() == 10 || $ulf->getRecordCount() == 1 ) //Always recalculate if just a single employee is selected. || ( $u_obj->getStatus() != 10 && ( ( $recalculate_company == true && ( $u_obj->getTerminationDate() != '' && TTDate::getMiddleDayEpoch( $u_obj->getTerminationDate() ) > TTDate::getMiddleDayEpoch( $start_date ) ) //Only recaclulate terminated employees if they were terminated within this pay period. ) || ( $recalculate_company == false && ( $u_obj->getTerminationDate() != '' && TTDate::getMiddleDayEpoch( $u_obj->getTerminationDate() ) > ( TTDate::getMiddleDayEpoch( $start_date ) - ( 86400 * 90 ) ) ) //If the user was terminated more than 3 months ago, skip recalculating. ) || ( $u_obj->getTerminationDate() == '' && TTDate::getMiddleDayEpoch( $u_obj->getUpdatedDate() ) > ( TTDate::getMiddleDayEpoch( $start_date ) - ( 86400 * 30 ) ) ) //If user is terminated and no termination date is set, only recalculate if the user record has been updated in the last 30 days. ) ) ) { return true; } return false; } /** * ReCalculate timesheet/policies * @param string|string[] $pay_period_ids UUID * @param string $user_ids UUID * @return array|bool */ function reCalculateTimeSheet( $pay_period_ids, $user_ids = null ) { //Debug::text('Recalculating Employee Timesheet: User ID: '. $user_ids .' Pay Period ID: '. $pay_period_ids, __FILE__, __LINE__, __METHOD__, 10); //Debug::setVerbosity(11); if ( !$this->getPermissionObject()->Check( 'punch', 'enabled' ) || !( $this->getPermissionObject()->Check( 'punch', 'edit' ) || $this->getPermissionObject()->Check( 'punch', 'edit_child' ) ) ) { return $this->getPermissionObject()->PermissionDenied(); } if ( Misc::isSystemLoadValid() == false ) { //Check system load before anything starts. Debug::Text( 'ERROR: System load exceeded, preventing new recalculation processes from starting...', __FILE__, __LINE__, __METHOD__, 10 ); return $this->returnHandler( false ); } //Use report maximum execution time here because larger customers may need more time to recalculate many employees, similar to a report. global $config_vars; if ( isset( $config_vars['other']['report_maximum_execution_limit'] ) && $config_vars['other']['report_maximum_execution_limit'] != '' ) { $maximum_execution_time = $config_vars['other']['report_maximum_execution_limit']; Debug::Text( 'Setting maximum execution time: ' . $maximum_execution_time, __FILE__, __LINE__, __METHOD__, 10 ); ini_set( 'max_execution_time', $maximum_execution_time ); } //Make sure pay period is not CLOSED. //We can re-calc on locked though. $pplf = TTnew( 'PayPeriodListFactory' ); /** @var PayPeriodListFactory $pplf */ $pplf->getByIdList( $pay_period_ids, null, [ 'start_date' => 'asc' ] ); if ( $pplf->getRecordCount() > 0 ) { foreach ( $pplf as $pp_obj ) { Debug::Text( 'Recalculating Pay Period: ' . $pp_obj->getId() . ' Start Date: ' . TTDate::getDate( 'DATE', $pp_obj->getStartDate() ), __FILE__, __LINE__, __METHOD__, 10 ); if ( $pp_obj->getStatus() != 20 ) { $recalculate_company = false; $ulf = TTnew( 'UserListFactory' ); /** @var UserListFactory $ulf */ if ( is_array( $user_ids ) && count( $user_ids ) > 0 && isset( $user_ids[0] ) && TTUUID::isUUID( $user_ids[0] ) && $user_ids[0] != TTUUID::getZeroID() && $user_ids[0] != TTUUID::getNotExistID() ) { $ulf->getByIdAndCompanyId( $user_ids, $this->getCurrentCompanyObject()->getId() ); } else if ( $this->getPermissionObject()->Check( 'punch', 'edit' ) == true ) { //Make sure they have the permissions to recalculate all employees. TTLog::addEntry( $this->getCurrentCompanyObject()->getId(), 500, TTi18n::gettext( 'Recalculating Company TimeSheet' ), $this->getCurrentUserObject()->getId(), 'user_date_total' ); $ulf->getByCompanyId( $this->getCurrentCompanyObject()->getId() ); $recalculate_company = true; } else { return $this->getPermissionObject()->PermissionDenied(); } if ( $ulf->getRecordCount() > 0 ) { $start_date = $pp_obj->getStartDate(); $end_date = $pp_obj->getEndDate(); Debug::text( 'Found users to re-calculate: ' . $ulf->getRecordCount() . ' Start: ' . TTDate::getDate( 'DATE', $start_date ) . ' End: ' . TTDate::getDate( 'DATE', $end_date ), __FILE__, __LINE__, __METHOD__, 10 ); if ( ( !isset( $config_vars['other']['enable_job_queue'] ) || $config_vars['other']['enable_job_queue'] == true ) && ( $pplf->getRecordCount() * $ulf->getRecordCount() ) > 200 ) { $this->getProgressBarObject()->start( $this->getAPIMessageID(), $ulf->getRecordCount(), null, TTi18n::getText( 'ReCalculating Pay Period Ending' ) . ': ' . TTDate::getDate( 'DATE', $pp_obj->getEndDate() ) ); $system_job_queue_batch_id = TTUUID::generateUUID(); $x = 1; foreach ( $ulf as $u_obj ) { //Ignore terminated employees when recalculating company. However allow all employees to be recalculated if they are selected individually. if ( $this->_isUserEligibleToRecalculate( $u_obj, $ulf, $recalculate_company, $start_date, $end_date ) == true ) { TTLog::addEntry( $u_obj->getId(), 500, TTi18n::gettext( 'Recalculating Employee TimeSheet' ) . ': ' . $u_obj->getFullName() . ' ' . TTi18n::gettext( 'From' ) . ': ' . TTDate::getDate( 'DATE', $start_date ) . ' ' . TTi18n::gettext( 'To' ) . ': ' . TTDate::getDate( 'DATE', $end_date ), $this->getCurrentUserObject()->getId(), 'user_date_total' ); SystemJobQueue::Add( TTi18n::getText( 'ReCalculating Quick Exceptions' ), $system_job_queue_batch_id, 'CalculatePolicy', 'reCalculateForJobQueue', [ $u_obj->getID(), 'APITimeSheet', $start_date, $end_date ], 30 ); $x++; //Only count valid users that we are recalculating. } } //Restart the progress bar with the true number of users to recalculate. $this->getProgressBarObject()->start( $this->getAPIMessageID(), $x, null, TTi18n::getText( 'ReCalculating Pay Period Ending' ) . ': ' . TTDate::getDate( 'DATE', $pp_obj->getEndDate() ) ); SystemJobQueue::waitUntilBatchCompleted( $system_job_queue_batch_id, $this->getAPIMessageID(), 5, 7200 ); //Run up to 2hrs. } else { $this->getProgressBarObject()->start( $this->getAPIMessageID(), $ulf->getRecordCount(), null, TTi18n::getText( 'ReCalculating Pay Period Ending' ) . ': ' . TTDate::getDate( 'DATE', $pp_obj->getEndDate() ) ); $x = 1; foreach ( $ulf as $u_obj ) { if ( Misc::isSystemLoadValid() == false ) { //Check system load as the user could ask to calculate decades worth at a time. Debug::Text( 'ERROR: System load exceeded, stopping recalculation...', __FILE__, __LINE__, __METHOD__, 10 ); break; } //Ignore terminated employees when recalculating company. However allow all employees to be recalculated if they are selected individually. if ( $this->_isUserEligibleToRecalculate( $u_obj, $ulf, $recalculate_company, $start_date, $end_date ) == true ) { TTLog::addEntry( $u_obj->getId(), 500, TTi18n::gettext( 'Recalculating Employee TimeSheet' ) . ': ' . $u_obj->getFullName() . ' ' . TTi18n::gettext( 'From' ) . ': ' . TTDate::getDate( 'DATE', $start_date ) . ' ' . TTi18n::gettext( 'To' ) . ': ' . TTDate::getDate( 'DATE', $end_date ), $this->getCurrentUserObject()->getId(), 'user_date_total' ); $transaction_function = function () use ( $u_obj, $start_date, $end_date ) { $cp = TTNew( 'CalculatePolicy' ); /** @var CalculatePolicy $cp */ $cp->setUserObject( $u_obj ); $cp->getUserObject()->setTransactionMode( 'REPEATABLE READ' ); $cp->addPendingCalculationDate( $start_date, $end_date ); $cp->calculate(); //This sets timezone itself. $cp->Save(); $cp->getUserObject()->setTransactionMode(); //Back to default isolation level. return true; }; $u_obj->RetryTransaction( $transaction_function, 3, 3 ); //Set retry_sleep this fairly high so real-time punches have a chance to get saved between retries. } // else { // Debug::text('Skipping inactive or terminated user: '. $u_obj->getID() .' Status: '. $u_obj->getStatus() .' Termination Date: '. TTDate::getDate('DATE', $u_obj->getTerminationDate() ) .' Updated Date: '. TTDate::getDate('DATE', $u_obj->getUpdatedDate() ), __FILE__, __LINE__, __METHOD__, 10); // } $this->getProgressBarObject()->set( $this->getAPIMessageID(), $x ); $x++; } $this->getProgressBarObject()->stop( $this->getAPIMessageID() ); } } else { Debug::text( 'No Users to calculate!', __FILE__, __LINE__, __METHOD__, 10 ); } } else { Debug::text( 'Pay Period is CLOSED: ', __FILE__, __LINE__, __METHOD__, 10 ); } } } return $this->returnHandler( true ); } /** * Verify/Authorize timesheet * @param integer $user_id User ID of the timesheet that is being verified. * @param integer $pay_period_id Pay Period ID of the timesheet that is being verified. * @param bool $enable_authorize Create authorization record or not, should only be TRUE if called from the TimeSheet view and not from the TimeSheet Authorization View. * @return array|bool */ function verifyTimeSheet( $user_id, $pay_period_id, $enable_authorize = true ) { if ( $user_id != '' && $pay_period_id != '' ) { Debug::text( 'Verifying Pay Period TimeSheet ', __FILE__, __LINE__, __METHOD__, 10 ); $pptsvlf = TTnew( 'PayPeriodTimeSheetVerifyListFactory' ); /** @var PayPeriodTimeSheetVerifyListFactory $pptsvlf */ $transaction_function = function () use ( $pptsvlf, $user_id, $pay_period_id, $enable_authorize ) { $pptsvlf->setTransactionMode( 'REPEATABLE READ' ); $pptsvlf->StartTransaction(); $pptsvlf->getByPayPeriodIdAndUserId( $pay_period_id, $user_id ); if ( $pptsvlf->getRecordCount() == 0 ) { Debug::text( 'Timesheet NOT verified by employee yet.', __FILE__, __LINE__, __METHOD__, 10 ); $pptsvf = TTnew( 'PayPeriodTimeSheetVerifyFactory' ); /** @var PayPeriodTimeSheetVerifyFactory $pptsvf */ } else { Debug::text( 'Timesheet re-verified by employee, or superior...', __FILE__, __LINE__, __METHOD__, 10 ); $pptsvf = $pptsvlf->getCurrent(); } $pptsvf->setCurrentUser( $this->getCurrentUserObject()->getId() ); $pptsvf->setUser( $user_id ); $pptsvf->setPayPeriod( $pay_period_id ); $pptsvf->setEnableAuthorize( $enable_authorize ); if ( $pptsvf->isValid() ) { $pptsvf->Save( false ); $retval = $this->returnHandler( $pptsvf->getId() ); } else { $pptsvlf->FailTransaction(); $retval = $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'INVALID DATA' ), [ 'errors' => $pptsvf->Validator->getErrorsArray() ], [ 'total_records' => 1, 'valid_records' => 0 ] ); } $pptsvlf->CommitTransaction(); $pptsvlf->setTransactionMode(); return [ $retval ]; }; [ $retval ] = $pptsvlf->RetryTransaction( $transaction_function, 3, 3 ); //Set retry_sleep this fairly high so real-time punches have a chance to get saved between retries. return $retval; //This is a returnHandler() } return $this->returnHandler( false ); } } ?>