Add punches report, check if user assigned to a shift,

add report date filter, attach punches to shift in DB
master
Skylar Ittner 7 years ago
parent 4d42dd20d6
commit a02d96385c

@ -35,7 +35,29 @@ switch ($VARS['action']) {
if ($database->has('punches', ['AND' => ['uid' => $_SESSION['uid'], 'out' => null]])) { if ($database->has('punches', ['AND' => ['uid' => $_SESSION['uid'], 'out' => null]])) {
returnToSender("already_in"); 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"); returnToSender("punched_in");
case "punchout": case "punchout":
if (!$database->has('punches', ['AND' => ['uid' => $_SESSION['uid'], 'out' => null]])) { if (!$database->has('punches', ['AND' => ['uid' => $_SESSION['uid'], 'out' => null]])) {

Binary file not shown.

@ -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;

@ -85,9 +85,17 @@ define("STRINGS", [
"csv file" => "CSV text file", "csv file" => "CSV text file",
"ods file" => "ODS spreadsheet", "ods file" => "ODS spreadsheet",
"html file" => "HTML web page", "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", "all users" => "All users",
"one user" => "One user", "one user" => "One user",
"choose user" => "Type to choose 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."
]); ]);

@ -60,5 +60,9 @@ define("MESSAGES", [
"shift_assigned" => [ "shift_assigned" => [
"string" => "shift assigned", "string" => "shift assigned",
"type" => "success" "type" => "success"
],
"not_assigned_to_work" => [
"string" => "not assigned to work now",
"type" => "danger"
] ]
]); ]);

@ -40,7 +40,7 @@ if (LOADED) {
$user = getUserByUsername($VARS['user']); $user = getUserByUsername($VARS['user']);
} }
if (isset($VARS['type']) && isset($VARS['format'])) { if (isset($VARS['type']) && isset($VARS['format'])) {
generateReport($VARS['type'], $VARS['format'], $user); generateReport($VARS['type'], $VARS['format'], $user, $VARS['startdate'], $VARS['enddate']);
die(); die();
} else { } else {
lang("invalid parameters"); lang("invalid parameters");
@ -90,33 +90,93 @@ function getShiftReport($user = null) {
return $out; 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) { switch ($type) {
case "shifts": case "shifts":
return getShiftReport($user); return getShiftReport($user);
break; break;
case "punches":
return getPunchReport($user, $start, $end);
break;
default: default:
return [["error"]]; return [["error"]];
} }
} }
function dataToCSV($data, $name = "report", $user = null) { function dataToCSV($data, $name = "report", $user = null, $start = null, $end = null) {
$csv = Writer::createFromString(''); $csv = Writer::createFromString('');
$usernotice = ""; $usernotice = "";
$usertitle = ""; $usertitle = "";
$datetitle = "";
if ($user != null && array_key_exists('username', $user) && array_key_exists('name', $user)) { 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']; $usertitle = "_" . $user['username'];
$csv->insertOne([$usernotice]); $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); $csv->insertAll($data);
header('Content-type: text/csv'); 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; echo $csv;
die(); die();
} }
function dataToODS($data, $name = "report", $user = null) { function dataToODS($data, $name = "report", $user = null, $start = null, $end = null) {
$ods = new ods(); $ods = new ods();
$styleColumn = new odsStyleTableColumn(); $styleColumn = new odsStyleTableColumn();
$styleColumn->setUseOptimalColumnWidth(true); $styleColumn->setUseOptimalColumnWidth(true);
@ -130,13 +190,28 @@ function dataToODS($data, $name = "report", $user = null) {
$usernotice = ""; $usernotice = "";
$usertitle = ""; $usertitle = "";
$datetitle = "";
if ($user != null && array_key_exists('username', $user) && array_key_exists('name', $user)) { 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']; $usertitle = "_" . $user['username'];
$row = new odsTableRow(); $row = new odsTableRow();
$row->addCell(new odsTableCellString($usernotice)); $row->addCell(new odsTableCellString($usernotice));
$table->addRow($row); $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; $rowid = 0;
foreach ($data as $datarow) { foreach ($data as $datarow) {
@ -152,10 +227,10 @@ function dataToODS($data, $name = "report", $user = null) {
$rowid++; $rowid++;
} }
$ods->addTable($table); $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; global $SECURE_NONCE;
// HTML exporter doesn't like null values // HTML exporter doesn't like null values
for ($i = 0; $i < count($data); $i++) { for ($i = 0; $i < count($data); $i++) {
@ -167,16 +242,26 @@ function dataToHTML($data, $name = "report", $user = null) {
} }
$usernotice = ""; $usernotice = "";
$usertitle = ""; $usertitle = "";
$datenotice = "";
$datetitle = "";
if ($user != null && array_key_exists('username', $user) && array_key_exists('name', $user)) { if ($user != null && array_key_exists('username', $user) && array_key_exists('name', $user)) {
$usernotice = "<span>" . lang2("report filtered to", ["name" => $user['name'], "username" => $user['username']], false) . "</span><br />"; $usernotice = "<span>" . lang2("report filtered to user", ["name" => $user['name'], "username" => $user['username']], false) . "</span><br />";
$usertitle = "_" . $user['username']; $usertitle = "_" . $user['username'];
} }
if ($start != null && (bool) strtotime($start)) {
$datenotice = "<span>" . lang2("report filtered to start date", ["date" => date(DATE_FORMAT, strtotime($start))], false) . "</span><br />";
$datetitle = "_" . date(DATE_FORMAT, strtotime($start));
}
if ($end != null && (bool) strtotime($end)) {
$datenotice .= "<span>" . lang2("report filtered to end date", ["date" => date(DATE_FORMAT, strtotime($end))], false) . "</span><br />";
$datetitle .= ($datetitle == "" ? "_" : "-") . date(DATE_FORMAT, strtotime($end));
}
header('Content-type: text/html'); header('Content-type: text/html');
$converter = new HTMLConverter(); $converter = new HTMLConverter();
$out = "<!DOCTYPE html>\n" $out = "<!DOCTYPE html>\n"
. "<meta charset=\"utf-8\">\n" . "<meta charset=\"utf-8\">\n"
. "<meta name=\"viewport\" content=\"width=device-width\">\n" . "<meta name=\"viewport\" content=\"width=device-width\">\n"
. "<title>" . $name . $usertitle . "_" . date("Y-m-d_Hi") . "</title>\n" . "<title>" . $name . $usertitle . $datetitle . "_" . date("Y-m-d_Hi") . "</title>\n"
. <<<STYLE . <<<STYLE
<style nonce="$SECURE_NONCE"> <style nonce="$SECURE_NONCE">
.table-csv-data { .table-csv-data {
@ -190,23 +275,23 @@ function dataToHTML($data, $name = "report", $user = null) {
} }
</style> </style>
STYLE STYLE
. $usernotice . $usernotice . $datenotice
. $converter->convert($data); . $converter->convert($data);
echo $out; echo $out;
} }
function generateReport($type, $format, $user = null) { function generateReport($type, $format, $user = null, $start = null, $end = null) {
$data = getReportData($type, $user); $data = getReportData($type, $user, $start, $end);
switch ($format) { switch ($format) {
case "ods": case "ods":
dataToODS($data, $type, $user); dataToODS($data, $type, $user, $start, $end);
break; break;
case "html": case "html":
dataToHTML($data, $type, $user); dataToHTML($data, $type, $user, $start, $end);
break; break;
case "csv": case "csv":
default: default:
echo dataToCSV($data, $type, $user); echo dataToCSV($data, $type, $user, $start, $end);
break; break;
} }
} }

@ -56,9 +56,12 @@ define("PAGES", [
"navbar" => true, "navbar" => true,
"icon" => "download", "icon" => "download",
"styles" => [ "styles" => [
"static/css/bootstrap-datetimepicker.min.css",
"static/css/easy-autocomplete.min.css" "static/css/easy-autocomplete.min.css"
], ],
"scripts" => [ "scripts" => [
"static/js/moment.min.js",
"static/js/bootstrap-datetimepicker.min.js",
"static/js/jquery.easy-autocomplete.min.js", "static/js/jquery.easy-autocomplete.min.js",
"static/js/export.js" "static/js/export.js"
] ]

@ -5,41 +5,71 @@ redirectifnotloggedin();
if (!account_has_permission($_SESSION['username'], "QWIKCLOCK_MANAGE")) { if (!account_has_permission($_SESSION['username'], "QWIKCLOCK_MANAGE")) {
?> ?>
<div class="alert alert-danger"><?php lang("missing permission") ?></div> <div class="alert alert-danger"><?php lang("missing permission") ?></div>
<?php <?php
} else { } else {
?> ?>
<form action="lib/reports.php" method="GET" target="_BLANK"> <form action="lib/reports.php" method="GET" target="_BLANK">
<div class="row"> <div class="row">
<div class="col-xs-12 col-sm-6 col-md-4"> <div class="col-xs-12 col-sm-6">
<label for="type"><?php lang("report type"); ?></label> <div class="row">
<select name="type" class="form-control" required> <div class="col-xs-12 col-sm-12 col-md-6">
<option value="shifts"><?php lang("shifts") ?></option> <div class="panel panel-default">
</select> <div class="panel-heading">
</div> <h3 class="panel-title"><label for="type"><i class="fa fa-list"></i> <?php lang("report type"); ?></label></h3>
<div class="col-xs-12 col-sm-6 col-md-4"> </div>
<label for="users"><?php lang("filter"); ?></label> <div class="panel-body">
<div class="radio"> <select name="type" class="form-control" required>
<label> <option value="shifts"><?php lang("shifts") ?></option>
<input name="users" value="all" checked="" type="radio"> <i class="fa fa-users fa-fw"></i> <option value="punches"><?php lang("punches") ?></option>
<?php lang("all users") ?> </select>
</label> </div>
</div> </div>
<div class="radio"> </div>
<label> <div class="col-xs-12 col-sm-12 col-md-6">
<input name="users" value="one" type="radio"> <i class="fa fa-user fa-fw"></i> <div class="panel panel-default">
<?php lang("one user") ?> <div class="panel-heading">
<input type="text" name="user" class="form-control" id="user-box" placeholder="<?php lang("choose user") ?>" /> <h3 class="panel-title"><label for="format"><i class="fa fa-file"></i> <?php lang("format"); ?></label></h3>
</label> </div>
<div class="panel-body">
<select name="format" class="form-control" required>
<option value="csv"><?php lang("csv file") ?></option>
<option value="ods"><?php lang("ods file") ?></option>
<option value="html"><?php lang("html file") ?></option>
</select>
</div>
</div>
</div>
</div> </div>
</div> </div>
<div class="col-xs-12 col-sm-6 col-md-4"> <div class="col-xs-12 col-sm-6">
<label for="type"><?php lang("format"); ?></label> <div class="panel panel-default">
<select name="format" class="form-control" required> <div class="panel-heading">
<option value="csv"><?php lang("csv file") ?></option> <h3 class="panel-title"><label><i class="fa fa-filter"></i> <?php lang("filter"); ?></label></h3>
<option value="ods"><?php lang("ods file") ?></option> </div>
<option value="html"><?php lang("html file") ?></option> <div class="panel-body">
</select> <div class="radio">
<label>
<input name="users" value="all" checked="" type="radio"> <i class="fa fa-users fa-fw"></i>
<?php lang("all users") ?>
</label>
</div>
<div class="radio">
<label>
<input name="users" value="one" type="radio"> <i class="fa fa-user fa-fw"></i>
<?php lang("one user") ?>
<input type="text" name="user" class="form-control" id="user-box" placeholder="<?php lang("choose user") ?>" />
</label>
</div>
<hr />
<label><i class="fa fa-calendar"></i> <?php lang("date range") ?></label><br />
<div class="input-group">
<input type="text" id="startdate" name="startdate" class="form-control" />
<span class="input-group-addon"><i class="fa fa-chevron-right"></i></span>
<input type="text" id="enddate" name="enddate" class="form-control" />
</div>
</div>
</div>
</div> </div>
</div> </div>
<br /> <br />

@ -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 A"); // 12 hour time
#define("DATETIME_FORMAT", "M j Y G:i:s"); // 24 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 // Used on the clock widget
define("LONG_DATE_FORMAT", "l F j"); define("LONG_DATE_FORMAT", "l F j");

@ -28,4 +28,15 @@ var options = {
} }
}; };
$("#user-box").easyAutocomplete(options); $("#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
});
});
Loading…
Cancel
Save