PageRenderTime 46ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/api/ui/pull/productivity_report.class.php

https://github.com/patrickdemond/beartooth
PHP | 298 lines | 223 code | 29 blank | 46 comment | 33 complexity | ff1281d6c23e448881f02a781c793129 MD5 | raw file
  1. <?php
  2. /**
  3. * productivity_report.class.php
  4. *
  5. * @author Patrick Emond <emondpd@mcmaster.ca>
  6. * @filesource
  7. */
  8. namespace beartooth\ui\pull;
  9. use cenozo\lib, cenozo\log, beartooth\util;
  10. /**
  11. * Productivity report data.
  12. *
  13. * @abstract
  14. */
  15. class productivity_report extends \cenozo\ui\pull\base_report
  16. {
  17. /**
  18. * Constructor
  19. *
  20. * @author Patrick Emond <emondpd@mcmaster.ca>
  21. * @param string $subject The subject to retrieve the primary information from.
  22. * @param array $args Pull arguments.
  23. * @access public
  24. */
  25. public function __construct( $args )
  26. {
  27. parent::__construct( 'productivity', $args );
  28. }
  29. /**
  30. * Builds the report.
  31. * @author Patrick Emond <emondpd@mcmaster.ca>
  32. * @access protected
  33. */
  34. protected function build()
  35. {
  36. // determine whether or not to round time to 15 minute increments
  37. $round_times = $this->get_argument( 'round_times', true );
  38. $role_class_name = lib::get_class_name( 'database\role' );
  39. $site_class_name = lib::get_class_name( 'database\site' );
  40. $user_class_name = lib::get_class_name( 'database\user' );
  41. $activity_class_name = lib::get_class_name( 'database\activity' );
  42. $assignment_class_name = lib::get_class_name( 'database\assignment' );
  43. $db_role = $role_class_name::get_unique_record( 'name', 'interviewer' );
  44. $restrict_site_id = $this->get_argument( 'restrict_site_id', 0 );
  45. $site_mod = lib::create( 'database\modifier' );
  46. if( $restrict_site_id )
  47. $site_mod->where( 'id', '=', $restrict_site_id );
  48. $restrict_start_date = $this->get_argument( 'restrict_start_date' );
  49. $restrict_end_date = $this->get_argument( 'restrict_end_date' );
  50. $now_datetime_obj = util::get_datetime_object();
  51. $start_datetime_obj = NULL;
  52. $end_datetime_obj = NULL;
  53. if( $restrict_start_date )
  54. {
  55. $start_datetime_obj = util::get_datetime_object( $restrict_start_date );
  56. if( $start_datetime_obj > $now_datetime_obj )
  57. $start_datetime_obj = clone $now_datetime_obj;
  58. }
  59. if( $restrict_end_date )
  60. {
  61. $end_datetime_obj = util::get_datetime_object( $restrict_end_date );
  62. if( $end_datetime_obj > $now_datetime_obj )
  63. $end_datetime_obj = clone $now_datetime_obj;
  64. }
  65. if( $restrict_start_date && $restrict_end_date && $end_datetime_obj < $start_datetime_obj )
  66. {
  67. $temp_datetime_obj = clone $start_datetime_obj;
  68. $start_datetime_obj = clone $end_datetime_obj;
  69. $end_datetime_obj = clone $temp_datetime_obj;
  70. }
  71. // determine whether we are running the report for a single date or not
  72. $single_date = ( !is_null( $start_datetime_obj ) &&
  73. !is_null( $end_datetime_obj ) &&
  74. $start_datetime_obj == $end_datetime_obj ) ||
  75. ( !is_null( $start_datetime_obj ) &&
  76. $start_datetime_obj == $now_datetime_obj );
  77. if( $single_date ) $single_datetime_obj = clone $start_datetime_obj;
  78. $db_qnaire = lib::create( 'database\qnaire', $this->get_argument( 'restrict_qnaire_id' ) );
  79. $this->add_title(
  80. sprintf( 'Interviewer productivity for '.
  81. 'the %s interview', $db_qnaire->name ) ) ;
  82. // we define the min and max datetime objects here, they get set in the next foreach loop, then
  83. // used in the for loop below
  84. $min_datetime_obj = NULL;
  85. $max_datetime_obj = NULL;
  86. // now create a table for every site included in the report
  87. foreach( $site_class_name::select( $site_mod ) as $db_site )
  88. {
  89. $contents = array();
  90. // start by determining the table contents
  91. $grand_total_time = 0;
  92. $grand_total_completes = 0;
  93. $grand_total_calls = 0;
  94. foreach( $user_class_name::select() as $db_user )
  95. {
  96. // make sure the interviewer has min/max time for this date range
  97. $activity_mod = lib::create( 'database\modifier' );
  98. $activity_mod->where( 'activity.user_id', '=', $db_user->id );
  99. $activity_mod->where( 'activity.site_id', '=', $db_site->id );
  100. $activity_mod->where( 'activity.role_id', '=', $db_role->id );
  101. $activity_mod->where( 'operation.subject', '!=', 'self' );
  102. $assignment_mod = lib::create( 'database\modifier' );
  103. if( $restrict_start_date && $restrict_end_date )
  104. {
  105. $activity_mod->where( 'datetime', '>=',
  106. $start_datetime_obj->format( 'Y-m-d' ).' 0:00:00' );
  107. $activity_mod->where( 'datetime', '<=',
  108. $end_datetime_obj->format( 'Y-m-d' ).' 23:59:59' );
  109. $assignment_mod->where( 'start_datetime', '>=',
  110. $start_datetime_obj->format( 'Y-m-d' ).' 0:00:00' );
  111. $assignment_mod->where( 'end_datetime', '<=',
  112. $end_datetime_obj->format( 'Y-m-d' ).' 23:59:59' );
  113. }
  114. else if( $restrict_start_date && !$restrict_end_date )
  115. {
  116. $activity_mod->where( 'datetime', '>=',
  117. $start_datetime_obj->format( 'Y-m-d' ).' 0:00:00' );
  118. $assignment_mod->where( 'start_datetime', '>=',
  119. $start_datetime_obj->format( 'Y-m-d' ).' 0:00:00' );
  120. }
  121. else if( !$restrict_start_date && $restrict_end_date )
  122. {
  123. $activity_mod->where( 'datetime', '<=',
  124. $end_datetime_obj->format( 'Y-m-d' ).' 23:59:59' );
  125. $assignment_mod->where( 'start_datetime', '<=',
  126. $end_datetime_obj->format( 'Y-m-d' ).' 23:59:59' );
  127. }
  128. $min_activity_datetime_obj = $activity_class_name::get_min_datetime( $activity_mod );
  129. $max_activity_datetime_obj = $activity_class_name::get_max_datetime( $activity_mod );
  130. // if there is no activity then skip this user
  131. if( is_null( $min_activity_datetime_obj ) ||
  132. is_null( $max_activity_datetime_obj ) ) continue;
  133. // Determine the number of completed interviews and their average length.
  134. // This is done by looping through all of this user's assignments. Any assignment
  135. // with an interview that is completed is tested to see if that interview's last
  136. // assignment is the originating assignment.
  137. ///////////////////////////////////////////////////////////////////////////////////////////
  138. $completes = 0;
  139. $interview_time = 0;
  140. $calls = 0;
  141. foreach( $db_user->get_assignment_list( $assignment_mod ) as $db_assignment )
  142. {
  143. $db_interview = $db_assignment->get_interview();
  144. $calls += $db_assignment->get_phone_call_count();
  145. if( $db_interview->completed )
  146. {
  147. $last_assignment_mod = lib::create( 'database\modifier' );
  148. $last_assignment_mod->where( 'interview_id', '=', $db_interview->id );
  149. $last_assignment_mod->order_desc( 'start_datetime' );
  150. $last_assignment_mod->limit( 1 );
  151. $db_last_assignment = current( $assignment_class_name::select( $last_assignment_mod ) );
  152. if( $db_assignment->id == $db_last_assignment->id )
  153. {
  154. $completes++;
  155. foreach( $db_interview->get_qnaire()->get_phase_list() as $db_phase )
  156. {
  157. // only count the time in non-repeating phases
  158. if( !$db_phase->repeated )
  159. $interview_time += $db_interview->get_interview_time( $db_phase );
  160. }
  161. }
  162. }
  163. } // end loop on assignments
  164. // Determine the total working time.
  165. // This is done by finding the minimum and maximum activity time for every day included in
  166. // the report and calculating the difference between the two times.
  167. ///////////////////////////////////////////////////////////////////////////////////////////
  168. $time = 0;
  169. $total_time = 0;
  170. $min_activity_datetime_obj->setTime( 0, 0 );
  171. $max_activity_datetime_obj->setTime( 0, 0 );
  172. $interval = new \DateInterval( 'P1D' );
  173. for( $datetime_obj = clone $min_activity_datetime_obj;
  174. $datetime_obj <= $max_activity_datetime_obj;
  175. $datetime_obj->add( $interval ) )
  176. {
  177. // if reporting a single date restrict the count to that day only
  178. if( $single_date && $single_datetime_obj != $datetime_obj ) continue;
  179. // get the elapsed time and round to 15 minute increments (if necessary)
  180. $time += $activity_class_name::get_elapsed_time(
  181. $db_user, $db_site, $db_role, $datetime_obj->format( 'Y-m-d' ) );
  182. $total_time = $round_times ? floor( 4 * $time ) / 4 : $time;
  183. }
  184. // Now we can use all the information gathered above to fill in the contents of the table.
  185. ///////////////////////////////////////////////////////////////////////////////////////////
  186. if( $single_date )
  187. {
  188. $day_activity_mod = lib::create( 'database\modifier' );
  189. $day_activity_mod->where( 'activity.user_id', '=', $db_user->id );
  190. $day_activity_mod->where( 'activity.site_id', '=', $db_site->id );
  191. $day_activity_mod->where( 'activity.role_id', '=', $db_role->id );
  192. $day_activity_mod->where( 'operation.subject', '!=', 'self' );
  193. $day_activity_mod->where( 'datetime', '>=',
  194. $min_activity_datetime_obj->format( 'Y-m-d' ).' 0:00:00' );
  195. $day_activity_mod->where( 'datetime', '<=',
  196. $min_activity_datetime_obj->format( 'Y-m-d' ).' 23:59:59' );
  197. $min_datetime_obj = $activity_class_name::get_min_datetime( $day_activity_mod );
  198. $max_datetime_obj = $activity_class_name::get_max_datetime( $day_activity_mod );
  199. $contents[] = array(
  200. $db_user->name,
  201. $completes,
  202. is_null( $min_datetime_obj ) ? '??' : $min_datetime_obj->format( "H:i" ),
  203. is_null( $max_datetime_obj ) ? '??' : $max_datetime_obj->format( "H:i" ),
  204. sprintf( '%0.2f', $total_time ),
  205. $total_time > 0 ? sprintf( '%0.2f', $completes / $total_time ) : '',
  206. $completes > 0 ? sprintf( '%0.2f', $interview_time / $completes / 60 ) : '',
  207. $total_time > 0 ? sprintf( '%0.2f', $calls / $total_time ) : '' );
  208. }
  209. else
  210. {
  211. $contents[] = array(
  212. $db_user->name,
  213. $completes,
  214. sprintf( '%0.2f', $total_time ),
  215. $total_time > 0 ? sprintf( '%0.2f', $completes / $total_time ) : '',
  216. $completes > 0 ? sprintf( '%0.2f', $interview_time / $completes / 60 ) : '',
  217. $total_time > 0 ? sprintf( '%0.2f', $calls / $total_time ) : '' );
  218. }
  219. $grand_total_completes += $completes;
  220. $grand_total_time += $total_time;
  221. $grand_total_calls += $calls;
  222. }
  223. $average_callPH = $grand_total_time > 0 ?
  224. sprintf( '%0.2f', $grand_total_calls / $grand_total_time ) : 'N/A';
  225. $average_compPH = $grand_total_time > 0 ?
  226. sprintf( '%0.2f', $grand_total_completes / $grand_total_time ) : 'N/A';
  227. if( $single_date )
  228. {
  229. $header = array(
  230. "Interviewer",
  231. "Completes",
  232. "Start Time",
  233. "End Time",
  234. "Total Time",
  235. "CompPH",
  236. "Avg. Length",
  237. "CallPH" );
  238. $footer = array(
  239. "Total",
  240. "sum()",
  241. "--",
  242. "--",
  243. "sum()",
  244. $average_compPH,
  245. "average()",
  246. $average_callPH );
  247. }
  248. else
  249. {
  250. $header = array(
  251. "Interviewer",
  252. "Completes",
  253. "Total Time",
  254. "CompPH",
  255. "Avg. Length",
  256. "CallPH" );
  257. $footer = array(
  258. "Total",
  259. "sum()",
  260. "sum()",
  261. $average_compPH,
  262. "average()",
  263. $average_callPH );
  264. }
  265. $title = 0 == $restrict_site_id ? $db_site->name : NULL;
  266. $this->add_table( $title, $header, $contents, $footer );
  267. }
  268. }
  269. }