Added a function to temporariliy suspend availability of products

for service. Also did some refactoring in related areas.
This commit is contained in:
Erik Thuning 2019-06-12 20:39:55 +02:00
parent e5790ca013
commit 0923435462
12 changed files with 339 additions and 113 deletions

@ -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`),

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

@ -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.');
}
}
}
?>

@ -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();
}

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

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

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

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

109
include/Service.php Normal file

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

@ -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);
}
}
?>

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

@ -84,6 +84,10 @@ td.discarded {
background-color: #a0a0a0;
}
td.service {
background-color: #e7e08d;
}
tbody tr {
background-color: #d7e0eb;
}