466 lines
16 KiB
PHP
466 lines
16 KiB
PHP
<?php
|
|
/******************************************************************************
|
|
* YMS - Yacht Management Software
|
|
* Copyright (C) 2024-2026 Thomas Hooge
|
|
*
|
|
* SPDX-License-Identifier: WTFPL
|
|
******************************************************************************/
|
|
|
|
require 'globals.inc';
|
|
require 'lib/gpc.inc';
|
|
|
|
$pagetitle=_('Inventory');
|
|
|
|
$id = gpc_get_int($_REQUEST, 'id', 0);
|
|
|
|
$opt_storage = db_get_opt_storage($user->vid);
|
|
$opt_box = db_get_opt_box($user->vid);
|
|
$opt_invcond = db_load_enum('inventory', 'invcond', true, true);
|
|
$opt_equipment = db_get_opt_equip($user->vid, [-1 => _('― none ―')]);
|
|
|
|
// ========== ACTIONS START ===================================================
|
|
|
|
switch ($submit = form_get_action()) {
|
|
|
|
case NULL: break;
|
|
|
|
case 'add': $action = ACT_ADD; break;
|
|
case 'view': $action = ACT_VIEW; break;
|
|
case 'edit': $action = ACT_EDIT; break;
|
|
case 'del': $action = ACT_DELETE; break;
|
|
|
|
case 'filter':
|
|
$flt = array();
|
|
$flt['stor'] = gpc_get_string($_POST, 'flt_stor');
|
|
//$flt['box'] = gpc_get_string($_POST, 'flt_box');
|
|
$flt['cond'] = gpc_get_string($_POST, 'flt_cond');
|
|
$flt['txt'] = gpc_get_string($_POST, 'flt_txt');
|
|
db_save_filter($flt, 201);
|
|
$action = ACT_DEFAULT;
|
|
break;
|
|
|
|
case 'freset':
|
|
db_clear_filter(201);
|
|
$action = ACT_DEFAULT;
|
|
break;
|
|
|
|
case 'insert':
|
|
$p[':invname'] = gpc_get_string($_POST, 'invname');
|
|
$p[':number'] = gpc_get_int($_POST, 'number');
|
|
$p[':conttype'] = gpc_get_string($_POST, 'conttype');
|
|
$p[':sid'] = gpc_get_int($_POST, 'sid');
|
|
$p[':boxid'] = gpc_get_int($_POST, 'boxid');
|
|
$p[':invcond'] = 'good';
|
|
$id = db_exec_insert('inventory', $p);
|
|
$action = ACT_VIEW;
|
|
break;
|
|
|
|
case 'update':
|
|
$p[':invid'] = $id;
|
|
$p[':invname'] = gpc_get_string($_POST, 'invname');
|
|
$p[':number'] = gpc_get_int($_POST, 'number');
|
|
$p[':conttype'] = gpc_get_string($_POST, 'conttype');
|
|
$p[':sid'] = gpc_get_int($_POST, 'sid');
|
|
$p[':boxid'] = gpc_get_int($_POST, 'boxid');
|
|
$p[':weight'] = min(gpc_get_float($_POST, 'weight'), 999.99); // limit max value
|
|
$p[':price'] = gpc_get_currency($_POST, 'price');
|
|
$p[':purchdate'] = gpc_get_date($_POST, 'purchdate');
|
|
$p[':invcond'] = gpc_get_string($_POST, 'invcond');
|
|
$p[':remarks'] = gpc_get_string($_POST, 'remarks', 150);
|
|
$p[':eid'] = gpc_get_int($_POST, 'eid');
|
|
db_exec_update('inventory', $p, 'invid');
|
|
$action = ACT_VIEW;
|
|
break;
|
|
|
|
case 'delete':
|
|
// Security token needed!
|
|
if (gpc_get_string($_POST, 'token', 16) != $_SESSION['token']) {
|
|
$g_error->Add(_('Delete prohibited, invalid security token!'));
|
|
$action = ACT_VIEW;
|
|
break;
|
|
}
|
|
unset($_SESSION['token']);
|
|
$sth = $pdo->prepare("DELETE FROM inventory WHERE invid=?");
|
|
$sth->execute([$id]);
|
|
$g_message->Add(sprintf(_('Deleted inventory no. %d'), $id));
|
|
$action = ACT_DEFAULT;
|
|
break;
|
|
|
|
default:
|
|
$g_error->Add(sprintf(_('Unknown function!'), $submit));
|
|
$valid = FALSE;
|
|
|
|
}
|
|
|
|
// ========== ACTIONS END =====================================================
|
|
|
|
require 'header.php';
|
|
|
|
// ========== PAGE CONTENT ====================================================
|
|
|
|
if ($action == ACT_DEFAULT):
|
|
// ========== VARIANT: default behavior =======================================
|
|
|
|
page_caption_search($pagetitle);
|
|
|
|
$flt = db_get_filter($user->id, 201);
|
|
|
|
/*
|
|
The only way to find out whether the inventory is on the current boat is to use
|
|
the box assignment. But this is also intentional to allow easy rearrangement.
|
|
*/
|
|
|
|
$w = array('vid=:vid');
|
|
$p = array(':vid' => $user->vid);
|
|
if ($flt->stor == -1) {
|
|
$w[] = 'i.sid IS NULL';
|
|
} elseif ($flt->stor > 0) {
|
|
$w[] = 'i.sid=:sid';
|
|
$p[':sid'] = $flt->stor;
|
|
}
|
|
if (strlen($flt->cond) > 2 ) {
|
|
$w[] = 'invcond=:invcond';
|
|
$p[':invcond'] = $flt->cond;
|
|
}
|
|
if (strlen($flt->txt) > 1) {
|
|
$w[] = 'invname LIKE :txt';
|
|
$p[':txt'] = '%'.$flt->txt.'%';
|
|
}
|
|
$where = join(' AND ', $w);
|
|
$order = ' ORDER BY invname';
|
|
|
|
$sql = "SELECT "
|
|
. "(SELECT COUNT(*) FROM inventory AS i JOIN storage AS s ON (i.conttype='storage' AND i.sid=s.sid) WHERE $where)"
|
|
. "+"
|
|
. "(SELECT COUNT(*) FROM inventory AS i JOIN box AS b ON (i.conttype='box' AND i.boxid=b.boxid) JOIN storage AS s ON (b.sid=s.sid) WHERE $where)";
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute($p);
|
|
} catch(PDOException $e) {
|
|
$g_error->Add($e->getMessage());
|
|
$g_error->Add($sql);
|
|
$g_error->Add(print_r($p, true));
|
|
$g_error->PrintOut();
|
|
}
|
|
$numrows = $sth->fetchColumn();
|
|
$page = gpc_get_int($_REQUEST, 'p', 1);
|
|
$rows_pp = gpc_get_int($_REQUEST, 'n', $g_rows_pp);
|
|
$lastpage = ceil($numrows/$rows_pp);
|
|
|
|
$sql = "SELECT i.invid, i.invname, i.number, i.invcond, i.eid, s.sname AS container "
|
|
. "FROM inventory AS i JOIN storage AS s ON (i.conttype='storage' AND i.sid=s.sid)";
|
|
$sql .= ' WHERE ' . $where;
|
|
$sql .= " UNION SELECT i.invid, i.invname, i.number, i.invcond, i.eid, b.label AS container "
|
|
. "FROM inventory AS i JOIN box AS b ON (i.conttype='box' AND i.boxid=b.boxid) "
|
|
. " JOIN storage AS s ON (b.sid=s.sid) ";
|
|
$sql .= ' WHERE ' . $where;
|
|
$sql .= $order;
|
|
|
|
// if pagination:
|
|
$sql .= ' LIMIT ' . ($page - 1) * $g_rows_pp . ',' . $g_rows_pp;
|
|
|
|
$sth = $pdo->prepare($sql);
|
|
try {
|
|
$sth->execute($p);
|
|
} catch(PDOException $e) {
|
|
$g_error->Add($e->getMessage());
|
|
$g_error->Add($sql);
|
|
$g_error->Add(print_r($p, true));
|
|
$g_error->PrintOut();
|
|
}
|
|
$res = $sth->fetchAll();
|
|
|
|
// Filter
|
|
$opt_special = array(
|
|
-2 => _('― all ―'),
|
|
-1 => _('― none ―')
|
|
);
|
|
?>
|
|
<form method="post" action="<?=$g_scriptname?>">
|
|
<div id="filter" class="card">
|
|
<div class="card-header"><i class="bi bi-funnel me-2"></i>Filter</div>
|
|
<div class="card-body d-flex flex-wrap gap-3">
|
|
<?php
|
|
// page specific filter start
|
|
// - selection for storage
|
|
// - selection for box
|
|
// page specific filter end
|
|
?>
|
|
<div class="d-flex flex-nowrap gap-2">
|
|
<label for="flt_stor"><?=_('Storage')?></label>
|
|
<select id="flt_stor" name="flt_stor">
|
|
<?php
|
|
foreach ($opt_special + $opt_storage as $k => $v) {
|
|
echo '<option value="', $k,'"';
|
|
if ($k == $flt->stor) {
|
|
echo ' selected';
|
|
}
|
|
echo '>', $v, '</option>';
|
|
}
|
|
?>
|
|
</select>
|
|
</div>
|
|
<div class="d-flex flex-nowrap gap-2">
|
|
<label for="flt_cond"><?=_('Condition')?></label>
|
|
<select id="flt_cond" name="flt_cond">
|
|
<?php
|
|
$opt_special = array(
|
|
-2 => _('― all ―'),
|
|
);
|
|
foreach ($opt_special + $opt_invcond as $k => $v) {
|
|
echo '<option value="', $k,'"';
|
|
if ($k == $flt->cond) {
|
|
echo ' selected';
|
|
}
|
|
echo '>', $v, '</option>';
|
|
}
|
|
?>
|
|
</select>
|
|
</div>
|
|
<div class="d-flex flex-nowrap gap-2">
|
|
<label for="flt_txt">Text</label>
|
|
<input type="text" name="flt_txt" size="15" maxlength="30" value="<?=$flt->txt ?>">
|
|
</div>
|
|
</div>
|
|
<div class="card-footer">
|
|
<button name="submit[filter]" class="btn btn-sm btn-primary" title="<?=_('Apply')?>"><?=_('Go')?></button>
|
|
<button name="submit[freset]" class="btn btn-sm btn-secondary float-end" title="<?=_('Reset filter to defaults')?>"><?=_('Clear')?></button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<?php
|
|
// Pagination on top
|
|
$pagination = get_pagination($g_scriptname, '', $page, $lastpage);
|
|
echo $pagination;
|
|
|
|
echo '<table class="table table-striped">', "\n";
|
|
echo "<thead>\n";
|
|
echo "<tr>\n";
|
|
echo '<th>#</th>';
|
|
echo "<th>", _('Name'), "</th>\n";
|
|
echo "<th>", _('Container'), "</th>\n";
|
|
echo "<th>", _('Number'), "</th>\n";
|
|
echo "<th>", _('Condition'), "</th>\n";
|
|
echo "<th>", _('Tags'), "</th>\n";
|
|
echo "<td></td>\n";
|
|
echo "</tr>\n";
|
|
echo "</thead>\n";
|
|
$i = ($page - 1) * $rows_pp;
|
|
$i0 = $i + 1;
|
|
foreach ($res as $row) {
|
|
$i++; // record number
|
|
echo "<tr>\n";
|
|
echo '<td>', $i;
|
|
echo '<a class="text-nowrap" title="', _('View'), '" href="', $g_scriptname, '?f=view&id=', $row['invid'], '"><i class="bi bi-eye ms-2"></i></a>', "\n";
|
|
echo "</td>\n";
|
|
// alternative symbols: wrench, wrench-adjustable, tools
|
|
echo '<td>', $row['invname'];
|
|
echo isset($row['eid']) && $row['eid'] > 0 ? ' <i class="bi bi-wrench"></i>' : '', "</td>\n";
|
|
echo "<td>", $row['container'], "</td>\n";
|
|
echo "<td>", $row['number'], "</td>\n";
|
|
echo "<td>", ($opt_invcond[$row['invcond']] ?? ''), "</td>\n";
|
|
echo "<td>", _('No tags assigned'), "</td>\n";
|
|
// Edit current record button
|
|
echo "<td>";
|
|
echo '<a title="', _('Edit'), '" href="', $g_scriptname, '?f=edit&id=', $row['invid'], '"><i class="bi-pencil"></i></a>', "\n";
|
|
echo "</td>\n";
|
|
echo "</tr>\n";
|
|
}
|
|
// Table summary
|
|
echo "<tfoot>\n";
|
|
echo '<tr><td colspan="7">', sprintf(_('Records %d to %d of %d'), $i0, $i, $numrows), '</td></tr>', "\n";
|
|
echo "</tfoot>\n";
|
|
echo "</table>\n";
|
|
|
|
echo $pagination;
|
|
|
|
form_add_button($g_scriptname);
|
|
|
|
elseif ($action == ACT_ADD):
|
|
// ========== VARIANT: add record =============================================
|
|
|
|
echo '<h2>', _('Add Inventory'), "</h2>\n";
|
|
?>
|
|
<form method="post" action="<?=$g_scriptname?>">
|
|
<div class="mb-3">
|
|
<label for="invname" class="form-label"><?=_('Name')?></label>
|
|
<input type="text" class="form-control" id="invname" name="invname">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="number" class="form-label"><?=_('Number')?></label>
|
|
<input type="number" class="form-control" id="number" name="number" value="1">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="conttype" class="form-label"><?=_('Container type')?></label>
|
|
<select class="form-select" name="conttype" id="conttype">
|
|
<option value="box"><?=_('Box')?></option>
|
|
<option value="storage"><?=_('Storage')?></option>
|
|
</select>
|
|
</div>
|
|
<?php
|
|
form_create_select('sid', _('Storage'), $g_opt_none + $opt_storage);
|
|
form_create_select('boxid', _('Box'), $g_opt_none + $opt_box);
|
|
?>
|
|
<div class="container-fluid px-0 my-3">
|
|
<button type="submit" name="submit[insert]" class="btn btn-primary"><?=_('Save')?></button>
|
|
<a href="<?=$g_scriptname?>" class="btn btn-secondary"><?=_('Back')?></a>
|
|
</div>
|
|
</form>
|
|
<?php
|
|
|
|
elseif ($action == ACT_VIEW):
|
|
// ========== VARIANT: view single record =====================================
|
|
|
|
$sql = "SELECT invname, conttype, sid, boxid, number, weight, price, purchdate,"
|
|
. " invcond, remarks "
|
|
. "FROM inventory "
|
|
. "WHERE invid=?";
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$id]);
|
|
$inventory = $sth->fetch(PDO::FETCH_OBJ);
|
|
|
|
// container type can be storage or box
|
|
switch ($inventory->conttype) {
|
|
case 'storage':
|
|
$sql = "SELECT sname, vid FROM storage WHERE sid=?";
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$inventory->sid]);
|
|
$row = $sth->fetch();
|
|
$storagename = $row['sname'];
|
|
$vid = $row['vid'];
|
|
break;
|
|
case 'box':
|
|
// Future: Boxes can also be nested!
|
|
// Recursive Function "get_storage_for_box()"
|
|
$sql = "SELECT sid, label FROM box WHERE boxid=?";
|
|
// TODO Load additional box data
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$inventory->boxid]);
|
|
$box = $sth->fetch();
|
|
$sql = "SELECT sname, vid FROM storage WHERE sid=?";
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$box['sid']]);
|
|
$row = $sth->fetch();
|
|
$storagename = $row['sname'];
|
|
$vid = $row['vid'];
|
|
break;
|
|
}
|
|
|
|
echo '<h2>', $inventory->invname, "</h2>\n";
|
|
|
|
echo '<table class="table">', "\n";
|
|
echo '<tr><th style="width:20%">', _('Box type'),"</th><td>", $inventory->conttype, "</td></tr>\n";
|
|
|
|
// Container with link
|
|
echo "<tr><th>", _('Container'),"</th><td>", sprintf(_('%s in %s'), $box['label'], $storagename);
|
|
echo "</td></tr>\n";
|
|
|
|
echo "<tr><th>", _('Location ID'), "</th><td>", $inventory->locationid, "</td></tr>\n";
|
|
echo "<tr><th>", _('Number'), "</th><td>", $inventory->number, "</td></tr>\n";
|
|
echo "<tr><th>", _('Weight'), "</th><td>", format_float($inventory->weight, 2, 'kg'), "</td></tr>\n";
|
|
echo "<tr><th>", _('Price'), "</th><td>", format_currency($inventory->price), "</td></tr>\n";
|
|
echo "<tr><th>", _('Purchase date'), "</th><td>", $inventory->purchdate, "</td></tr>\n";
|
|
echo "<tr><th>", _('Condition'), "</th><td>", $opt_invcond[$inventory->invcond], "</td></tr>\n";
|
|
echo "<tr><th>", _('Remarks'), "</th><td>", nl2br($inventory->remarks), "</td></tr>\n";
|
|
|
|
$tags = db_load_taglist('inv', $id);
|
|
form_tag_assignment($g_scriptname, $id, $tags);
|
|
|
|
echo "</table>\n";
|
|
|
|
form_view_buttons($g_scriptname, $id);
|
|
|
|
elseif ($action == ACT_EDIT):
|
|
// ========== VARIANT: edit single record =====================================
|
|
|
|
$sql = "SELECT invname, conttype, sid, boxid, number, weight, price, purchdate, "
|
|
. " invcond, eid, remarks "
|
|
. "FROM inventory "
|
|
. "WHERE invid=?";
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$id]);
|
|
$inventory = $sth->fetch(PDO::FETCH_OBJ);
|
|
|
|
?>
|
|
<h2><?=_('Edit Inventory')?></h2>
|
|
<form method="post" action="<?=$g_scriptname?>">
|
|
<input type="hidden" name="id" value="<?=$id?>">
|
|
<div class="mb-3">
|
|
<label for="invname" class="form-label"><?=_('Name')?></label>
|
|
<input type="text" class="form-control" id="invname" name="invname" value="<?=$inventory->invname ?>">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="number" class="form-label"><?=_('Number')?></label>
|
|
<input type="number" class="form-control" id="number" name="number" value="<?=$inventory->number ?>">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="weight" class="form-label"><?=_('Weight, kg')?></label>
|
|
<input type="text" class="form-control" id="weight" name="weight" value="<?=format_float($inventory->weight); ?>">
|
|
</div>
|
|
<?php
|
|
$opt_conttype = db_load_enum('inventory', 'conttype', true, true);
|
|
form_create_select('conttype', _('Container type'), $opt_conttype, $inventory->conttype);
|
|
form_create_select('sid', _('Storage'), $g_opt_none + $opt_storage, $inventory->sid);
|
|
form_create_select('boxid', _('Box'), $g_opt_none + $opt_box, $inventory->boxid);
|
|
?>
|
|
<div class="mb-3">
|
|
<label for="price" class="form-label"><?=sprintf(_('Price, %s'), $g_lconv['currency_symbol']); ?></label>
|
|
<input type="text" class="form-control" id="price" name="price" value="<?=format_float($inventory->price) ?>">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="purchdate"><?=_('Purchase date')?></label>
|
|
<input type="date" class="form-control" id="purchdate" name="purchdate" value="<?=$inventory->purchdate ?>">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="invcond" class="form-label"><?=_('Condition')?></label>
|
|
<select name="invcond" id="invcond" class="form-control">
|
|
<?php
|
|
$cond = db_load_enum('inventory', 'invcond', true, true);
|
|
foreach ($cond as $k => $v) {
|
|
echo '<option value="', $k, '"';
|
|
if ($k == $inventory->invcond) {
|
|
echo ' selected';
|
|
}
|
|
echo '>', $v. "</option>\n";
|
|
}
|
|
?>
|
|
</select>
|
|
</div>
|
|
<?php form_create_select('eid', _('Equipment'), $opt_equipment, $inventory->eid); ?>
|
|
<div class="mb-3">
|
|
<label for="remarks" class="form-label"><?=_('Remarks')?></label>
|
|
<textarea class="form-control" id="remarks" name="remarks" rows="3"><?=$inventory->remarks ?></textarea>
|
|
</div>
|
|
|
|
<?php
|
|
|
|
form_edit_buttons($g_scriptname, $id);
|
|
echo "</form>\n";
|
|
|
|
elseif ($action == ACT_DELETE):
|
|
// ========== VARIANT: delete record ==========================================
|
|
|
|
$sql = "SELECT invname FROM inventory WHERE invid=?";
|
|
$sth = $pdo->prepare($sql);
|
|
$sth->execute([$id]);
|
|
$inventory = $sth->fetch(PDO::FETCH_OBJ);
|
|
|
|
echo '<h2>', _('Delete Inventory'), "</h2>\n";
|
|
echo '<p>', sprintf(_('Record no. %d'), $id), "</p>\n";
|
|
echo '<p>Name: ', $inventory->invname, "</p>";
|
|
|
|
echo '<p>', _('Deleting an inventory item is final. There is no way back. Only delete if you are absolute sure.'), "</p>\n";
|
|
$_SESSION['token'] = bin2hex(random_bytes(8));
|
|
form_delete_buttons($g_scriptname, $id, $_SESSION['token']);
|
|
|
|
else:
|
|
// ========== ERROR UNKNOWN VARIANT ===========================================
|
|
|
|
echo '<p>', _('Unknown function call: Please report to system development!'), "</p>\n";
|
|
|
|
endif; // $action == ...
|
|
// ========== END OF VARIANTS =================================================
|
|
|
|
include 'footer.php';
|