Almost full functionality implemented.

Missing:
 * discarding products
 * list discarded products
 * autocompletion of tag and field names
 * datepicker
 * expand search grammar
This commit is contained in:
Erik Thuning 2018-09-05 17:14:18 +02:00
parent 0ce81fabcc
commit c049133d83
4 changed files with 841 additions and 203 deletions

@ -7,8 +7,10 @@
¤¤ item_link ¤¤
<a href="./?page=¤page¤&action=show&id=¤id¤">¤name¤</a>
¤¤ error ¤¤
<div id="error">
¤¤ message ¤¤
<div onClick="JavaScript:hideMessage(event)"
id="message"
class="¤type¤">
¤message¤
</div>
@ -43,7 +45,7 @@
¤¤ user_row ¤¤
<tr>
<td>
¤item_link¤
¤item_link¤<span title="¤notes¤">¤has_notes¤</span>
</td>
<td>
¤name¤
@ -106,10 +108,7 @@
</form>
¤¤ product_details ¤¤
<form action="./">
<input type="hidden"
name="page"
value="do" />
<form onSubmit="JavaScript:saveProduct(event)">
<input type="hidden"
name="id"
value="¤id¤" />
@ -119,10 +118,12 @@
<td>
</td>
<td>
<button class="right" disabled>
<button id="save"
class="right">
Spara
</button>
<button class="right" disabled>
<button id="reset"
class="right">
Återställ
</button>
</td>
@ -162,13 +163,17 @@
¤info¤
<tr>
<td>
<input type="text"
name="new_info_key"
<input onKeyPress="addField(event)"
class="newfield"
type="text"
name="new_key"
placeholder="Nytt fält" />
<button class="minibutton"
onClick="addField(event)">
+
</button>
</td>
<td>
<input type="text"
name="new_info_value" />
</td>
</tr>
<tr>
@ -176,9 +181,16 @@
Taggar:
</td>
<td>
¤tags¤ <input type="text"
name="new_tag"
placeholder="Ny tagg" />
¤tags¤
<input onKeyPress="JavaScript:addTag(event)"
class="newtag"
type="text"
name="new_tag"
placeholder="Ny tagg" />
<button class="minibutton"
onClick="JavaScript:addTag(event)">
+
</button>
</td>
</tr>
</tbody>
@ -188,35 +200,44 @@
¤¤ info_item ¤¤
<tr>
<td>
¤key¤:
¤name¤:
</td>
<td>
<input type="text"
name="info_¤key¤"
name="¤key¤"
value="¤value¤" />
<a href="">x</a>
</td>
</tr>
¤¤ tag ¤¤
<p>
<span class="tag">
¤tag¤ <a href="">x</a>
<span class="tag"
data-name="¤tag¤">
¤tag¤
<a class="tagremove"
onClick="JavaScript:removeTag(event)">
x
</a>
</span>
</p>
¤¤ user_details ¤¤
<form>
<form onSubmit="JavaScript:updateUser(event)">
<input type="hidden"
name="id"
value="¤id¤" />
<table>
<tfoot>
<tr>
<td>
</td>
<td>
<button class="right" disabled>
<button class="right"
id="save">
Spara
</button>
<button class="right" disabled>
<button class="right"
id="reset">
Återställ
</button>
</td>
@ -228,7 +249,9 @@
Namn:
</td>
<td>
¤displayname¤
<input type="text"
value="¤displayname¤"
disabled />
</td>
</tr>
<tr>
@ -246,7 +269,7 @@
Anteckningar:
</td>
<td>
<textarea>¤notes¤</textarea>
<textarea name="notes">¤notes¤</textarea>
</td>
</tr>
</tbody>
@ -301,12 +324,14 @@
¤return_date¤
</td>
<td class="¤vis_renew¤">
<form class="renew_button hidden">
<button>
<form class="renew_button"
onSubmit="JavaScript:showExtend(event)">
<button class="¤vis_renew_button¤">
Förläng
</button>
</form>
<form class="renew_confirm">
<form class="renew_confirm hidden"
onSubmit="JavaScript:extendLoan(event)">
<input type="hidden"
name="product"
value="¤id¤" />
@ -317,7 +342,7 @@
<button>
Spara
</button>
</div>
</form>
</td>
</tr>
@ -329,23 +354,28 @@
<input type="hidden"
name="page"
value="checkout" />
<span class="label">Användarnamn:</span>
<span class="label">
Användarnamn:
</span>
<input type="text"
name="user"
placeholder="Användarnamn"
value="¤user¤" required />
value="¤user¤" />
<button type="submit" >
Välj
</button>
<br/>
<span class="label">Namn:</span>
<span class="label">
Namn:
</span>
<input type="text"
name="displayname"
value="¤displayname¤"
disabled />
</form>
<h3>Låna ut artikel</h3>
<form class="light">
<form class="light"
onSubmit="JavaScript:checkoutProduct(event)">
<input type="hidden"
name="page"
value="checkout" />
@ -355,21 +385,23 @@
<span class="label">Artikel:</span>
<input type="text"
name="product"
placeholder="Serienummer" />
placeholder="Serienummer"
required />
<button>
Låna ut
</button>
<br/>
<span class="label">Slutdatum:</span>
<input type="text"
name="end_date"
name="end"
value="¤end¤" />
<button disabled>
Låna ut
</button>
</form>
¤subtitle¤
¤loan_table¤
¤¤ inventory_start ¤¤
<form class="dark">
<form class="dark"
onSubmit="JavaScript:startInventory(event)">
<button name="start">
Starta inventering
</button>
@ -393,18 +425,21 @@
<span id="seen_count">
¤seen_count¤
</span>
<form class="dark">
<form class="dark ¤hide¤"
onSubmit="JavaScript:endInventory(event)">
<button name="end">
Avsluta inventering
</button>
</form>
</p>
<form class="light">
<form class="light ¤hide¤"
onSubmit="JavaScript:inventoryProduct(event)">
<span class="label">
Artikelnummer:
Artikel:
</span>
<input type="text"
name="serial" />
name="serial"
placeholder="Serienummer" />
<button>
Registrera
</button>
@ -415,14 +450,65 @@
¤unseen_table¤
¤¤ return_page ¤¤
<form class="dark">
<form class="dark"
onSubmit="JavaScript:returnProduct(event)">
<span class="label">
Artikelnummer:
Artikel:
</span>
<input type="text"
name="serial"
placeholder="Serienummer"
required />
<button>
Lämna tillbaka
</button>
</form>
¤¤ inventory_table ¤¤
<table>
<thead>
<tr>
<th>
</th>
<th>
¤item¤
</th>
<th>
Startdatum
</th>
<th>
Slutdatum
</th>
<th>
Inventerade artiklar
</th>
<th>
Saknade artiklar
</th>
</tr>
</thead>
<tbody>
¤rows¤
</tbody>
</table>
¤¤ inventory_row ¤¤
<tr>
<td>
</td>
<td>
¤item_link¤
</td>
<td>
¤start_date¤
</td>
<td>
¤end_date¤
</td>
<td>
¤num_seen¤
</td>
<td>
¤num_unseen¤
</td>
</tr>

