diff --git a/composer.json b/composer.json index 6b3633d..36cd777 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,9 @@ "type": "project", "require": { "catfan/medoo": "^1.2", - "guzzlehttp/guzzle": "^6.2" + "guzzlehttp/guzzle": "^6.2", + "league/csv": "^9.0", + "lapinator/ods-php-generator": "^0.0.3" }, "license": "MPL-2.0", "authors": [ diff --git a/composer.lock b/composer.lock index e9fdb12..c8066f7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "67e95c250946f032648f9527831ec71a", - "content-hash": "36bca807ff9018548a5ce6c686d131dc", + "hash": "0476ce3c9f1be7b4a13d6533df71433d", + "content-hash": "9ff412188d3e72a7961ecf9b9e966404", "packages": [ { "name": "catfan/medoo", @@ -247,6 +247,113 @@ ], "time": "2017-03-20 17:10:46" }, + { + "name": "lapinator/ods-php-generator", + "version": "v0.0.3", + "source": { + "type": "git", + "url": "https://github.com/Lapinator/odsPhpGenerator.git", + "reference": "575314c003c2ec3032813bedcc1d27032b7b7ab2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Lapinator/odsPhpGenerator/zipball/575314c003c2ec3032813bedcc1d27032b7b7ab2", + "reference": "575314c003c2ec3032813bedcc1d27032b7b7ab2", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Laurent VUIBERT", + "email": "lapinator@gmx.fr", + "homepage": "http://lapinator.net", + "role": "Developer" + } + ], + "description": "Open Document Spreadsheet (.ods) generator ", + "homepage": "https://odsphpgenerator.lapinator.net/", + "keywords": [ + "LibreOffice", + "ods" + ], + "time": "2016-04-14 21:51:27" + }, + { + "name": "league/csv", + "version": "9.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/csv.git", + "reference": "66118f5c2a7e4da77e743e69f74773c63b73e8f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/66118f5c2a7e4da77e743e69f74773c63b73e8f9", + "reference": "66118f5c2a7e4da77e743e69f74773c63b73e8f9", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=7.0.10" + }, + "require-dev": { + "ext-curl": "*", + "friendsofphp/php-cs-fixer": "^2.0", + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Csv\\": "src" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://github.com/nyamsprod/", + "role": "Developer" + } + ], + "description": "Csv data manipulation made easy in PHP", + "homepage": "http://csv.thephpleague.com", + "keywords": [ + "csv", + "export", + "filter", + "import", + "read", + "write" + ], + "time": "2017-11-28 08:29:49" + }, { "name": "psr/http-message", "version": "1.0.1", diff --git a/database.mwb b/database.mwb index bcb3d74..1496b94 100644 Binary files a/database.mwb and b/database.mwb differ diff --git a/database.sql b/database.sql index 709c4d7..b4bf013 100644 --- a/database.sql +++ b/database.sql @@ -1,5 +1,5 @@ -- MySQL Script generated by MySQL Workbench --- Tue 17 Oct 2017 03:17:47 AM MDT +-- Tue 26 Dec 2017 04:44:44 PM MST -- Model: New Model Version: 1.0 -- MySQL Workbench Forward Engineering @@ -102,6 +102,18 @@ CREATE TABLE IF NOT EXISTS `inventory`.`permissions` ( ENGINE = InnoDB; +-- ----------------------------------------------------- +-- Table `inventory`.`report_access_codes` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `inventory`.`report_access_codes` ( + `id` INT NOT NULL AUTO_INCREMENT, + `code` VARCHAR(45) NULL, + `expires` DATETIME NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `id_UNIQUE` (`id` ASC)) +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/database_upgrade/v1.0.1_1.1.sql b/database_upgrade/v1.0.1_1.1.sql new file mode 100644 index 0000000..8f3d3af --- /dev/null +++ b/database_upgrade/v1.0.1_1.1.sql @@ -0,0 +1,13 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +CREATE TABLE IF NOT EXISTS `report_access_codes` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `code` VARCHAR(45) NULL DEFAULT NULL, + `expires` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `id_UNIQUE` (`id` ASC)) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8 diff --git a/lang/en_us.php b/lang/en_us.php index 3214dc4..ed35671 100644 --- a/lang/en_us.php +++ b/lang/en_us.php @@ -95,5 +95,15 @@ define("STRINGS", [ "missing name" => "You need to enter a name.", "use the dropdowns" => "Whoops, you need to use the category and location autocomplete boxes.", "make categories and locations" => "Please create at least one category and location before adding an item.", - "search" => "Search Items" -]); \ No newline at end of file + "search" => "Search Items", + "report export" => "Reports/Export", + "report type" => "Report type", + "format" => "Format", + "generate report" => "Generate report", + "choose an option" => "Choose an option", + "csv file" => "CSV text file", + "ods file" => "ODS spreadsheet", + "html file" => "HTML web page", + "itemid" => "Item ID", + "id" => "ID" +]); diff --git a/lib/reports.php b/lib/reports.php new file mode 100644 index 0000000..432f15d --- /dev/null +++ b/lib/reports.php @@ -0,0 +1,261 @@ +has('report_access_codes', ["AND" => ['code' => $VARS['code'], 'expires[>]' => $date]])) { + dieifnotloggedin(); + } +} else { + dieifnotloggedin(); +} + +// Delete old DB entries +$database->delete('report_access_codes', ['expires[<=]' => $date]); + +if (LOADED) { + if (isset($VARS['type']) && isset($VARS['format'])) { + generateReport($VARS['type'], $VARS['format']); + die(); + } else { + lang("invalid parameters"); + die(); + } +} + +/** + * Get a 2d array of the items in the database. + * @global type $database + * @param array $filter Medoo WHERE clause. + * @return string + */ +function getItemReport($filter = []) { + global $database; + $items = $database->select( + "items", [ + "[>]locations" => ["locid"], + "[>]categories" => ["catid"] + ], [ + "itemid", + "name", + "catname", + "locname", + "code1", + "code2", + "qty", + "want", + "userid", + "text1", + "text2", + "text3" + ], $filter + ); + $header = [ + lang("itemid", false), + lang("name", false), + lang("category", false), + lang("location", false), + lang("code 1", false), + lang("code 2", false), + lang("quantity", false), + lang("want", false), + lang("assigned to", false), + lang("description", false), + lang("notes", false), + lang("comments", false) + ]; + $out = [$header]; + for ($i = 0; $i < count($items); $i++) { + $user = ""; + if (!is_null($items[$i]["userid"])) { + require_once __DIR__ . "/userinfo.php"; + $u = getUserByID($items[$i]["userid"]); + $user = $u['name'] . " (" . $u['username'] . ')'; + } + $out[] = [ + $items[$i]["itemid"], + $items[$i]["name"], + $items[$i]["catname"], + $items[$i]["locname"], + $items[$i]["code1"], + $items[$i]["code2"], + $items[$i]["qty"], + $items[$i]["want"], + $user, + $items[$i]["text1"], + $items[$i]["text2"], + $items[$i]["text3"] + ]; + } + return $out; +} + +function getCategoryReport() { + global $database; + $cats = $database->select('categories', [ + 'catid', + 'catname' + ]); + $header = [lang("id", false), lang("category", false), lang("item count", false)]; + $out = [$header]; + for ($i = 0; $i < count($cats); $i++) { + $itemcount = $database->count('items', ['catid' => $cats[$i]['catid']]); + $out[] = [ + $cats[$i]["catid"], + $cats[$i]["catname"], + $itemcount . "" + ]; + } + return $out; +} + +function getLocationReport() { + global $database; + $locs = $database->select('locations', [ + 'locid', + 'locname', + 'loccode' + ]); + $header = [lang("id", false), lang("location", false), lang("code", false), lang("item count", false)]; + $out = [$header]; + for ($i = 0; $i < count($locs); $i++) { + $itemcount = $database->count('items', ['locid' => $locs[$i]['locid']]); + $out[] = [ + $locs[$i]["locid"], + $locs[$i]["locname"], + $locs[$i]["loccode"], + $itemcount . "" + ]; + } + return $out; +} + +function getReportData($type) { + switch ($type) { + case "item": + return getItemReport(); + break; + case "category": + return getCategoryReport(); + break; + case "location": + return getLocationReport(); + break; + case "itemstock": + return getItemReport(["AND" => ["qty[<]want", "want[>]" => 0]]); + break; + default: + return [["error"]]; + } +} + +function dataToCSV($data, $name = "report") { + $csv = Writer::createFromString(''); + $csv->insertAll($data); + header('Content-type: text/csv'); + header('Content-Disposition: attachment; filename="' . $name . "_" . date("Y-m-d_Hi") . ".csv" . '"'); + echo $csv; + die(); +} + +function dataToODS($data, $name = "report") { + $ods = new ods(); + $styleColumn = new odsStyleTableColumn(); + $styleColumn->setUseOptimalColumnWidth(true); + $headerstyle = new odsStyleTableCell(); + $headerstyle->setFontWeight("bold"); + $table = new odsTable($name); + + for ($i = 0; $i < count($data[0]); $i++) { + $table->addTableColumn(new odsTableColumn($styleColumn)); + } + + $rowid = 0; + foreach ($data as $datarow) { + $row = new odsTableRow(); + foreach ($datarow as $cell) { + if ($rowid == 0) { + $row->addCell(new odsTableCellString($cell, $headerstyle)); + } else { + $row->addCell(new odsTableCellString($cell)); + } + } + $table->addRow($row); + $rowid++; + } + $ods->addTable($table); + $ods->downloadOdsFile($name . "_" . date("Y-m-d_Hi") . ".ods"); +} + +function dataToHTML($data, $name = "report") { + global $SECURE_NONCE; + // HTML exporter doesn't like null values + for ($i = 0; $i < count($data); $i++) { + for ($j = 0; $j < count($data[$i]); $j++) { + if (is_null($data[$i][$j])) { + $data[$i][$j] = ''; + } + } + } + header('Content-type: text/html'); + $converter = new HTMLConverter(); + $out = "\n" + . "\n" + . "\n" + . "" . $name . "_" . date("Y-m-d_Hi") . "\n" + . << +STYLE + . $converter->convert($data); + echo $out; +} + +function generateReport($type, $format) { + $data = getReportData($type); + switch ($format) { + case "ods": + dataToODS($data, $type); + break; + case "html": + dataToHTML($data, $type); + break; + case "csv": + default: + echo dataToCSV($data, $type); + break; + } +} diff --git a/pages.php b/pages.php index eb6daab..2218f0b 100644 --- a/pages.php +++ b/pages.php @@ -75,6 +75,14 @@ define("PAGES", [ "static/js/editloc.js" ], ], + "export" => [ + "title" => "report export", + "navbar" => true, + "icon" => "download", + "scripts" => [ + "static/js/export.js" + ] + ], "404" => [ "title" => "404 error" ] diff --git a/pages/export.php b/pages/export.php new file mode 100644 index 0000000..97f86a9 --- /dev/null +++ b/pages/export.php @@ -0,0 +1,40 @@ + + +
+
+
+ + +
+
+ + +
+
+
+ insert('report_access_codes', ['code' => $code, 'expires' => date("Y-m-d H:i:s", strtotime("+5 minutes"))]); + ?> + + + +
\ No newline at end of file diff --git a/static/js/export.js b/static/js/export.js new file mode 100644 index 0000000..430ba51 --- /dev/null +++ b/static/js/export.js @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +$("#genrptbtn").click(function () { + setTimeout(function () { + window.location.reload(); + }, 1000) +}); \ No newline at end of file