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]])) {
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]])) {

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",
"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."
]);

@ -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"
]
]);

@ -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 = "<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'];
}
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');
$converter = new HTMLConverter();
$out = "<!DOCTYPE html>\n"
. "<meta charset=\"utf-8\">\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 nonce="$SECURE_NONCE">
.table-csv-data {
@ -190,23 +275,23 @@ function dataToHTML($data, $name = "report", $user = null) {
}
</style>
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;
}
}

@ -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"
]

@ -5,41 +5,71 @@ redirectifnotloggedin();
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
} else {
?>
<form action="lib/reports.php" method="GET" target="_BLANK">
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-4">
<label for="type"><?php lang("report type"); ?></label>
<select name="type" class="form-control" required>
<option value="shifts"><?php lang("shifts") ?></option>
</select>
</div>
<div class="col-xs-12 col-sm-6 col-md-4">
<label for="users"><?php lang("filter"); ?></label>
<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 class="col-xs-12 col-sm-6">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><label for="type"><i class="fa fa-list"></i> <?php lang("report type"); ?></label></h3>
</div>
<div class="panel-body">
<select name="type" class="form-control" required>
<option value="shifts"><?php lang("shifts") ?></option>
<option value="punches"><?php lang("punches") ?></option>
</select>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><label for="format"><i class="fa fa-file"></i> <?php lang("format"); ?></label></h3>
</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 class="col-xs-12 col-sm-6 col-md-4">
<label for="type"><?php lang("format"); ?></label>
<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 class="col-xs-12 col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><label><i class="fa fa-filter"></i> <?php lang("filter"); ?></label></h3>
</div>
<div class="panel-body">
<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>
<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"); // 24 hour time
// Used for reports
define("DATE_FORMAT", "M j, Y");
// Used on the clock widget
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