From 0923435462bd4e57aecbe2e0810b49568ad625f4 Mon Sep 17 00:00:00 2001 From: Erik Thuning <boooink@gmail.com> Date: Wed, 12 Jun 2019 20:39:55 +0200 Subject: [PATCH] Added a function to temporariliy suspend availability of products for service. Also did some refactoring in related areas. --- database.sql | 11 ++++ html/fragments.html | 21 ++++---- include/Ajax.php | 22 +++++++- include/Loan.php | 44 +++++++++++++++- include/Page.php | 94 ++++++++++++++++++---------------- include/Product.php | 76 +++++++++++++++++++++------- include/ProductPage.php | 28 ++++++----- include/SearchPage.php | 6 ++- include/Service.php | 109 ++++++++++++++++++++++++++++++++++++++++ include/User.php | 24 --------- script.js | 13 +++++ style.css | 4 ++ 12 files changed, 339 insertions(+), 113 deletions(-) create mode 100644 include/Service.php diff --git a/database.sql b/database.sql index 3d5f035..5bf343f 100644 --- a/database.sql +++ b/database.sql @@ -89,6 +89,17 @@ create table `loan` ( ) character set utf8mb4, collate utf8mb4_unicode_ci; +create table `service` ( + `id` bigint(20) not null auto_increment, + primary key(`id`), + `product` bigint(20) not null, + constraint `s_f_product` + foreign key(`product`) references `product`(`id`), + `starttime` bigint(20) not null, + `returntime` bigint(20) default null +) character set utf8mb4, + collate utf8mb4_unicode_ci; + create table `inventory` ( `id` bigint(20) not null auto_increment, primary key(`id`), diff --git a/html/fragments.html b/html/fragments.html index d97a63f..192d5e2 100644 --- a/html/fragments.html +++ b/html/fragments.html @@ -244,6 +244,17 @@ </form> ¤label¤ <div class="clear"></div> +<form class="¤hidden¤"> + <input type="hidden" + name="id" + value="¤id¤" /> + <button onClick="JavaScript:discardProduct(event)"> + Skrota artikel + </button> + <button onClick="JavaScript:toggleService(event)"> + ¤service¤ + </button> +</form> ¤¤ product_label ¤¤ <div class="qr left"> @@ -271,16 +282,6 @@ </body> </html> -¤¤ discard_button ¤¤ -<form> - <input type="hidden" - name="id" - value="¤id¤" /> - <button onClick="JavaScript:discardProduct(event)"> - Skrota artikel - </button> -</form> - ¤¤ info_item ¤¤ <tr> <td> diff --git a/include/Ajax.php b/include/Ajax.php index 7496423..82ee28d 100644 --- a/include/Ajax.php +++ b/include/Ajax.php @@ -54,6 +54,8 @@ class Ajax extends Responder { case 'discardproduct': $out = $this->discard_product(); break; + case 'toggleservice': + $out = $this->toggle_service(); } print($out->toJson()); } @@ -67,7 +69,12 @@ class Ajax extends Responder { } private function checkout_product() { - $user = new User($_POST['user'], 'name'); + $user = null; + try { + $user = new User($_POST['user'], 'name'); + } catch(Exception $e) { + return new Failure('Ogiltigt användar-id.'); + } $product = null; try { $product = new Product($_POST['product'], 'serial'); @@ -75,7 +82,7 @@ class Ajax extends Responder { return new Failure('Ogiltigt serienummer.'); } try { - $user->create_loan($product, $_POST['end']); + Loan::create_loan($user, $product, $_POST['end']); return new Success($product->get_name() . 'utlånad.'); } catch(Exception $e) { return new Failure('Artikeln är redan utlånad.'); @@ -345,5 +352,16 @@ class Ajax extends Responder { return new Failure('Artikeln är redan skrotad.'); } } + + private function toggle_service() { + $product = new Product($_POST['id']); + try { + $product->toggle_service(); + return new Success('Service-status uppdaterad.'); + } catch(Exception $e) { + return new Failure('Service kan inte registreras ' + .'på den här artikeln nu.'); + } + } } ?> diff --git a/include/Loan.php b/include/Loan.php index 87d57d7..1fb4fd6 100644 --- a/include/Loan.php +++ b/include/Loan.php @@ -6,9 +6,51 @@ class Loan { private $starttime = 0; private $endtime = 0; private $returntime = null; + + public static function create_loan($user, $product, $endtime) { + $status = $product->get_status(); + if($status != 'available') { + $emsg = ''; + $prod_id = $product->get_id(); + switch($status) { + case 'on_loan': + case 'overdue': + $loan_id = $product->get_active_loan()->get_id(); + $emsg = "Product $prod_id has an active "; + $emsg .= "loan (id $loan_id) already."; + break; + case 'discarded': + $emsg = "Product $prod_id has been discarded."; + break; + case 'service': + $service_id = $product->get_active_service()->get_id(); + $emsg = "Product $prod_id is on service (id $service_id)."; + break; + } + throw new Exception($emsg); + } + $now = time(); + $insert = prepare('insert into + `loan`(`user`, `product`, `starttime`, `endtime`) + values (?, ?, ?, ?)'); + bind($insert, 'iiii', + $user->get_id(), $product->get_id(), + $now, strtotime($endtime . ' 13:00')); + execute($insert); + $loan_id = $insert->insert_id; + return new Loan($loan_id); + } public function __construct($id) { - $this->id = $id; + $search = prepare('select `id` from `loan` + where `id`=?'); + bind($search, 'i', $id); + execute($search); + $result = result_single($search); + if($result === null) { + throw new Exception('Loan does not exist.'); + } + $this->id = $result['id']; $this->update_fields(); } diff --git a/include/Page.php b/include/Page.php index 3f3902a..0fb4bf3 100644 --- a/include/Page.php +++ b/include/Page.php @@ -135,14 +135,19 @@ abstract class Page extends Responder { 'page' => 'products'), $this->fragments['item_link']); $available = 'Tillgänglig'; - $status = 'available'; - $discarded = $product->get_discardtime(); - if($discarded) { - $available = 'Skrotad '.$discarded; - $status = 'discarded'; - } else { - $loan = $product->get_active_loan(); - if($loan) { + $status = $product->get_status(); + switch($status) { + case 'discarded': + $available = 'Skrotad '.$discarded; + break; + case 'service': + $service = $product->get_active_service(); + $available = 'På service sedan ' + .$service->get_duration()['start']; + break; + case 'on_loan': + case 'overdue': + $loan = $product->get_active_loan(); $user = $loan->get_user(); $userlink = replace(array('name' => $user->get_displayname(), 'id' => $user->get_id(), @@ -150,13 +155,11 @@ abstract class Page extends Responder { $this->fragments['item_link']); $available = 'Utlånad till '.$userlink; if($loan->is_overdue()) { - $status = 'overdue'; $available .= ', försenad'; } else { - $status = 'on_loan'; $available .= ', åter '.$loan->get_duration()['end']; } - } + break; } $rows .= replace(array('available' => $available, 'serial' => $product->get_serial(), @@ -194,7 +197,6 @@ abstract class Page extends Responder { 'name' => $product->get_name(), 'page' => 'products'), $this->fragments['item_link']); - $available = ''; $duration = $loan->get_duration(); $status = 'on_loan'; if($loan->is_overdue()) { @@ -222,41 +224,45 @@ abstract class Page extends Responder { $this->fragments['loan_table']); } - final protected function build_product_loan_table($loans) { + final protected function build_product_history_table($history) { $rows = ''; $renew_column_visible = 'hidden'; - foreach($loans as $loan) { - $user = $loan->get_user(); - $product = $loan->get_product(); - $userlink = replace(array('id' => $user->get_id(), - 'name' => $user->get_name(), - 'page' => 'users'), - $this->fragments['item_link']); - $available = ''; - $duration = $loan->get_duration(); - $status = 'on_loan'; - if($loan->is_overdue()) { - $status = 'overdue'; + foreach($history as $event) { + $duration = $event->get_duration(); + $product = $event->get_product(); + $fields = array('item_link' => 'Service', + 'start_date' => $duration['start'], + 'end_date' => $duration['end'], + 'return_date' => $duration['return'], + 'status' => 'available', + 'vis_renew' => $renew_column_visible, + 'vis_renew_button' => 'hidden', + 'vis_return' => '', + 'id' => $product->get_id(), + 'end_new' => ''); + if($event instanceof Loan) { + $user = $event->get_user(); + $id = $user->get_id(); + $name = $user->get_name(); + $fields['item_link'] = replace(array('id' => $id, + 'name' => $name, + 'page' => 'users'), + $this->fragments['item_link']); + $fields['end_new'] = $duration['end_renew']; + if($event->is_active()) { + $fields['vis_renew_button'] = ''; + $fields['vis_renew'] = ''; + $renew_column_visible = ''; + if($event->is_overdue()) { + $fields['status'] = 'overdue'; + } else { + $fields['status'] = 'on_loan'; + } + } + } else if ($event instanceof Service) { + $fields['status'] = 'service'; } - $returndate = ''; - $renew_visible = ''; - if($duration['return']) { - $returndate = $duration['return']; - $renew_visible = 'hidden'; - } else { - $renew_column_visible = ''; - } - $rows .= replace(array('item_link' => $userlink, - 'start_date' => $duration['start'], - 'end_date' => $duration['end'], - 'return_date' => $returndate, - 'status' => $status, - 'vis_renew' => $renew_column_visible, - 'vis_renew_button' => $renew_visible, - 'vis_return' => '', - 'id' => $product->get_id(), - 'end_new' => $duration['end_renew']), - $this->fragments['loan_row']); + $rows .= replace($fields, $this->fragments['loan_row']); } return replace(array('rows' => $rows, 'vis_renew' => $renew_column_visible, diff --git a/include/Product.php b/include/Product.php index 0b8a806..9bfabdc 100644 --- a/include/Product.php +++ b/include/Product.php @@ -9,13 +9,11 @@ class Product { private $info = array(); private $tags = array(); - public static function create_product( - $name = '', - $invoice = '', - $serial = '', - $info = array(), - $tags = array() - ) { + public static function create_product($name = '', + $invoice = '', + $serial = '', + $info = array(), + $tags = array()) { $now = time(); begin_trans(); try { @@ -59,7 +57,7 @@ class Product { execute($search); $result = result_single($search); if($result === null) { - throw new Exception('Product does not exist..'); + throw new Exception('Product does not exist.'); } $this->id = $result['id']; $this->update_fields(); @@ -118,7 +116,7 @@ class Product { case 'tag': $matchvalues = $this->get_tags(); case 'status': - $matchvalues[] = $this->get_loan_status(); + $matchvalues[] = $this->get_status(); case 'fritext': $matchvalues[] = $this->name; $matchvalues[] = $this->serial; @@ -152,6 +150,9 @@ class Product { } public function discard() { + if($this->get_status() != 'available') { + return false; + } $now = time(); $update = prepare('update `product` set `discardtime`=? where `id`=?'); bind($update, 'ii', $now, $this->id); @@ -160,6 +161,33 @@ class Product { return true; } + public function toggle_service() { + $status = $this->get_status(); + $now = time(); + $update = ''; + if($status == 'service') { + return $this->get_active_service()->end(); + } else if($status == 'available') { + Service::create_service($this); + return true; + } + $id = $this->get_id(); + throw new Exception("The state ($status) of this product (id $id) " + ."does not allow servicing."); + } + + public function get_active_service() { + $find = prepare('select `id` from `service`' + .'where `returntime` is null and product=?'); + bind($find, 'i', $this->id); + execute($find); + $result = result_single($find); + if($result === null) { + return null; + } + return new Service($result['id']); + } + public function get_name() { return $this->name; } @@ -276,13 +304,16 @@ class Product { return true; } - public function get_loan_status() { + public function get_status() { if($this->get_discardtime(false)) { return 'discarded'; } + if($this->get_active_service()) { + return 'service'; + } $loan = $this->get_active_loan(); if(!$loan) { - return 'no_loan'; + return 'available'; } if($loan->is_overdue()) { return 'overdue'; @@ -302,16 +333,23 @@ class Product { return new Loan($result['id']); } - public function get_loan_history() { - $find = prepare('select `id` from `loan` - where product=? order by `starttime` desc'); - bind($find, 'i', $this->id); - execute($find); - $loans = result_list($find); + public function get_history() { $out = array(); - foreach($loans as $loan) { - $out[] = new Loan($loan['id']); + foreach(array('loan' => function($id) { return new Loan($id);}, + 'service' => function($id) { return new Service($id);}) + as $type => $func) { + $find = prepare("select `id` from `$type`" + .'where `product`=? order by `starttime` desc'); + bind($find, 'i', $this->id); + execute($find); + $items = result_list($find); + foreach($items as $item) { + $out[] = $func($item['id']); + } } + usort($out, function($a, $b) { + return $a->get_duration()['start'] < $b->get_duration()['start']; + }); return $out; } } diff --git a/include/ProductPage.php b/include/ProductPage.php index e9d23fe..065bdaa 100644 --- a/include/ProductPage.php +++ b/include/ProductPage.php @@ -78,25 +78,29 @@ class ProductPage extends Page { 'serial' => $this->product->get_serial(), 'invoice' => $this->product->get_invoice(), 'tags' => $tags, - 'info' => $info); - $label = ''; + 'info' => $info, + 'label' => '', + 'hidden' => 'hidden', + 'service' => 'Starta service'); if(class_exists('QRcode')) { - $label = replace($fields, $this->fragments['product_label']); + $fields['label'] = replace($fields, + $this->fragments['product_label']); } - $fields['label'] = $label; - $out = replace($fields, $this->fragments['product_details']); if(!$this->product->get_discardtime()) { - $out .= replace(array('id' => $this->product->get_id()), - $this->fragments['discard_button']); + $fields['hidden'] = ''; + if($this->product->get_status() == 'service') { + $fields['service'] = 'Avsluta service'; + } } - $out .= replace(array('title' => 'Lånehistorik'), + $out = replace($fields, $this->fragments['product_details']); + $out .= replace(array('title' => 'Artikelhistorik'), $this->fragments['subtitle']); - $loan_table = 'Inga lån att visa.'; - $history = $this->product->get_loan_history(); + $history_table = 'Ingen historik att visa.'; + $history = $this->product->get_history(); if($history) { - $loan_table = $this->build_product_loan_table($history); + $history_table = $this->build_product_history_table($history); } - $out .= $loan_table; + $out .= $history_table; return $out; } diff --git a/include/SearchPage.php b/include/SearchPage.php index 49d6970..0536cdb 100644 --- a/include/SearchPage.php +++ b/include/SearchPage.php @@ -80,7 +80,7 @@ class SearchPage extends Page { case 'ledigt': case 'tillgänglig': case 'tillgängligt': - $newitem = 'no_loan'; + $newitem = 'available'; break; case 'sen': case 'sent': @@ -95,6 +95,10 @@ class SearchPage extends Page { case 'slängt': $newitem = 'discarded'; break; + case 'lagning': + case 'reparation': + $newitem = 'service'; + break; } $translated[] = $newitem; } diff --git a/include/Service.php b/include/Service.php new file mode 100644 index 0000000..5f19014 --- /dev/null +++ b/include/Service.php @@ -0,0 +1,109 @@ +<?php +class Service { + private $id = 0; + private $product = 0; + private $starttime = 0; + private $returntime = null; + + public static function create_service($product) { + $status = $product->get_status(); + if($status != 'available') { + $emsg = ''; + $prod_id = $product->get_id(); + switch($status) { + case 'on_loan': + case 'overdue': + $loan_id = $product->get_active_loan()->get_id(); + $emsg = "Product $prod_id has an active "; + $emsg .= "loan (id $loan_id)."; + break; + case 'discarded': + $emsg = "Product $prod_id has been discarded."; + break; + case 'service': + $service_id = $product->get_active_service()->get_id(); + $emsg = "Product $prod_id is on service " + ."(id $service_id) already."; + break; + } + throw new Exception($emsg); + } + $now = time(); + $insert = prepare('insert into + `service`(`product`, `starttime`) + values (?, ?)'); + bind($insert, 'ii', $product->get_id(), $now); + execute($insert); + $service_id = $insert->insert_id; + return new Loan($service_id); + } + + public function __construct($id) { + $search = prepare('select `id` from `service` + where `id`=?'); + bind($search, 'i', $id); + execute($search); + $result = result_single($search); + if($result === null) { + throw new Exception('Service does not exist.'); + } + $this->id = $result['id']; + $this->update_fields(); + } + + private function update_fields() { + $get = prepare('select * from `service` where `id`=?'); + bind($get, 'i', $this->id); + execute($get); + $loan = result_single($get); + $this->product = $loan['product']; + $this->starttime = $loan['starttime']; + $this->returntime = $loan['returntime']; + } + + public function get_id() { + return $this->id; + } + + public function get_user() { + return 'Service'; + } + + public function get_product() { + return new Product($this->product); + } + + public function get_duration($format = true) { + $style = function($time) { + return $time; + }; + if($format) { + $style = function($time) { + if($time) { + return gmdate('Y-m-d', $time); + } + return $time; + }; + } + return array('start' => $style($this->starttime), + 'end' => '', + 'return' => $style($this->returntime)); + } + + public function is_active() { + if($this->returntime === null) { + return true; + } + return false; + } + + public function end() { + $now = time(); + $query = prepare('update `service` set `returntime`=? where `id`=?'); + bind($query, 'ii', $now, $this->id); + execute($query); + $this->returntime = $now; + return true; + } +} +?> diff --git a/include/User.php b/include/User.php index d21c0a4..7e79fcb 100644 --- a/include/User.php +++ b/include/User.php @@ -148,29 +148,5 @@ class User { } return $overdue; } - - public function create_loan($product, $endtime) { - $find = prepare('select * from `loan` - where `product`=? and `returntime` is null'); - $prod_id = $product->get_id(); - bind($find, 'i', $prod_id); - execute($find); - $loan = result_single($find); - if($loan !== null) { - $loan_id = $loan['id']; - throw new Exception( - "Product $prod_id has an active loan (id $loan_id) already."); - } - $now = time(); - $insert = prepare('insert into - `loan`(`user`, `product`, `starttime`, `endtime`) - values (?, ?, ?, ?)'); - bind($insert, 'iiii', - $this->id, $prod_id, - $now, strtotime($endtime . ' 13:00')); - execute($insert); - $loan_id = $insert->insert_id; - return new Loan($loan_id); - } } ?> diff --git a/script.js b/script.js index 48b3140..8cd1a91 100644 --- a/script.js +++ b/script.js @@ -434,6 +434,19 @@ function discardProduct(event) { ajaxRequest('discardproduct', dataListFromForm(form), render) } +function toggleService(event) { + event.preventDefault() + var form = event.currentTarget.parentNode + var render = function(result) { + if(result.type == 'success') { + window.location.reload(false) + } else { + showResult(result) + } + } + ajaxRequest('toggleservice', dataListFromForm(form), render) +} + function searchInput(event) { if(event.key != "Enter") { return diff --git a/style.css b/style.css index 7f42a55..4354786 100644 --- a/style.css +++ b/style.css @@ -84,6 +84,10 @@ td.discarded { background-color: #a0a0a0; } +td.service { + background-color: #e7e08d; +} + tbody tr { background-color: #d7e0eb; }