@ -3,24 +3,37 @@ require_once('./include/sql.php');
require_once('./include/ldap.php');
function get_ids($type) {
$only_active = false;
$append = '';
switch($type) {
case 'user':
break;
case 'product':
$append = 'where `discardtime` is null';
break;
case 'loan':
break;
case 'active_loan':
$only_active = true;
case 'inventory':
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':
$append = 'where `endtime` is not null order by `id` desc';
$type = 'inventory';
break;
default:
$err = "$type is not a valid argument. Valid arguments are user, product, loan.";
$err = "$type is not a valid argument.";
throw new Exception($err);
break;
}
$query = "select `id` from `$type`";
if($only_active) {
$query .= ' where `returntime` is null';
if($append) {
$query .= " $append";
}
$get = prepare($query);
execute($get);
@ -40,17 +53,25 @@ function get_items($type) {
};
break;
case 'product':
case 'product_discarded':
$construct = function($id) {
return new Product($id);
};
break;
case 'loan':
case 'loan_active':
$construct = function($id) {
return new Loan($id);
};
break;
case 'inventory':
case 'inventory_old':
$construct = function($id) {
return new Inventory($id);
};
break;
default:
$err = "$type is not a valid argument. Valid arguments are user, product, loan.";
$err = "$type is not a valid argument.";
throw new Exception($err);
break;
}
@ -63,7 +84,7 @@ function get_items($type) {
}
function get_tags() {
$search = prepare('select distinct `tag` from `product_tag`');
$search = prepare('select distinct `tag` from `product_tag` order by `tag`');
execute($search);
$out = array();
foreach(result_list($search) as $row) {
@ -72,6 +93,16 @@ function get_tags() {
return $out;
}
function get_fields() {
$search = prepare('select distinct `field` from `product_info` order by `field`');
execute($search);
$out = array();
foreach(result_list($search) as $row) {
$out[] = $row['field'];
}
return $out;
}
function search_products($term) {
$search = prepare("select * from `product` where `name` like ?");
bind($search, 's', '%'.$term.'%');
@ -122,12 +153,13 @@ function search_loans($products) {
return $out;
}
class Product {
private $id = 0;
private $name = '';
private $invoice = '';
private $serial = '';
private $createtime = null;
private $discardtime = null;
private $info = array();
private $tags = array();
@ -139,26 +171,49 @@ class Product {
$tags = array()
) {
$now = time();
$stmt = 'insert into `product`(`name`, `invoice`, `serial`, `createtime`) values (?, ?, ?, ?)';
$ins_prod = prepare($stmt);
bind($ins_prod, 'sssi', $name, $invoice, $serial, $now);
execute($ins_prod);
$prodid = $ins_prod->insert_id;
$ins_info = prepare('insert into `product_info`(`product`, `field`, `data`) values (?, ?, ?)');
bind($ins_info, 'iss', $prodid, $key, $value);
foreach($ins_info as $key => $value) {
execute($ins_info);
begin_trans();
try {
$stmt = 'insert into `product`(`name`, `invoice`, `serial`, `createtime`) values (?, ?, ?, ?)';
$ins_prod = prepare($stmt);
bind($ins_prod, 'sssi', $name, $invoice, $serial, $now);
execute($ins_prod);
$prodid = $ins_prod->insert_id;
$ins_info = prepare('insert into `product_info`(`product`, `field`, `data`) values (?, ?, ?)');
foreach($info as $key => $value) {
bind($ins_info, 'iss', $prodid, $key, $value);
execute($ins_info);
}
$ins_tag = prepare('insert into `product_tag`(`product`, `tag`) values (?, ?)');
foreach($tags as $tag) {
bind($ins_tag, 'is', $prodid, $tag);
execute($ins_tag);
}
commit_trans();
return new Product($prodid);
} catch(Exception $e) {
revert_trans();
throw $e;
}
$ins_tag = prepare('insert into `product_tag`(`product`, `tag`) values (?, ?)');
bind($ins_tag, 'is', $prodid, $tag);
foreach($tags as $tag) {
execute($ins_tag);
}
return new Product($prodid);
}
public function __construct($id) {
$this->id = $id;
public function __construct($clue, $type = 'id') {
switch($type) {
case 'id':
$this->id = $clue;
break;
case 'serial':
$search = prepare('select `id` from `product` where `serial`=?');
bind($search, 's', $clue);
execute($search);
$result = result_single($search);
if($result === null) {
throw new Exception('Invalid serial.');
}
$this->id = $result['id'];
break;
default:
throw new Exception('Invalid type.');
}
$this->update_fields();
$this->update_info();
$this->update_tags();
@ -172,11 +227,13 @@ class Product {
$this->name = $product['name'];
$this->invoice = $product['invoice'];
$this->serial = $product['serial'];
$this->createtime = $product['createtime'];
$this->discardtime = $product['discardtime'];
return true;
}
private function update_info() {
$get = prepare('select * from `product_info` where `product`=?');
$get = prepare('select * from `product_info` where `product`=? order by `field`');
bind($get, 'i', $this->id);
execute($get);
foreach(result_list($get) as $row) {
@ -188,7 +245,7 @@ class Product {
}
private function update_tags() {
$get = prepare('select * from `product_tag` where `product`=?');
$get = prepare('select * from `product_tag` where `product`=? order by `tag`');
bind($get, 'i', $this->id);
execute($get);
$newtags = array();
@ -202,6 +259,26 @@ class Product {
public function get_id() {
return $this->id;
}
public function get_createtime() {
return $this->createtime;
}
public function get_discardtime($format = true) {
if($format) {
return gmdate('Y-m-d', $this->discardtime);
}
return $this->discardtime;
}
public function discard() {
$now = time();
$update = prepare('update `product` set `discardtime`=? where `id`=?');
bind($update, 'ii', $now, $this->id);
execute($update);
$this->discardtime = $now;
return true;
}
public function get_name() {
return $this->name;
@ -244,7 +321,7 @@ class Product {
}
public function set_info($field, $value) {
$find = prepare('select * from `product_info` where `id`=? and `field`=?');
$find = prepare('select * from `product_info` where `product`=? and `field`=?');
bind($find, 'is', $this->id, $field);
execute($find);
if(result_single($find) === null) {
@ -341,16 +418,22 @@ class User {
return new User($ins_user->insert_id);
}
public function __construct($clue) {
public function __construct($clue, $type = 'id') {
$id = $clue;
if(preg_match('/[a-z]/', $clue)) {
$find = prepare('select `id` from `user` where `name`=?');
bind($find, 's', $clue);
execute($find);
$id = result_single($find)['id'];
if($id === null) {
throw new Exception("Invalid username '$clue'");
}
switch($type) {
case 'id':
break;
case 'name':
$find = prepare('select `id` from `user` where `name`=?');
bind($find, 's', $clue);
execute($find);
$id = result_single($find)['id'];
if($id === null) {
throw new Exception("Invalid username '$clue'");
}
break;
default:
throw new Exception('Invalid type');
}
$this->id = $id;
$this->update_fields();
@ -368,7 +451,11 @@ class User {
public function get_displayname() {
global $ldap;
return $ldap->get_user($this->name);
try {
return $ldap->get_user($this->name);
} catch(Exception $e) {
return 'Ej i SUKAT';
}
}
public function get_id() {
@ -438,10 +525,12 @@ class User {
}
$now = time();
$insert = prepare('insert into `loan`(`user`, `product`, `starttime`, `endtime`) values (?, ?, ?, ?)');
bind($insert, 'iiii', $this->id, $prod_id, $now, $endtime);
bind($insert, 'iiii',
$this->id, $prod_id,
$now, strtotime($endtime . ' 13:00'));
execute($insert);
$loan_id = $statement->insert_id;
return new Loan($id);
$loan_id = $insert->insert_id;
return new Loan($loan_id);
}
}
@ -488,7 +577,10 @@ class Loan {
};
if($format) {
$style = function($time) {
return gmdate('Y-m-d', $time);
if($time) {
return gmdate('Y-m-d', $time);
}
return $time;
};
}
return array('start' => $style($this->starttime),
@ -503,12 +595,21 @@ class Loan {
}
return false;
}
public function extend($time) {
$ts = strtotime($time . ' 13:00');
$query = prepare('update `loan` set `endtime`=? where `id`=?');
bind($query, 'ii', $ts, $this->id);
execute($query);
$this->endtime = $ts;
return true;
}
public function end_loan() {
public function end() {
$now = time();
$end = prepare('update `loan` set `returntime`=? where `id`=?');
bind($end, 'ii', $now, $this->id);
execute($end);
$query = prepare('update `loan` set `returntime`=? where `id`=?');
bind($query, 'ii', $now, $this->id);
execute($query);
$this->returntime = $now;
return true;
}
@ -530,17 +631,24 @@ class Inventory {
private $starttime = '';
private $endtime = null;
private $seen_products = array();
private $active_loans = array();
public static function begin() {
if(Inventory::get_active_inventory() !== null) {
if(Inventory::get_active() !== null) {
throw new Exception('Inventory already in progress.');
}
$now = time();
$start = prepare('insert into `inventory`(`starttime`) values (?)');
bind($start, 'i', $now);
execute($start);
return new Inventory($start->insert_id);
$invid = $start->insert_id;
$prodid = '';
$register = prepare('insert into `inventory_product`(`inventory`, `product`) values (?, ?)');
foreach(get_items('loan_active') as $loan) {
$prodid = $loan->get_product()->get_id();
bind($register, 'ii', $invid, $prodid);
execute($register);
}
return new Inventory($invid);
}
public static function get_active() {
@ -571,7 +679,6 @@ class Inventory {
foreach(result_list($prodget) as $row) {
$this->seen_products[] = $row['product'];
}
$this->active_loans = get_ids('active_loan');
}
public function end() {
@ -586,7 +693,11 @@ class Inventory {
public function add_product($product) {
$add = prepare('insert into `inventory_product`(`inventory`, `product`) values (?, ?)');
bind($add, 'ii', $this->id, $product->get_id());
execute($add);
try {
execute($add);
} catch(Exception $e) {
return false;
}
$this->products[] = $product->get_id();
return true;
}
@ -616,11 +727,28 @@ class Inventory {
return $out;
}
public function get_products_on_loan() {
public function get_unseen_products() {
$all = get_items('product');
$out = array();
foreach($this->active_loans as $loanid) {
$loan = new Loan($loanid);
$out[] = $loan->get_product();
$include = function($product) {
if(!in_array($product->get_id(), $this->seen_products)) {
return true;
}
return false;
};
if($this->endtime) {
$include = function($product) {
if($product->get_createtime() < $this->endtime
&& !in_array($product->get_id(), $this->seen_products)) {
return true;
}
return false;
};
}
foreach($all as $product) {
if($include($product)) {
$out[] = $product;
}
}
return $out;
}

@ -19,12 +19,24 @@ function make_page($page) {
return new UserPage();
case 'inventory':
return new InventoryPage();
case 'history':
return new HistoryPage();
case 'ajax':
return new Ajax();
}
}
abstract class Page {
protected abstract function render_body();
abstract class Responder {
protected $fragments = array();
public function __construct() {
$this->fragments = get_fragments('./html/fragments.html');
}
}
abstract class Page extends Responder {
protected abstract function render_body();
protected $page = 'checkout';
protected $title = "Boka2";
protected $subtitle = '';
@ -33,14 +45,14 @@ abstract class Page {
'return' => 'Lämna',
'products' => 'Artiklar',
'users' => 'Låntagare',
'inventory' => 'Inventera');
'inventory' => 'Inventera',
'history' => 'Historik');
private $template_parts = array();
protected $fragments = array();
public function __construct() {
parent::__construct();
$this->template_parts = get_fragments('./html/base.html');
$this->fragments = get_fragments('./html/fragments.html');
if(isset($_GET['page'])) {
$this->page = $_GET['page'];
}
@ -95,8 +107,9 @@ abstract class Page {
}
final private function render_error() {
print(replace(array('message' => $this->error),
$this->fragments['error']));
print(replace(array('type' => 'error',
'message' => $this->error),
$this->fragments['message']));
}
final private function render_foot() {
@ -105,15 +118,22 @@ abstract class Page {
final protected function build_user_table($users) {
$rows = '';
$replacements = array('name' => '',
'loan' => '',
'item_link' => '');
foreach($users as $user) {
$replacements = array('name' => '',
'loan' => '',
'has_notes' => '',
'notes' => '',
'item_link' => '');
$replacements['name'] = $user->get_name();
$notes = $user->get_notes();
if($notes) {
$replacements['notes'] = $notes;
$replacements['has_notes'] = '*';
}
$userlink = replace(array('id' => $user->get_id(),
'name' => $user->get_displayname(),
'page' => 'users'
), $this->fragments['item_link']);
'page' => 'users'),
$this->fragments['item_link']);
$replacements['item_link'] = $userlink;
$loans = $user->get_loans('active');
$loan_str = '';
@ -141,8 +161,8 @@ abstract class Page {
foreach($products as $product) {
$prodlink = replace(array('id' => $product->get_id(),
'name' => $product->get_name(),
'page' => 'products'
), $this->fragments['item_link']);
'page' => 'products'),
$this->fragments['item_link']);
$available = 'Tillgänglig';
$status = 'available';
$loan = $product->get_active_loan();
@ -150,8 +170,8 @@ abstract class Page {
$user = $loan->get_user();
$userlink = replace(array('name' => $user->get_displayname(),
'id' => $user->get_id(),
'page' => 'users'
), $this->fragments['item_link']);
'page' => 'users'),
$this->fragments['item_link']);
$available = 'Utlånad till '.$userlink;
if($loan->is_overdue()) {
$status = 'overdue';
@ -163,8 +183,8 @@ abstract class Page {
}
$rows .= replace(array('available' => $available,
'status' => $status,
'item_link' => $prodlink
), $this->fragments['product_row']);
'item_link' => $prodlink),
$this->fragments['product_row']);
}
return replace(array('rows' => $rows),
$this->fragments['product_table']);
@ -194,8 +214,8 @@ abstract class Page {
$product = $loan->get_product();
$prodlink = replace(array('id' => $product->get_id(),
'name' => $product->get_name(),
'page' => 'products'
), $this->fragments['item_link']);
'page' => 'products'),
$this->fragments['item_link']);
$available = '';
$duration = $loan->get_duration();
$status = 'on_loan';
@ -214,24 +234,26 @@ abstract class Page {
'status' => $status,
'vis_renew' => $vis_renew,
'vis_return' => $vis_return,
'end_new' => $duration['end_renew']
), $this->fragments['loan_row']);
'end_new' => $duration['end_renew']),
$this->fragments['loan_row']);
}
return replace(array('rows' => $rows,
'vis_renew' => $vis_renew,
'vis_return' => $vis_return,
'item' => 'Artikel'
), $this->fragments['loan_table']);
'item' => 'Artikel'),
$this->fragments['loan_table']);
}
final protected function build_product_loan_table($loans) {
$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']);
'page' => 'users'),
$this->fragments['item_link']);
$available = '';
$duration = $loan->get_duration();
$status = 'on_loan';
@ -239,21 +261,70 @@ abstract class Page {
$status = 'overdue';
}
$returndate = '';
if(isset($duration['return'])) {
$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,
'visibility' => ''
), $this->fragments['loan_row']);
'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']);
}
return replace(array('rows' => $rows,
'visibility' => '',
'item' => 'Låntagare'
), $this->fragments['loan_table']);
'vis_renew' => $renew_column_visible,
'vis_return' => '',
'item' => 'Låntagare'),
$this->fragments['loan_table']);
}
final protected function build_inventory_details($inventory,
$interactive = true) {
$duration = $inventory->get_duration();
$all_products = get_items('product');
$seen = $inventory->get_seen_products();
$unseen = array();
foreach($all_products as $product) {
if(!in_array($product, $seen)) {
$unseen[] = $product;
}
}
$seen_title = replace(array('title' => 'Inventerade artiklar'),
$this->fragments['subtitle']);
$seen_table = 'Inga artiklar inventerade.';
$unseen_table = 'Inga artiklar saknas.';
$missing = 'Saknade artiklar';
$hidden = 'hidden';
if($interactive) {
$missing = 'Kvarvarande artiklar';
$hidden = '';
}
$unseen_title = replace(array('title' => $missing),
$this->fragments['subtitle']);
if($seen) {
$seen_table = $this->build_product_table($seen);
}
if($unseen) {
$unseen_table = $this->build_product_table($unseen);
}
print(replace(array('start_date' => $duration['start'],
'total_count' => count($all_products),
'seen_count' => count($seen),
'seen_title' => $seen_title,
'seen_table' => $seen_table,
'unseen_title' => $unseen_title,
'unseen_table' => $unseen_table,
'hide' => $hidden),
$this->fragments['inventory_do']));
}
}
@ -344,9 +415,10 @@ class ProductPage extends Page {
private function build_product_details() {
$info = '';
foreach($this->product->get_info() as $key => $value) {
$info .= replace(array('key' => $key,
'value' => $value
), $this->fragments['info_item']);
$info .= replace(array('name' => ucfirst($key),
'key' => $key,
'value' => $value),
$this->fragments['info_item']);
}
$tags = '';
foreach($this->product->get_tags() as $tag) {
@ -357,12 +429,16 @@ class ProductPage extends Page {
'serial' => $this->product->get_serial(),
'invoice' => $this->product->get_invoice(),
'tags' => $tags,
'info' => $info
), $this->fragments['product_details']);
'info' => $info),
$this->fragments['product_details']);
$out .= replace(array('title' => 'Lånehistorik'),
$this->fragments['subtitle']);
$out .= $this->build_product_loan_table(
$this->product->get_loan_history());
$loan_table = 'Inga lån att visa.';
$history = $this->product->get_loan_history();
if($history) {
$loan_table = $this->build_product_loan_table($history);
}
$out .= $loan_table;
return $out;
}
@ -372,8 +448,8 @@ class ProductPage extends Page {
'serial' => '',
'invoice' => '',
'tags' => '',
'info' => ''
), $this->fragments['product_details']);
'info' => ''),
$this->fragments['product_details']);
}
}
@ -427,8 +503,8 @@ class UserPage extends Page {
'id' => $this->user->get_id(),
'name' => $this->user->get_name(),
'displayname' => $this->user->get_displayname(),
'notes' => $this->user->get_notes()
), $this->fragments['user_details']);
'notes' => $this->user->get_notes()),
$this->fragments['user_details']);
}
}
@ -441,7 +517,7 @@ class CheckoutPage extends Page {
if(isset($_GET['user'])) {
$this->userstr = $_GET['user'];
try {
$this->user = new User($this->userstr);
$this->user = new User($this->userstr, 'name');
} catch(Exception $ue) {
try {
$ldap = new Ldap();
@ -470,24 +546,19 @@ class CheckoutPage extends Page {
if($loans) {
$loan_table = $this->build_user_loan_table($loans, 'renew');
}
$subhead = replace(array(
'title' => 'Lånade artiklar'
), $this->fragments['subtitle']);
$subhead = replace(array('title' => 'Lånade artiklar'),
$this->fragments['subtitle']);
}
print(replace(array('user' => $this->userstr,
'displayname' => $displayname,
'end' => $enddate,
'subtitle' => $subhead,
'loan_table' => $loan_table,
), $this->fragments['checkout_page']));
'loan_table' => $loan_table),
$this->fragments['checkout_page']));
}
}
class ReturnPage extends Page {
public function __construct() {
parent::__construct();
}
protected function render_body() {
print($this->fragments['return_page']);
}
@ -506,37 +577,375 @@ class InventoryPage extends Page {
print($this->fragments['inventory_start']);
return;
}
$duration = $this->inventory->get_duration();
$all_products = get_items('product');
$total = count($all_products);
$on_loan = $this->inventory->get_products_on_loan();
$seen = $this->inventory->get_seen_products();
$inventoried = $seen;
foreach($on_loan as $product) {
if(!in_array($product, $inventoried)) {
$inventoried[] = $product;
print($this->build_inventory_details($this->inventory));
}
}
class HistoryPage extends Page {
private $action = 'list';
private $inventory = null;
public function __construct() {
parent::__construct();
if(isset($_GET['action'])) {
$this->action = $_GET['action'];
}
if(isset($_GET['id'])) {
$this->inventory = new Inventory($_GET['id']);
}
switch($this->action) {
case 'show':
$this->subtitle = 'Inventeringsdetaljer';
break;
case 'list':
$this->subtitle = 'Genomförda inventeringar';
break;
}
}
protected function render_body() {
switch($this->action) {
case 'list':
print($this->build_inventory_table());
break;
case 'show':
if($this->inventory &&
Inventory::get_active() !== $this->inventory) {
print($this->build_inventory_details($this->inventory, false));
}
break;
}
}
private function build_inventory_table() {
$items = get_items('inventory_old');
if(!$items) {
return 'Inga inventeringar gjorda.';
}
$rows = '';
foreach($items as $inventory) {
$id = $inventory->get_id();
$inventory_link = replace(array('id' => $id,
'name' => $id,
'page' => 'history'),
$this->fragments['item_link']);
$duration = $inventory->get_duration();
$num_seen = count($inventory->get_seen_products());
$num_unseen = count($inventory->get_unseen_products());
$rows .= replace(array('item_link' => $inventory_link,
'start_date' => $duration['start'],
'end_date' => $duration['end'],
'num_seen' => $num_seen,
'num_unseen' => $num_unseen),
$this->fragments['inventory_row']);
}
return replace(array('item' => 'Tillfälle',
'rows' => $rows),
$this->fragments['inventory_table']);
}
protected function temp($loans) {
$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';
}
$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']);
}
return replace(array('rows' => $rows,
'vis_renew' => $renew_column_visible,
'vis_return' => '',
'item' => 'Låntagare'),
$this->fragments['loan_table']);
}
}
class Ajax extends Responder {
private $action = '';
public function __construct() {
parent::__construct();
if(isset($_GET['action'])) {
$this->action = $_GET['action'];
}
}
public function render() {
$out = '';
switch($this->action) {
default:
$out = new Success('ajax endpoint');
break;
case 'getfragment':
$out = $this->get_fragment();
break;
case 'checkout':
$out = $this->checkout_product();
break;
case 'return':
$out = $this->return_product();
break;
case 'extend':
$out = $this->extend_loan();
break;
case 'startinventory':
$out = $this->start_inventory();
break;
case 'endinventory':
$out = $this->end_inventory();
break;
case 'inventoryproduct':
$out = $this->inventory_product();
break;
case 'updateproduct':
$out = $this->update_product();
break;
case 'updateuser':
$out = $this->update_user();
break;
}
print($out->toJson());
}
private function get_fragment() {
$fragment = $_POST['fragment'];
if(isset($this->fragments[$fragment])) {
return new Success($this->fragments[$fragment]);
}
return new Failure("Ogiltigt fragment '$fragment'");
}
private function checkout_product() {
$user = new User($_POST['user'], 'name');
$product = null;
try {
$product = new Product($_POST['product'], 'serial');
} catch(Exception $e) {
return new Failure('Ogiltigt serienummer.');
}
try {
$user->create_loan($product, $_POST['end']);
return new Success($product->get_name() . 'utlånad.');
} catch(Exception $e) {
return new Failure('Artikeln är redan utlånad.');
}
}
private function return_product() {
$product = null;
try {
$product = new Product($_POST['serial'], 'serial');
} catch(Exception $e) {
return new Failure('Ogiltigt serienummer.');
}
$loan = $product->get_active_loan();
if($loan) {
$loan->end();
return new Success($product->get_name() . ' har lämnats tillbaka.');
}
return new Failure('Artikeln är inte utlånad.');
}
private function extend_loan() {
$product = null;
try {
$product = new Product($_POST['product']);
} catch(Exception $e) {
return new Failure('Ogiltigt ID.');
}
$loan = $product->get_active_loan();
if($loan) {
$loan->extend($_POST['end']);
return new Success('Lånet förlängt');
}
return new Failure('Lån saknas.');
}
private function start_inventory() {
try {
Inventory::begin();
return new Success('Inventering startad.');
} catch(Exception $e) {
return new Failure('Inventering redan igång.');
}
}
private function end_inventory() {
$inventory = Inventory::get_active();
if($inventory === null) {
return new Failure('Ingen inventering pågår.');
}
$inventory->end();
return new Success('Inventering avslutad.');
}
private function inventory_product() {
$inventory = Inventory::get_active();
if($inventory === null) {
return new Failure('Ingen inventering pågår.');
}
$product = null;
try {
$product = new Product($_POST['serial'], 'serial');
} catch(Exception $e) {
return new Failure('Ogiltigt serienummer.');
}
$result = $inventory->add_product($product);
if(!$result) {
return new Failure('Artikeln är redan registrerad.');
}
return new Success('Artikeln registrerad.');
}
private function update_product() {
$info = $_POST;
$id = $info['id'];
$name = $info['name'];
$serial = $info['serial'];
$invoice = $info['invoice'];
$tags = explode(',', $info['tags']);
foreach(array('id', 'name', 'serial', 'invoice', 'tags') as $key) {
unset($info[$key]);
}
$product = null;
if(!$id) {
try {
$product = Product::create_product($name,
$invoice,
$serial,
$info,
$tags);
$prodlink = replace(array('page' => 'products',
'id' => $product->get_id(),
'name' => $product->get_name()),
$this->fragments['item_link']);
return new Success("Artikeln '$prodlink' sparad.");
} catch(Exception $e) {
return new Failure($e->getMessage());
}
}
$unseen = array();
foreach($all_products as $product) {
if(!in_array($product, $inventoried)) {
$unseen[] = $product;
$product = new Product($id);
if(!$name) {
return new Failure('Produkten måste ha ett namn.');
}
if($name != $product->get_name()) {
$product->set_name($name);
}
if(!$serial) {
return new Failure('Produkten måste ha ett serienummer.');
}
if($serial != $product->get_serial()) {
try {
$product->set_serial($serial);
} catch(Exception $e) {
return new Failure('Det angivna serienumret finns redan på en annan artikel.');
}
}
$seen_title = replace(array('title' => 'Inventerade artiklar'),
$this->fragments['subtitle']);
$unseen_title = replace(array('title' => 'Kvarvarande artiklar'),
$this->fragments['subtitle']);
$seen_table = $this->build_product_table($inventoried);
$unseen_table = $this->build_product_table($unseen);
print(replace(array('start_date' => $duration['start'],
'total_count' => $total,
'seen_count' => count($on_loan) + count($seen),
'seen_title' => $seen_title,
'seen_table' => $seen_table,
'unseen_title' => $unseen_title,
'unseen_table' => $unseen_table
), $this->fragments['inventory_do']));
if(!$invoice) {
return new Failure('Produkten måste ha ett fakturanummer.');
}
if($invoice != $product->get_invoice()) {
$product->set_invoice($invoice);
}
foreach($product->get_info() as $key => $prodvalue) {
if(!isset($info[$key]) || !$info[$key]) {
$product->remove_info($key);
continue;
}
if($prodvalue != $info[$key]) {
$product->set_info($key, $info[$key]);
}
unset($info[$key]);
}
foreach($info as $key => $invalue) {
if($invalue) {
$product->set_info($key, $invalue);
}
}
foreach($product->get_tags() as $tag) {
if(!in_array($tag, $tags)) {
$product->remove_tag($tag);
continue;
}
unset($tags[array_search($tag, $tags)]);
}
foreach($tags as $tag) {
$product->add_tag($tag);
}
return new Success('Ändringarna sparade.');
}
private function update_user() {
$id = $_POST['id'];
$name = $_POST['name'];
$notes = $_POST['notes'];
if(!$name) {
return new Failure('Användarnamnet får inte vara tomt.');
}
$user = new User($id);
if($user->get_name() != $name) {
$user->set_name($name);
}
if($user->get_notes() != $notes) {
$user->set_notes($notes);
}
return new Success('Ändringarna sparade.');
}
}
class Result {
private $type = '';
private $message = '';
public function __construct($type, $message) {
$this->type = $type;
$this->message = $message;
}
public function toJson() {
return json_encode(array(
'type' => $this->type,
'message' => $this->message
));
}
}
class Success extends Result {
public function __construct($message) {
parent::__construct('success', $message);
}
}
class Failure extends Result {
public function __construct($message) {
parent::__construct('error', $message);
}
}
?>

@ -1,18 +1,31 @@
#error {
position: absolute;
textarea {
height: 80px;
}
#message {
position: absolute;
top: 5px;
left: 50%;
transform: translate(-50%);
background-color: #e89e66;
border: 2px solid #d95e00;
padding: 6px 10px;
border-style: solid;
border-width: 2px;
}
.error {
background-color: #e89e66;
border-color: #d95e00;
}
.success {
background-color: #c8cba6;
border-color: #a3a86b;
}
.tag {
margin: 1px;
border: 1px solid black;
padding: 1px 2px;
background-color: lightgray;
background-color: #acdee6;
padding-left: 2px;
}
.label {
@ -83,26 +96,28 @@ input:disabled {
background-color: #ededed;
}
.button {
.newtag, .newfield {
width: 11.5em !important;
}
.minibutton {
font-family: Verdana, Arial, Helvetica, sans-serif;
background-image: url(images/button-background-repeater.gif);
background-position: left;
background-repeat: repeat-x;
color: white;
padding-left: 10px;
padding-right: 10px;
text-decoration: none;
draggable: false;
color: white !important;
padding-right: 2px;
padding-left: 2px;
}
.button:link {
.tagremove {
background-color: #e17e33;
padding-left: 2px;
padding-right: 2px;
color: white;
}
.button:visited {
.tagremove:hover {
cursor: pointer;
color: white;
}
.button:hover {
color: white;
}