Several changes:

- The inventory page now shows status of seen products as of when they were registered,
   not as of now.
 - Renamed a placeholder in fragments.html to be more intuitive.
 - Added a type field to the event table in the database, so that complete events can
   be constructed without complex logic.
 - Refactored to support the above changes
This commit is contained in:
Erik Thuning 2019-07-24 14:09:31 +02:00
parent 763d1c6ba5
commit ef756e36b4
9 changed files with 172 additions and 62 deletions

@ -78,6 +78,7 @@ create table `user` (
create table `event` ( create table `event` (
`id` bigint(20) not null auto_increment, `id` bigint(20) not null auto_increment,
primary key(`id`), primary key(`id`),
`type` varchar(64),
`product` bigint(20) not null, `product` bigint(20) not null,
constraint `e_f_product` constraint `e_f_product`
foreign key(`product`) references `product`(`id`), foreign key(`product`) references `product`(`id`),
@ -123,7 +124,8 @@ create table `inventory_product` (
`product` bigint(20) not null, `product` bigint(20) not null,
constraint `i_f_product` constraint `i_f_product`
foreign key(`product`) references `product`(`id`), foreign key(`product`) references `product`(`id`),
unique `uniq_inventory_product`(`inventory`, `product`) unique `uniq_inventory_product`(`inventory`, `product`),
`regtime` bigint(20) not null
) character set utf8mb4, ) character set utf8mb4,
collate utf8mb4_unicode_ci; collate utf8mb4_unicode_ci;

@ -95,7 +95,7 @@
¤serial¤ ¤serial¤
</td> </td>
<td> <td>
¤availabl ¤not
</td> </td>
</tr> </tr>

@ -5,7 +5,7 @@ class Event {
protected $starttime = 0; protected $starttime = 0;
protected $returntime = null; protected $returntime = null;
protected static function create_event($product) { protected static function create_event($product, $type) {
$status = $product->get_status(); $status = $product->get_status();
if($status != 'available') { if($status != 'available') {
$emsg = ''; $emsg = '';
@ -28,11 +28,18 @@ class Event {
} }
throw new Exception($emsg); throw new Exception($emsg);
} }
switch($type) {
case 'loan':
case 'service':
break;
default:
throw new Excpetion("Invalid argument '$type'");
}
$now = time(); $now = time();
$insert = prepare('insert into $insert = prepare('insert into `event`
`event`(`product`, `starttime`) (`product`, `type`, `starttime`)
values (?, ?)'); values (?, ?, ?)');
bind($insert, 'ii', $product->get_id(), $now); bind($insert, 'isi', $product->get_id(), $type, $now);
execute($insert); execute($insert);
$event_id = $insert->insert_id; $event_id = $insert->insert_id;
return new Event($event_id); return new Event($event_id);

@ -15,12 +15,12 @@ class Inventory {
execute($start); execute($start);
$invid = $start->insert_id; $invid = $start->insert_id;
$prodid = ''; $prodid = '';
$register = prepare('insert into $register = prepare('insert into `inventory_product`
`inventory_product`(`inventory`, `product`) (`inventory`, `product`, `regtime`)
values (?, ?)'); values (?, ?, ?)');
foreach(get_items('loan_active') as $loan) { foreach(get_items('event_active') as $event) {
$prodid = $loan->get_product()->get_id(); $prodid = $event->get_product()->get_id();
bind($register, 'ii', $invid, $prodid); bind($register, 'iii', $invid, $prodid, $now);
execute($register); execute($register);
} }
return new Inventory($invid); return new Inventory($invid);
@ -75,9 +75,10 @@ class Inventory {
} }
public function add_product($product) { public function add_product($product) {
$add = prepare('insert into `inventory_product`(`inventory`, `product`) $add = prepare('insert into `inventory_product`
values (?, ?)'); (`inventory`, `product`, `regtime`)
bind($add, 'ii', $this->id, $product->get_id()); values (?, ?, ?)');
bind($add, 'iii', $this->id, $product->get_id(), time());
try { try {
execute($add); execute($add);
} catch(Exception $e) { } catch(Exception $e) {
@ -107,6 +108,21 @@ class Inventory {
return $out; return $out;
} }
public function get_product_regtime($product) {
$invid = $this->id;
$prodid = $product->get_id();
$search = prepare('select `regtime` from `inventory_product`
where `inventory` = ? and `product` = ?');
bind($search, 'ii', $invid, $prodid);
execute($search);
$result = result_single($search);
if(!$result) {
$emsg = "Inventory $invid has no reference to product $prodid.";
throw new Exception($emsg);
}
return $result['regtime'];
}
public function get_unseen_products() { public function get_unseen_products() {
$all = get_items('product'); $all = get_items('product');
$out = array(); $out = array();

@ -5,10 +5,12 @@ class Loan extends Event {
public static function create_loan($user, $product, $endtime) { public static function create_loan($user, $product, $endtime) {
begin_trans(); begin_trans();
$event = parent::create_event($product); $event = parent::create_event($product, 'loan');
$event_id = $event->get_id(); $event_id = $event->get_id();
$insert = prepare('insert into `loan`(`user`, `endtime`) values (?, ?)'); $insert = prepare('insert into `loan`(`event`, `user`, `endtime`)
bind($insert, 'ii', $user->get_id(), strtotime($endtime . ' 13:00')); values (?, ?, ?)');
$endtime .= '13:00';
bind($insert, 'iii', $event_id, $user->get_id(), strtotime($endtime));
execute($insert); execute($insert);
commit_trans(); commit_trans();
return new Loan($event_id); return new Loan($event_id);

@ -134,15 +134,16 @@ abstract class Page extends Responder {
'name' => $product->get_name(), 'name' => $product->get_name(),
'page' => 'products'), 'page' => 'products'),
$this->fragments['item_link']); $this->fragments['item_link']);
$available = 'Tillgänglig'; $note = 'Tillgänglig';
$status = $product->get_status(); $status = $product->get_status();
switch($status) { switch($status) {
case 'discarded': case 'discarded':
$available = 'Skrotad '.$discarded; $discarded = format_date($product->get_discardtime());
$note = 'Skrotad '.$discarded;
break; break;
case 'service': case 'service':
$service = $product->get_active_service(); $service = $product->get_active_service();
$available = 'På service sedan ' $note = 'På service sedan '
.format_date($service->get_starttime()); .format_date($service->get_starttime());
break; break;
case 'on_loan': case 'on_loan':
@ -153,18 +154,18 @@ abstract class Page extends Responder {
'id' => $user->get_id(), 'id' => $user->get_id(),
'page' => 'users'), 'page' => 'users'),
$this->fragments['item_link']); $this->fragments['item_link']);
$available = 'Utlånad till '.$userlink; $note = 'Utlånad till '.$userlink;
if($loan->is_overdue()) { if($loan->is_overdue()) {
$available .= ', försenad'; $note .= ', försenad';
} else { } else {
$available .= ', åter '.format_date($loan->get_endtime()); $note .= ', slutdatum '.format_date($loan->get_endtime());
} }
break; break;
} }
$rows .= replace(array('available' => $available, $rows .= replace(array('status' => $status,
'item_link' => $prodlink,
'serial' => $product->get_serial(), 'serial' => $product->get_serial(),
'status' => $status, 'note' => $note,),
'item_link' => $prodlink),
$this->fragments['product_row']); $this->fragments['product_row']);
} }
return replace(array('rows' => $rows), return replace(array('rows' => $rows),
@ -240,6 +241,63 @@ abstract class Page extends Responder {
$this->fragments['history_table']); $this->fragments['history_table']);
} }
final protected function build_seen_table($products, $inventory) {
$rows = '';
foreach($products as $product) {
$prodid = $product->get_id();
$prodlink = replace(array('id' => $prodid,
'name' => $product->get_name(),
'page' => 'products'),
$this->fragments['item_link']);
$regtime = $inventory->get_product_regtime($product);
$event = $product->get_historic_event($regtime);
$status = '';
$note = '';
if(!$event) {
$discardtime = $product->get_discardtime();
if($discardtime && $discardtime < $regtime) {
$status = 'discarded';
$note = 'Skrotad '.format_date($discardtime);
} else {
$status = 'available';
}
} else if($event instanceof Service) {
$status = 'service';
$note = 'På service sedan '.format_date($event->get_starttime());
$returntime = $event->get_returntime();
if($returntime) {
$note .= ', åter den '.format_date($returntime);
}
} else if($event instanceof Loan) {
$user = $event->get_user();
$userlink = replace(array('name' => $user->get_displayname(),
'id' => $user->get_id(),
'page' => 'users'),
$this->fragments['item_link']);
$status = 'on_loan';
$note = 'Utlånad till '.$userlink;
$returntime = $event->get_returntime();
if($event->get_endtime() < $regtime) {
$status = 'overdue';
$note .= ', försenad';
} else {
$note .= ', slutdatum '.format_date($event->get_endtime());
}
if($returntime) {
$note .= ', återlämnad '.format_date($returntime);
}
}
$rows .= replace(array('status' => $status,
'item_link' => $prodlink,
'serial' => $product->get_serial(),
'note' => $note),
$this->fragments['product_row']);
}
return replace(array('rows' => $rows),
$this->fragments['product_table']);
}
final protected function build_inventory_details($inventory, final protected function build_inventory_details($inventory,
$interactive = true) { $interactive = true) {
$startdate = format_date($inventory->get_starttime()); $startdate = format_date($inventory->get_starttime());
@ -272,7 +330,7 @@ abstract class Page extends Responder {
$out .= replace(array('title' => 'Inventerade artiklar'), $out .= replace(array('title' => 'Inventerade artiklar'),
$this->fragments['subtitle']); $this->fragments['subtitle']);
if($seen) { if($seen) {
$out .= $this->build_product_table($seen); $out .= $this->build_seen_table($seen, $inventory);
} else { } else {
$out .= 'Inga artiklar inventerade.'; $out .= 'Inga artiklar inventerade.';
} }

@ -148,10 +148,7 @@ class Product {
return $this->createtime; return $this->createtime;
} }
public function get_discardtime($format = true) { public function get_discardtime() {
if($this->discardtime && $format) {
return gmdate('Y-m-d', $this->discardtime);
}
return $this->discardtime; return $this->discardtime;
} }
@ -325,7 +322,7 @@ class Product {
} }
public function get_status() { public function get_status() {
if($this->get_discardtime(false)) { if($this->get_discardtime()) {
return 'discarded'; return 'discarded';
} }
if($this->get_active_service()) { if($this->get_active_service()) {
@ -341,6 +338,32 @@ class Product {
return 'on_loan'; return 'on_loan';
} }
public function get_historic_event($time) {
$search = prepare("select `id`,`type` from `event`
where `product` = ?
and `starttime` < ?
and ( `returntime` > ?
or `returntime` is null )");
bind($search, 'iii', $this->id, $time, $time);
execute($search);
$result = result_single($search);
if(!$result) {
return null;
}
$id = $result['id'];
$type = $result['type'];
switch($type) {
case 'service':
return new Service($id);
break;
case 'loan':
return new Loan($id);
break;
default:
throw new Exception("Invalid type '$type'");
}
}
public function get_active_loan() { public function get_active_loan() {
$find = prepare('select `id` from `event` $find = prepare('select `id` from `event`
inner join `loan` inner join `loan`
@ -357,23 +380,25 @@ class Product {
public function get_history() { public function get_history() {
$out = array(); $out = array();
foreach(array('loan' => function($id) { return new Loan($id);}, $find = prepare('select `id`,`type` from `event` '
'service' => function($id) { return new Service($id);}) .'where `product`=? order by `starttime` desc');
as $type => $func) { bind($find, 'i', $this->id);
$find = prepare('select `id` from `event` ' execute($find);
."inner join `$type` " $items = result_list($find);
."on `event`.`id` = `$type`.`event` " foreach($items as $item) {
.'where `product`=? order by `starttime` desc'); $id = $item['id'];
bind($find, 'i', $this->id); switch($item['type']) {
execute($find); case 'service':
$items = result_list($find); $out[] = new Service($id);
foreach($items as $item) { break;
$out[] = $func($item['id']); case 'loan':
$out[] = new Loan($id);
break;
default:
$type = $item['type'];
throw new Exception("Invalid type '$type'");
} }
} }
usort($out, function($a, $b) {
return $a->get_starttime() < $b->get_starttime();
});
return $out; return $out;
} }
} }

@ -2,7 +2,7 @@
class Service extends Event { class Service extends Event {
public static function create_service($product) { public static function create_service($product) {
begin_trans(); begin_trans();
$event = parent::create_event($product); $event = parent::create_event($product, 'service');
$event_id = $event->get_id(); $event_id = $event->get_id();
$insert = prepare('insert into `service`(`event`) values (?)'); $insert = prepare('insert into `service`(`event`) values (?)');
bind($insert, 'i', $event_id); bind($insert, 'i', $event_id);

@ -108,18 +108,18 @@ function get_ids($type) {
case 'product': case 'product':
$append = 'where `discardtime` is null'; $append = 'where `discardtime` is null';
break; break;
case 'loan': case 'product_discarded':
$type = 'product';
$append = 'where `discardtime` is not null';
break;
case 'event':
break;
case 'event_active':
$type = 'event';
$append = 'where `returntime` is null';
break; break;
case 'inventory': case 'inventory':
break; break;
case 'product_discarded':
$append = 'where `discardtime` is not null';
$type = 'product';
break;
case 'loan_active':
$append = 'where `returntime` is null';
$type = 'loan';
break;
case 'inventory_old': case 'inventory_old':
$append = 'where `endtime` is not null order by `id` desc'; $append = 'where `endtime` is not null order by `id` desc';
$type = 'inventory'; $type = 'inventory';
@ -156,10 +156,10 @@ function get_items($type) {
return new Product($id); return new Product($id);
}; };
break; break;
case 'loan': case 'event':
case 'loan_active': case 'event_active':
$construct = function($id) { $construct = function($id) {
return new Loan($id); return new Event($id);
}; };
break; break;
case 'inventory': case 'inventory':