diff --git a/action.php b/action.php index 9599709..e697b3c 100644 --- a/action.php +++ b/action.php @@ -35,7 +35,29 @@ switch ($VARS['action']) { if ($database->has('punches', ['AND' => ['uid' => $_SESSION['uid'], 'out' => null]])) { returnToSender("already_in"); } - $database->insert('punches', ['uid' => $_SESSION['uid'], 'in' => date("Y-m-d H:i:s"), 'out' => null, 'notes' => '']); + + $shiftid = null; + if ($database->has('assigned_shifts', ['uid' => $_SESSION['uid']])) { + $minclockintime = strtotime("now + 5 minutes"); + $shifts = $database->select('shifts', ["[>]assigned_shifts" => ['shiftid' => 'shiftid']], ["shifts.shiftid", "start", "end", "days"], ["AND" =>['uid' => $_SESSION['uid'], 'start[<=]' => date("H:i:s", $minclockintime)]]); + foreach ($shifts as $shift) { + $curday = substr(date("D"), 0, 2); + if (strpos($shift['days'], $curday) === FALSE) { + continue; + } + if (strtotime($shift['end']) >= strtotime($shift['start'])) { + if (strtotime("now") >= strtotime($shift['end'])) { + continue; // shift is already over + } + } + $shiftid = $shift['shiftid']; + } + if (is_null($shiftid)) { + returnToSender("not_assigned_to_work"); + } + } + + $database->insert('punches', ['uid' => $_SESSION['uid'], 'in' => date("Y-m-d H:i:s"), 'out' => null, 'notes' => '', 'shiftid' => $shiftid]); returnToSender("punched_in"); case "punchout": if (!$database->has('punches', ['AND' => ['uid' => $_SESSION['uid'], 'out' => null]])) { diff --git a/database.mwb b/database.mwb index 2d9040b..06f6e70 100644 Binary files a/database.mwb and b/database.mwb differ diff --git a/database.sql b/database.sql new file mode 100644 index 0000000..ca96393 --- /dev/null +++ b/database.sql @@ -0,0 +1,93 @@ +-- MySQL Script generated by MySQL Workbench +-- Mon 20 Nov 2017 04:45:50 PM MST +-- Model: New Model Version: 1.0 +-- MySQL Workbench Forward Engineering + +SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; +SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; +SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES'; + +-- ----------------------------------------------------- +-- Schema qwikclock +-- ----------------------------------------------------- +DROP SCHEMA IF EXISTS `qwikclock` ; + +-- ----------------------------------------------------- +-- Schema qwikclock +-- ----------------------------------------------------- +CREATE SCHEMA IF NOT EXISTS `qwikclock` DEFAULT CHARACTER SET utf8 ; +USE `qwikclock` ; + +-- ----------------------------------------------------- +-- Table `qwikclock`.`shifts` +-- ----------------------------------------------------- +DROP TABLE IF EXISTS `qwikclock`.`shifts` ; + +CREATE TABLE IF NOT EXISTS `qwikclock`.`shifts` ( + `shiftid` INT NOT NULL AUTO_INCREMENT, + `shiftname` VARCHAR(255) NOT NULL, + `start` TIME NOT NULL, + `end` TIME NOT NULL, + `days` VARCHAR(14) NOT NULL DEFAULT 'MoTuWeThFr', + PRIMARY KEY (`shiftid`), + UNIQUE INDEX `shiftid_UNIQUE` (`shiftid` ASC)) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `qwikclock`.`punches` +-- ----------------------------------------------------- +DROP TABLE IF EXISTS `qwikclock`.`punches` ; + +CREATE TABLE IF NOT EXISTS `qwikclock`.`punches` ( + `punchid` INT NOT NULL AUTO_INCREMENT, + `uid` INT NOT NULL, + `in` DATETIME NOT NULL, + `out` DATETIME NULL, + `notes` TEXT(1000) NULL, + `shiftid` INT NULL, + PRIMARY KEY (`punchid`), + UNIQUE INDEX `punchid_UNIQUE` (`punchid` ASC), + INDEX `fk_punches_shifts1_idx` (`shiftid` ASC), + CONSTRAINT `fk_punches_shifts1` + FOREIGN KEY (`shiftid`) + REFERENCES `qwikclock`.`shifts` (`shiftid`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `qwikclock`.`assigned_shifts` +-- ----------------------------------------------------- +DROP TABLE IF EXISTS `qwikclock`.`assigned_shifts` ; + +CREATE TABLE IF NOT EXISTS `qwikclock`.`assigned_shifts` ( + `uid` INT NOT NULL, + `shiftid` INT NOT NULL, + PRIMARY KEY (`uid`, `shiftid`), + INDEX `fk_assigned_shifts_shifts_idx` (`shiftid` ASC), + CONSTRAINT `fk_assigned_shifts_shifts` + FOREIGN KEY (`shiftid`) + REFERENCES `qwikclock`.`shifts` (`shiftid`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `qwikclock`.`report_access_codes` +-- ----------------------------------------------------- +DROP TABLE IF EXISTS `qwikclock`.`report_access_codes` ; + +CREATE TABLE IF NOT EXISTS `qwikclock`.`report_access_codes` ( + `id` INT NOT NULL, + `code` VARCHAR(45) NULL, + `expires` DATETIME NULL, + PRIMARY KEY (`id`)) +ENGINE = InnoDB; + + +SET SQL_MODE=@OLD_SQL_MODE; +SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; +SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/lang/en_us.php b/lang/en_us.php index 47a583a..51cef41 100644 --- a/lang/en_us.php +++ b/lang/en_us.php @@ -85,9 +85,17 @@ define("STRINGS", [ "csv file" => "CSV text file", "ods file" => "ODS spreadsheet", "html file" => "HTML web page", - "report filtered to" => "Report filtered to {name} ({username})", + "report filtered to user" => "Report filtered to {name} ({username})", + "report filtered to start date" => "Only showing entries later than {date}", + "report filtered to end date" => "Only showing entries earlier than {date}", "all users" => "All users", "one user" => "One user", "choose user" => "Type to choose user", - "filter" => "Filter" + "filter" => "Filter", + "date range" => "Date range", + "punchid" => "Punch ID", + "shiftid" => "Shift ID", + "shiftname" => "Shift Name", + "punches" => "Punches", + "not assigned to work now" => "You are not assigned to work right now." ]); \ No newline at end of file diff --git a/lang/messages.php b/lang/messages.php index b051d5d..a65340d 100644 --- a/lang/messages.php +++ b/lang/messages.php @@ -60,5 +60,9 @@ define("MESSAGES", [ "shift_assigned" => [ "string" => "shift assigned", "type" => "success" + ], + "not_assigned_to_work" => [ + "string" => "not assigned to work now", + "type" => "danger" ] ]); diff --git a/lib/reports.php b/lib/reports.php index d70e2b8..f5f3a21 100644 --- a/lib/reports.php +++ b/lib/reports.php @@ -40,7 +40,7 @@ if (LOADED) { $user = getUserByUsername($VARS['user']); } if (isset($VARS['type']) && isset($VARS['format'])) { - generateReport($VARS['type'], $VARS['format'], $user); + generateReport($VARS['type'], $VARS['format'], $user, $VARS['startdate'], $VARS['enddate']); die(); } else { lang("invalid parameters"); @@ -90,33 +90,93 @@ function getShiftReport($user = null) { return $out; } -function getReportData($type, $user = null) { +function getPunchReport($user = null, $start = null, $end = null) { + global $database; + $where = []; + if ((bool) strtotime($start) == TRUE) { + $where["OR #start"] = [ + "in[>=]" => date("Y-m-d", strtotime($start)), + "out[>=]" => date("Y-m-d", strtotime($start)) + ]; + } + if ((bool) strtotime($end) == TRUE) { + // Make the date be the end of the day, not the start + $where["in[<=]"] = date("Y-m-d", strtotime($end)) . " 23:59:59"; + } + if ($user != null && array_key_exists('uid', $user)) { + $where["uid"] = $user['uid']; + } + if (count($where) > 1) { + $where = ["AND" => $where]; + } + $punches = $database->select( + "punches", [ + "[>]shifts" => ["shiftid" => "shiftid"] + ], [ + "punchid", "uid", "in", "out", "notes", "punches.shiftid", "shiftname" + ], $where + ); + $header = [lang("punchid", false), lang("name", false), lang("in", false), lang("out", false), lang("shiftid", false), lang("shiftname", false), lang("notes", false)]; + $out = [$header]; + $usercache = []; + for ($i = 0; $i < count($punches); $i++) { + if (!array_key_exists($punches[$i]["uid"], $usercache)) { + $usercache[$punches[$i]["uid"]] = getUserByID($punches[$i]["uid"]); + } + $out[] = [ + $punches[$i]["punchid"], + $usercache[$punches[$i]["uid"]]["name"] . " (" . $usercache[$punches[$i]["uid"]]["username"] . ")", + date(DATETIME_FORMAT, strtotime($punches[$i]['in'])), + (is_null($punches[$i]['out']) ? "" : date(DATETIME_FORMAT, strtotime($punches[$i]['out']))), + $punches[$i]['shiftid'], + $punches[$i]['shiftname'], + $punches[$i]['notes'] + ]; + } + return $out; +} + +function getReportData($type, $user = null, $start = null, $end = null) { switch ($type) { case "shifts": return getShiftReport($user); break; + case "punches": + return getPunchReport($user, $start, $end); + break; default: return [["error"]]; } } -function dataToCSV($data, $name = "report", $user = null) { +function dataToCSV($data, $name = "report", $user = null, $start = null, $end = null) { $csv = Writer::createFromString(''); $usernotice = ""; $usertitle = ""; + $datetitle = ""; if ($user != null && array_key_exists('username', $user) && array_key_exists('name', $user)) { - $usernotice = lang2("report filtered to", ["name" => $user['name'], "username" => $user['username']], false); + $usernotice = lang2("report filtered to user", ["name" => $user['name'], "username" => $user['username']], false); $usertitle = "_" . $user['username']; $csv->insertOne([$usernotice]); } + if ($start != null && (bool) strtotime($start)) { + $datenotice = lang2("report filtered to start date", ["date" => date(DATE_FORMAT, strtotime($start))], false); + $datetitle = "_" . date(DATE_FORMAT, strtotime($start)); + $csv->insertOne([$datenotice]); + } + if ($end != null && (bool) strtotime($end)) { + $datenotice = lang2("report filtered to end date", ["date" => date(DATE_FORMAT, strtotime($end))], false); + $datetitle .= ($datetitle == "" ? "_" : "-") . date(DATE_FORMAT, strtotime($end)); + $csv->insertOne([$datenotice]); + } $csv->insertAll($data); header('Content-type: text/csv'); - header('Content-Disposition: attachment; filename="' . $name . $usertitle . "_" . date("Y-m-d_Hi") . ".csv" . '"'); + header('Content-Disposition: attachment; filename="' . $name . $usertitle . $datetitle . "_" . date("Y-m-d_Hi") . ".csv" . '"'); echo $csv; die(); } -function dataToODS($data, $name = "report", $user = null) { +function dataToODS($data, $name = "report", $user = null, $start = null, $end = null) { $ods = new ods(); $styleColumn = new odsStyleTableColumn(); $styleColumn->setUseOptimalColumnWidth(true); @@ -130,13 +190,28 @@ function dataToODS($data, $name = "report", $user = null) { $usernotice = ""; $usertitle = ""; + $datetitle = ""; if ($user != null && array_key_exists('username', $user) && array_key_exists('name', $user)) { - $usernotice = lang2("report filtered to", ["name" => $user['name'], "username" => $user['username']], false); + $usernotice = lang2("report filtered to user", ["name" => $user['name'], "username" => $user['username']], false); $usertitle = "_" . $user['username']; $row = new odsTableRow(); $row->addCell(new odsTableCellString($usernotice)); $table->addRow($row); } + if ($start != null && (bool) strtotime($start)) { + $datenotice = lang2("report filtered to start date", ["date" => date(DATE_FORMAT, strtotime($start))], false); + $datetitle = "_" . date(DATE_FORMAT, strtotime($start)); + $row = new odsTableRow(); + $row->addCell(new odsTableCellString($datenotice)); + $table->addRow($row); + } + if ($end != null && (bool) strtotime($end)) { + $datenotice = lang2("report filtered to end date", ["date" => date(DATE_FORMAT, strtotime($end))], false); + $datetitle .= ($datetitle == "" ? "_" : "-") . date(DATE_FORMAT, strtotime($end)); + $row = new odsTableRow(); + $row->addCell(new odsTableCellString($datenotice)); + $table->addRow($row); + } $rowid = 0; foreach ($data as $datarow) { @@ -152,10 +227,10 @@ function dataToODS($data, $name = "report", $user = null) { $rowid++; } $ods->addTable($table); - $ods->downloadOdsFile($name . $usertitle . "_" . date("Y-m-d_Hi") . ".ods"); + $ods->downloadOdsFile($name . $usertitle . $datetitle . "_" . date("Y-m-d_Hi") . ".ods"); } -function dataToHTML($data, $name = "report", $user = null) { +function dataToHTML($data, $name = "report", $user = null, $start = null, $end = null) { global $SECURE_NONCE; // HTML exporter doesn't like null values for ($i = 0; $i < count($data); $i++) { @@ -167,16 +242,26 @@ function dataToHTML($data, $name = "report", $user = null) { } $usernotice = ""; $usertitle = ""; + $datenotice = ""; + $datetitle = ""; if ($user != null && array_key_exists('username', $user) && array_key_exists('name', $user)) { - $usernotice = "" . lang2("report filtered to", ["name" => $user['name'], "username" => $user['username']], false) . "
"; + $usernotice = "" . lang2("report filtered to user", ["name" => $user['name'], "username" => $user['username']], false) . "
"; $usertitle = "_" . $user['username']; } + if ($start != null && (bool) strtotime($start)) { + $datenotice = "" . lang2("report filtered to start date", ["date" => date(DATE_FORMAT, strtotime($start))], false) . "
"; + $datetitle = "_" . date(DATE_FORMAT, strtotime($start)); + } + if ($end != null && (bool) strtotime($end)) { + $datenotice .= "" . lang2("report filtered to end date", ["date" => date(DATE_FORMAT, strtotime($end))], false) . "
"; + $datetitle .= ($datetitle == "" ? "_" : "-") . date(DATE_FORMAT, strtotime($end)); + } header('Content-type: text/html'); $converter = new HTMLConverter(); $out = "\n" . "\n" . "\n" - . "" . $name . $usertitle . "_" . date("Y-m-d_Hi") . "\n" + . "" . $name . $usertitle . $datetitle . "_" . date("Y-m-d_Hi") . "\n" . << STYLE - . $usernotice + . $usernotice . $datenotice . $converter->convert($data); echo $out; } -function generateReport($type, $format, $user = null) { - $data = getReportData($type, $user); +function generateReport($type, $format, $user = null, $start = null, $end = null) { + $data = getReportData($type, $user, $start, $end); switch ($format) { case "ods": - dataToODS($data, $type, $user); + dataToODS($data, $type, $user, $start, $end); break; case "html": - dataToHTML($data, $type, $user); + dataToHTML($data, $type, $user, $start, $end); break; case "csv": default: - echo dataToCSV($data, $type, $user); + echo dataToCSV($data, $type, $user, $start, $end); break; } } diff --git a/pages.php b/pages.php index 7e1dca4..aceb669 100644 --- a/pages.php +++ b/pages.php @@ -56,9 +56,12 @@ define("PAGES", [ "navbar" => true, "icon" => "download", "styles" => [ + "static/css/bootstrap-datetimepicker.min.css", "static/css/easy-autocomplete.min.css" ], "scripts" => [ + "static/js/moment.min.js", + "static/js/bootstrap-datetimepicker.min.js", "static/js/jquery.easy-autocomplete.min.js", "static/js/export.js" ] diff --git a/pages/export.php b/pages/export.php index 88c4665..f9fd44c 100644 --- a/pages/export.php +++ b/pages/export.php @@ -5,41 +5,71 @@ redirectifnotloggedin(); if (!account_has_permission($_SESSION['username'], "QWIKCLOCK_MANAGE")) { ?> -
+
-
- - -
-
- -
- -
-
- +
+
+
+
+
+

+
+
+ +
+
+
+
+
+
+

+
+
+ +
+
+
-
- - +
+
+
+

+
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+

diff --git a/settings.template.php b/settings.template.php index d103445..84f7371 100644 --- a/settings.template.php +++ b/settings.template.php @@ -43,6 +43,9 @@ define("TIME_FORMAT", "g:i A"); // 12 hour time define("DATETIME_FORMAT", "M j Y g:i:s A"); // 12 hour time #define("DATETIME_FORMAT", "M j Y G:i:s"); // 24 hour time +// Used for reports +define("DATE_FORMAT", "M j, Y"); + // Used on the clock widget define("LONG_DATE_FORMAT", "l F j"); diff --git a/static/js/export.js b/static/js/export.js index 07d3329..2055041 100644 --- a/static/js/export.js +++ b/static/js/export.js @@ -28,4 +28,15 @@ var options = { } }; -$("#user-box").easyAutocomplete(options); \ No newline at end of file +$("#user-box").easyAutocomplete(options); + +$(function () { + $('#startdate').datetimepicker({ + format: "MMM D YYYY", + useCurrent: false + }); + $('#enddate').datetimepicker({ + format: "MMM D YYYY"/*"YYYY-M-DTH:m"*/, + useCurrent: true + }); +}); \ No newline at end of file