diff --git a/application/modules/default/views/scripts/menu.phtml b/application/modules/default/views/scripts/menu.phtml index c0e6e44644d421bf3a1879e7e4d4bb00d9eea197..0ac8a6822d169bf832ef4c3ae7c65c924aedfa57 100644 --- a/application/modules/default/views/scripts/menu.phtml +++ b/application/modules/default/views/scripts/menu.phtml @@ -8,13 +8,25 @@ </li> <?php } else { ?> <li> - User: <?php echo Zend_Auth::getInstance()->getIdentity(); ?><a></a> + User: <?php echo Zend_Auth::getInstance()->getIdentity(); ?> <ul> <li><a href="<?php echo $this->url(array('module' => 'auth', 'action' => 'logout'), null, true); ?>">Logout</a></li> - <li><a href="<?php echo $this->url(array('module' => 'timeclock', 'controller' => 'reader-manager'), null, true); ?>">Reader Manager</a></li> - <li><a href="<?php echo $this->url(array('module' => 'timeclock', 'controller' => 'event-manager'), null, true); ?>">Event Manager</a></li> - <li><a href="<?php echo $this->url(array('module' => 'timeclock', 'controller' => 'reports'), null, true); ?>">Reports</a></li> - + </ul> + </li> + <li> + Manage + <ul> + <li><a href="<?php echo $this->url(array('module' => 'timeclock', 'controller' => 'reader-manager'), null, true); ?>">Readers</a></li> + <li><a href="<?php echo $this->url(array('module' => 'timeclock', 'controller' => 'event-manager'), null, true); ?>">Events</a></li> + </ul> + </li> + <li> + Reports + <ul> + <li><a href="<?php echo $this->url(array('module' => 'timeclock', 'controller' => 'reports', 'action' => 'demographics'), null, true); ?>">Demographics</a></li> + <li><a href="<?php echo $this->url(array('module' => 'timeclock', 'controller' => 'reports', 'action' => 'monthly'), null, true); ?>">Monthly</a></li> + <li><a href="<?php echo $this->url(array('module' => 'timeclock', 'controller' => 'reports', 'action' => 'weekly'), null, true); ?>">Weekly</a></li> + <li><a href="<?php echo $this->url(array('module' => 'timeclock', 'controller' => 'reports', 'action' => 'daily'), null, true); ?>">Daily</a></li> </ul> </li> <?php } ?> diff --git a/application/modules/timeclock/controllers/ReportsController.php b/application/modules/timeclock/controllers/ReportsController.php index 175b232b90e5f2701e6f86c9acfabd05899ba544..eee624162d11a56351aeb84c5a0ea5db0ec169cd 100644 --- a/application/modules/timeclock/controllers/ReportsController.php +++ b/application/modules/timeclock/controllers/ReportsController.php @@ -22,6 +22,11 @@ class Timeclock_ReportsController extends Unl_Controller_Action 'ug_cum_gpa' => 'GPA'); public function indexAction() + { + $this->_redirect('/timeclock/reports/demographics'); + } + + public function demographicsAction() { $userTable = new Auth_Model_Users(); $events = $userTable->fetchCurrentUser()->findDependentRowset('Timeclock_Model_Events'); @@ -64,4 +69,141 @@ class Timeclock_ReportsController extends Unl_Controller_Action exit; } + + public function monthlyAction() + { + $userTable = new Auth_Model_Users(); + $events = $userTable->fetchCurrentUser()->findDependentRowset('Timeclock_Model_Events'); + $eventsArray = array(); + foreach ($events as $event) { + $eventsArray[$event->eventId] = $event->name; + } + $this->view->events = $eventsArray; + } + + public function monthlyPostAction() + { + $in = $this->_getAllParams(); + $eventId = $in['eventId']; + $date = strtotime($in['date']); + + $startTime = new Zend_Date($date); + $startTime->setDay(1); + $startTime->setHour(0); + $startTime->setMinute(0); + $startTime->setSecond(0); + $endTime = clone $startTime; + $endTime->addMonth(1); + $endTime->subSecond(1); + + $this->_redirect('/timeclock/reports/view/eventId/' . $eventId . '/startTime/' . $startTime->getTimestamp() . '/endTime/' . $endTime->getTimestamp()); + } + + public function weeklyAction() + { + $userTable = new Auth_Model_Users(); + $events = $userTable->fetchCurrentUser()->findDependentRowset('Timeclock_Model_Events'); + $eventsArray = array(); + foreach ($events as $event) { + $eventsArray[$event->eventId] = $event->name; + } + $this->view->events = $eventsArray; + } + + public function weeklyPostAction() + { + $in = $this->_getAllParams(); + $eventId = $in['eventId']; + $date = strtotime($in['date']); + + $startTime = new Zend_Date($date); + $startTime->setWeekday('Sunday'); + $startTime->setHour(0); + $startTime->setMinute(0); + $startTime->setSecond(0); + $endTime = clone $startTime; + $endTime->addWeek(1); + $endTime->subSecond(1); + + $this->_redirect('/timeclock/reports/view/eventId/' . $eventId . '/startTime/' . $startTime->getTimestamp() . '/endTime/' . $endTime->getTimestamp()); + } + + public function dailyAction() + { + $userTable = new Auth_Model_Users(); + $events = $userTable->fetchCurrentUser()->findDependentRowset('Timeclock_Model_Events'); + $eventsArray = array(); + foreach ($events as $event) { + $eventsArray[$event->eventId] = $event->name; + } + $this->view->events = $eventsArray; + } + + public function dailyPostAction() + { + $in = $this->_getAllParams(); + $eventId = $in['eventId']; + $date = strtotime($in['date']); + + $startTime = new Zend_Date($date); + $startTime->setHour(0); + $startTime->setMinute(0); + $startTime->setSecond(0); + $endTime = clone $startTime; + $endTime->addDay(1); + $endTime->subSecond(1); + + $this->_redirect('/timeclock/reports/view/eventId/' . $eventId . '/startTime/' . $startTime->getTimestamp() . '/endTime/' . $endTime->getTimestamp()); + } + + public function viewAction() + { + $in = $this->_getAllParams(); + $startTime = new Zend_Date($in['startTime']); + $endTime = new Zend_Date($in['endTime']); + $eventId = $in['eventId']; + + $userTable = new Auth_Model_Users(); + $events = $userTable->fetchCurrentUser()->findDependentRowset('Timeclock_Model_Events'); + $userEventIds = array(); + foreach ($events as $event) { + $userEventIds[] = $event->eventId; + } + if (!in_array($eventId, $userEventIds)) { + throw new Exception('You do not have access to this event!'); + } + + $attendancesTable = new Timeclock_Model_Attendances(); + $attendances = $attendancesTable->findWithEventInTimeRange($eventId, $startTime, $endTime); + + $userAttendances = array(); + $userDurations = array(); + foreach ($attendances as $attendance) { + if (!array_key_exists($attendance->nuid, $userDurations)) { + $userDurations[$attendance->nuid] = 0; + } + $userDurations[$attendance->nuid] += ($attendance->timeOut - $attendance->timeIn); + $userAttendances[$attendance->nuid][] = array('timeIn' => $attendance->timeIn, 'timeOut' => $attendance->timeOut); + } + + $eventTable = new Timeclock_Model_Events(); + $event = $eventTable->find($eventId); + $event = $event[0]; + + $peopleTable = new Timeclock_Model_People(); + $nuids = array_keys($userAttendances); + $people = $peopleTable->findWithNuids($nuids); + + $peopleArray = array(); + foreach ($people as $person) { + $peopleArray[$person->nuid] = $person; + } + + $this->view->userDurations = $userDurations; + $this->view->userAttendances = $userAttendances; + $this->view->startTime = $startTime; + $this->view->endTime = $endTime; + $this->view->eventName = $event->name; + $this->view->people = $peopleArray; + } } diff --git a/application/modules/timeclock/controllers/TakeAttendanceController.php b/application/modules/timeclock/controllers/TakeAttendanceController.php index a7439866fdba33766007d90e0577962c7eb5d0c6..fed93d5c304edb785c35894ae6eb1ce37a07e6b9 100644 --- a/application/modules/timeclock/controllers/TakeAttendanceController.php +++ b/application/modules/timeclock/controllers/TakeAttendanceController.php @@ -22,6 +22,8 @@ class Timeclock_TakeAttendanceController extends Unl_Controller_Action public function swipePostAction() { $errors = array(); + $question = NULL; + $choices = NULL; $config = $this->getInvokeArg('bootstrap')->getOptions(); $config = $config['timeclock']; @@ -30,6 +32,7 @@ class Timeclock_TakeAttendanceController extends Unl_Controller_Action $iso = $this->_getParam('iso'); $readerId = $this->_getParam('readerId'); $encryptedMacAddress = $this->_getParam('macAddress'); + $eventId = $this->_getParam('eventId'); $reader = NULL; if ($encryptedMacAddress) { @@ -83,14 +86,20 @@ class Timeclock_TakeAttendanceController extends Unl_Controller_Action $errors[] = 'User not found'; } else { $nuid = $data[0]['unluncwid'][0]; - switch (Sojourn_Attendance::recordSwipe($nuid, $reader)) + $swipeStatus = Sojourn_Attendance::recordSwipe($nuid, $reader, $eventId); + switch ($swipeStatus) { case Sojourn_Attendance::NO_EVENTS: $errors[] = 'No events found.'; break; case Sojourn_Attendance::SELECT_EVENT: - $errors[] = 'Multiple events found.'; + $question = 'Select an event.'; + $questionKey = 'eventId'; + $possibleEvents = Sojourn_Attendance::getEventsForNuidAndReader($nuid, $reader); + foreach ($possibleEvents as $possibleEvent) { + $choices[$possibleEvent->eventId] = $possibleEvent->name; + } break; } } @@ -113,8 +122,33 @@ class Timeclock_TakeAttendanceController extends Unl_Controller_Action exit; } + if ($question) { + $messageNode = $dom->createElement('message'); + $messageNode->appendChild($dom->createTextNode($question)); + $root->appendChild($messageNode); + + $choicesNode = $dom->createElement('choices'); + $choicesNode->setAttribute('key', $questionKey); + foreach ($choices as $choiceId => $choice) { + $choiceNode = $dom->createElement('choice'); + $choiceNode->setAttribute('id', $choiceId); + $choiceNode->appendChild($dom->createTextNode($choice)); + $choicesNode->appendChild($choiceNode); + } + $root->appendChild($choicesNode); + $root->setAttribute('status', 'question'); + echo $dom->saveXML(); + exit; + } + $messageNode = $dom->createElement('message'); - $messageNode->appendChild($dom->createTextNode('Greetings ' . $data[0]['cn'][0])); + if ($swipeStatus == Sojourn_Attendance::CLOCKED_IN) { + $messageNode->appendChild($dom->createTextNode('Greetings ' . $data[0]['cn'][0])); + } else if ($swipeStatus == Sojourn_Attendance::CLOCKED_OUT) { + $messageNode->appendChild($dom->createTextNode('Goodbye ' . $data[0]['cn'][0])); + } else { + $messageNode->appendChild('Something strange is afoot'); + } $root->appendChild($messageNode); $root->setAttribute('status', 'success'); diff --git a/application/modules/timeclock/models/Attendances.php b/application/modules/timeclock/models/Attendances.php index 43f34717de14eb7199bad918452643028fd4e1fb..8acef7c00cc3d2d642a03c13c7d5ac4914818875 100644 --- a/application/modules/timeclock/models/Attendances.php +++ b/application/modules/timeclock/models/Attendances.php @@ -12,6 +12,10 @@ class Timeclock_Model_Attendances extends Zend_Db_Table ) ); + const CLOCKED_IN = 1; + const CLOCKED_OUT = 2; + const ERROR = -1; + public function swipe($nuid, $eventId) { $db = $this->getAdapter(); @@ -22,15 +26,21 @@ class Timeclock_Model_Attendances extends Zend_Db_Table $where[] = 'timeOut IS NULL'; $where = implode(' AND ', $where); + $status = self::ERROR; + $rows = $this->fetchAll($where); if (count($rows) == 0) { // insert new row with timeIn + $status = self::CLOCKED_IN; + $row = $this->fetchNew(); $row->nuid = $nuid; $row->event = $eventId; $row->timeIn = time(); } else if (count($rows) == 1) { // update row with timeOut + $status = self::CLOCKED_OUT; + $row = $rows[0]; if ($row->timeIn < time() - 60*60*12) { $row->timeOut = $row->timeIn; @@ -47,6 +57,45 @@ class Timeclock_Model_Attendances extends Zend_Db_Table throw new Zend_Exception('Orphaned Attendance Record.'); } $row->save(); + + return $status; } + public function findWithEventInTimeRange($eventId, Zend_Date $startTime, Zend_Date $endTime, $includeAnonymous = FALSE) + { + if (!$includeAnonymous) { + $eventsTable = new Timeclock_Model_Events(); + $event = $eventsTable->find($eventId); + $event = $event[0]; + $attendees = $event->findDependentRowset('Timeclock_Model_EventAttendees'); + $attendeeNuids = array(); + foreach ($attendees as $attendee) { + $attendeeNuids[] = $attendee->nuid; + } + } + + $db = $this->getAdapter(); + $where = array(); + $where[] = $db->quoteInto('event = ?', $eventId); + $where[] = $db->quoteInto('timeOut >= ?', $startTime->getTimestamp()); + $where[] = $db->quoteInto('timeIn < ?', $endTime->getTimestamp()); + $where[] = 'timeOut IS NOT NULL'; + if (!$includeAnonymous) { + $where[] = $db->quoteInto('nuid IN(?)', $attendeeNuids); + } + $where = implode(' AND ', $where); + + $data = $this->fetchAll($where); + + foreach ($data as $row) { + if ($row->timeIn < $startTime->getTimestamp()) { + $row->timeIn = $startTime->getTimestamp(); + } + if ($row->timeOut > $endTime->getTimestamp()) { + $row->timeOut = $endTime->getTimestamp(); + } + } + + return $data; + } } diff --git a/application/modules/timeclock/models/People.php b/application/modules/timeclock/models/People.php index 9cb1a3751787ee1137a63b12084fc7fcc48fbeac..54fe182c0933d5e578b24e91fc461be83c87f4e6 100644 --- a/application/modules/timeclock/models/People.php +++ b/application/modules/timeclock/models/People.php @@ -5,6 +5,19 @@ class Timeclock_Model_People extends Zend_Db_Table protected $_name = 'people'; protected $_primary = 'nuid'; + public function findWithNuids($nuids) + { + $db = $this->getAdapter(); + $where = array(); + $where[] = $db->quoteInto('nuid IN(?)', $nuids); + $where = implode(' AND ', $where); + + return $this->fetchAll($where); + } + + /* + * Function to get demographic counts + */ public function fetchCountBy($eventId, $fields) { if (!is_array($fields)) { diff --git a/application/modules/timeclock/views/scripts/reports/daily.phtml b/application/modules/timeclock/views/scripts/reports/daily.phtml new file mode 100644 index 0000000000000000000000000000000000000000..bb1b6651fde88d6f6b142cd22f6232354962cffb --- /dev/null +++ b/application/modules/timeclock/views/scripts/reports/daily.phtml @@ -0,0 +1,13 @@ +<?php $this->headScript()->appendFile($this->baseUrl() . '/javascript/timeclock/reports/weekly.js'); ?> + +<form method="post" action="<?php echo $this->baseUrl(); ?>/timeclock/reports/daily.post"> + + <h4>Select Event:</h4> + <div><?php echo $this->formSelect('eventId', NULL, NULL, $this->events); ?></div> + + <h4>Select Day:</h4> + <div><?php echo $this->formText('date', NULL, array('class' => 'datepicker')); ?></div> + + <?php echo $this->formSubmit('submit', 'Submit'); ?> + +</form> \ No newline at end of file diff --git a/application/modules/timeclock/views/scripts/reports/index.phtml b/application/modules/timeclock/views/scripts/reports/demographics.phtml similarity index 96% rename from application/modules/timeclock/views/scripts/reports/index.phtml rename to application/modules/timeclock/views/scripts/reports/demographics.phtml index d28f13cf4c2d6ae16a42d56821dc8c3cec1d0e76..e91f27ec64526fa5a0283b46495c58cf71e98fd7 100644 --- a/application/modules/timeclock/views/scripts/reports/index.phtml +++ b/application/modules/timeclock/views/scripts/reports/demographics.phtml @@ -1,4 +1,4 @@ -<?php $this->headScript()->appendFile($this->baseUrl() . '/javascript/timeclock/reports.js'); ?> +<?php $this->headScript()->appendFile($this->baseUrl() . '/javascript/timeclock/reports/demographics.js'); ?> <?php $this->headLink()->appendStylesheet($this->baseUrl() . '/css/timeclock/reports.css', 'all'); ?> <form id="reportsForm"> diff --git a/application/modules/timeclock/views/scripts/reports/monthly.phtml b/application/modules/timeclock/views/scripts/reports/monthly.phtml new file mode 100644 index 0000000000000000000000000000000000000000..39e7f0049c8277838e785d0d674832ecef6d4656 --- /dev/null +++ b/application/modules/timeclock/views/scripts/reports/monthly.phtml @@ -0,0 +1,13 @@ +<?php $this->headScript()->appendFile($this->baseUrl() . '/javascript/timeclock/reports/weekly.js'); ?> + +<form method="post" action="<?php echo $this->baseUrl(); ?>/timeclock/reports/monthly.post"> + + <h4>Select Event:</h4> + <div><?php echo $this->formSelect('eventId', NULL, NULL, $this->events); ?></div> + + <h4>Select Month:</h4> + <div><?php echo $this->formText('date', NULL, array('class' => 'datepicker')); ?></div> + + <?php echo $this->formSubmit('submit', 'Submit'); ?> + +</form> \ No newline at end of file diff --git a/application/modules/timeclock/views/scripts/reports/view.phtml b/application/modules/timeclock/views/scripts/reports/view.phtml new file mode 100644 index 0000000000000000000000000000000000000000..5b1a948d9cc7087d913aa52dbd0f6d868baa65ac --- /dev/null +++ b/application/modules/timeclock/views/scripts/reports/view.phtml @@ -0,0 +1,27 @@ +<?php $this->layout()->tagline = 'Attendees of ' . $this->eventName + . ' from ' . date('Y-m-d', $this->startTime->getTimestamp()) + . ' through ' . date('Y-m-d', $this->endTime->getTimestamp()); ?> + +<table class="zentable cool"> + <tr> + <th>Name</th> + <th>Date</th> + <th>Time In</th> + <th>Time Out</th> + <th>Duration</th> + </tr> + <?php + foreach ($this->userAttendances as $nuid => $attendances) { + foreach ($attendances as $index => $attendance) { + $person = $this->people[$nuid]; + $duration = $this->userDurations[$nuid]; + ?> + <tr> + <td><?php if ($index == 0) echo $person->firstname . ' ' . $person->lastname; ?></td> + <td><?php echo date('Y-m-d', $attendance['timeIn']); ?></td> + <td><?php echo date('H:i', $attendance['timeIn']); ?></td> + <td><?php echo date('H:i', $attendance['timeOut']); ?></td> + <td><?php if($index == count($attendances) - 1) echo floor($duration / 3600) . ':' . str_pad(floor($duration / 60 % 60), 2, '0', STR_PAD_LEFT); ?></td> + </tr> + <?php } } ?> +</table> \ No newline at end of file diff --git a/application/modules/timeclock/views/scripts/reports/weekly.phtml b/application/modules/timeclock/views/scripts/reports/weekly.phtml new file mode 100644 index 0000000000000000000000000000000000000000..61da8f5351111561bb9d0180a890760390803723 --- /dev/null +++ b/application/modules/timeclock/views/scripts/reports/weekly.phtml @@ -0,0 +1,13 @@ +<?php $this->headScript()->appendFile($this->baseUrl() . '/javascript/timeclock/reports/weekly.js'); ?> + +<form method="post" action="<?php echo $this->baseUrl(); ?>/timeclock/reports/weekly.post"> + + <h4>Select Event:</h4> + <div><?php echo $this->formSelect('eventId', NULL, NULL, $this->events); ?></div> + + <h4>Select Week:</h4> + <div><?php echo $this->formText('date', NULL, array('class' => 'datepicker')); ?></div> + + <?php echo $this->formSubmit('submit', 'Submit'); ?> + +</form> \ No newline at end of file diff --git a/library/Sojourn/Attendance.php b/library/Sojourn/Attendance.php index a26d21112dae283ee3a382e4991e50426315331e..5950244d4fe91da5e13230f89bea62e858136992 100644 --- a/library/Sojourn/Attendance.php +++ b/library/Sojourn/Attendance.php @@ -2,14 +2,25 @@ class Sojourn_Attendance { - const SUCCESS = 0; + const CLOCKED_IN = 1; + const CLOCKED_OUT = 2; const SELECT_EVENT = -1; const NO_EVENTS = -2; + const ERROR = -3; - static public function recordSwipe($nuid, Zend_Db_Table_Row $reader, $eventName = NULL) + static public function recordSwipe($nuid, Zend_Db_Table_Row $reader, $eventId = NULL) { $events = self::getEventsForNuidAndReader($nuid, $reader); + if ($eventId) { + $found = FALSE; + foreach ($events as $possibleEvent) { + if ($possibleEvent->eventId == $eventId) { + $events = array($possibleEvent); + } + } + } + if (count($events) > 1) { return self::SELECT_EVENT; } @@ -21,9 +32,18 @@ class Sojourn_Attendance $event = $events[0]; $attendancesTable = new Timeclock_Model_Attendances(); - $attendancesTable->swipe($nuid, $event->eventId); + $status = $attendancesTable->swipe($nuid, $event->eventId); + + switch ($status) { + case Timeclock_Model_Attendances::CLOCKED_IN: + return self::CLOCKED_IN; + break; + + case Timeclock_Model_Attendances::CLOCKED_OUT: + return self::CLOCKED_OUT; + } - return self::SUCCESS; + return self::ERROR; } static public function getEventsForNuidAndReader($nuid, Zend_Db_Table_Row $reader) diff --git a/public/javascript/timeclock/reports.js b/public/javascript/timeclock/reports/demographics.js similarity index 100% rename from public/javascript/timeclock/reports.js rename to public/javascript/timeclock/reports/demographics.js diff --git a/public/javascript/timeclock/reports/weekly.js b/public/javascript/timeclock/reports/weekly.js new file mode 100644 index 0000000000000000000000000000000000000000..e6bfbbe0220c82c98748c1759ff510271a5031f9 --- /dev/null +++ b/public/javascript/timeclock/reports/weekly.js @@ -0,0 +1,5 @@ +var weekly = {}; + +$(document).ready(function() { + $('.datepicker').datepicker(); +}); \ No newline at end of file