summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLester Caine <lester@lsces.co.uk>2026-06-09 16:48:25 +0100
committerLester Caine <lester@lsces.co.uk>2026-06-09 16:48:25 +0100
commite792a1a73506f2c09b60528418a36f2c3035f399 (patch)
tree3b052d4b67d8db45cdacb69e0462c395cdc7ce7b
parent3ed604661c5e10dce6e14d73f0d78b80d9dca72b (diff)
downloadstock-e792a1a73506f2c09b60528418a36f2c3035f399.tar.gz
stock-e792a1a73506f2c09b60528418a36f2c3035f399.tar.bz2
stock-e792a1a73506f2c09b60528418a36f2c3035f399.zip
stock: shortages CSV/order workflow; list header layout
- list_stock: CSV export and Create Order from shortages report; floaticon icons for print/CSV/order; filter form moved into header - add_order: new page — pre-populate draft ORDER movement from shortages list, editable qty/delete per line, supplier autocomplete - list_movements: print icon; filter form moved into floaticon header - list_assemblies_simple: print icon added to floaticon Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--add_order.php249
-rw-r--r--list_stock.php18
-rw-r--r--templates/add_order.tpl163
-rw-r--r--templates/list_assemblies_simple.tpl1
-rw-r--r--templates/list_movements.tpl40
-rw-r--r--templates/list_stock.tpl26
6 files changed, 466 insertions, 31 deletions
diff --git a/add_order.php b/add_order.php
new file mode 100644
index 0000000..60b4eb3
--- /dev/null
+++ b/add_order.php
@@ -0,0 +1,249 @@
+<?php
+/**
+ * @package stock
+ * @subpackage functions
+ */
+
+namespace Bitweaver\Stock;
+
+use Bitweaver\KernelTools;
+
+require_once '../kernel/includes/setup_inc.php';
+
+global $gBitSystem, $gBitSmarty, $gBitUser, $gBitDb;
+
+$gBitSystem->verifyPermission( 'p_stock_create' );
+
+if( !empty( $_REQUEST['fCancel'] ) ) {
+ header( 'Location: '.STOCK_PKG_URL.'list_stock.php?shortages=1' );
+ die;
+}
+
+$errors = [];
+$fromPost = false;
+
+if( !empty( $_POST['fCreate'] ) ) {
+ $fromPost = true;
+ $refKey = trim( $_POST['ref_key'] ?? '' );
+ $supplierCid = isset( $_POST['supplier_contact_id'] ) && is_numeric( $_POST['supplier_contact_id'] )
+ ? (int)$_POST['supplier_contact_id'] : 0;
+ $componentIds = array_map( 'intval', (array)( $_POST['component_id'] ?? [] ) );
+ $qtyTypesPost = (array)( $_POST['qty_type'] ?? [] );
+ $qtysPost = (array)( $_POST['qty'] ?? [] );
+ $orderedDatePost = trim( $_POST['ordered_date'] ?? '' );
+
+ if( $refKey === '' ) {
+ $errors[] = KernelTools::tra( 'Please enter an order reference.' );
+ } elseif( !array_filter( $componentIds ) ) {
+ $errors[] = KernelTools::tra( 'No lines to order.' );
+ } else {
+ $movement = new StockMovement();
+ $paramHash = [
+ 'title' => $refKey,
+ 'content_type_guid' => STOCKMOVEMENT_CONTENT_TYPE_GUID,
+ ];
+ if( $movement->store( $paramHash ) ) {
+ $refHash = [
+ 'content_id' => $movement->mContentId,
+ 'item' => 'ORDER',
+ 'xkey' => $refKey,
+ 'fAddXref' => 1,
+ ];
+ if( $supplierCid ) {
+ $refHash['xref'] = $supplierCid;
+ }
+ if( $orderedDatePost !== '' ) {
+ $parts = explode( '/', $orderedDatePost );
+ if( count( $parts ) === 3 ) {
+ $year = (int)$parts[2] < 100 ? 2000 + (int)$parts[2] : (int)$parts[2];
+ $ts = (int)mktime( 0, 0, 0, (int)$parts[1], (int)$parts[0], $year );
+ if( $ts ) $refHash['start_date'] = $ts;
+ }
+ }
+ $movement->storeXref( $refHash );
+
+ $xorder = 1;
+ foreach( $componentIds as $i => $cid ) {
+ if( !$cid ) continue;
+ $qty = (float)( $qtysPost[$i] ?? 0 );
+ if( $qty <= 0 ) continue;
+ $qtype = in_array( $qtyTypesPost[$i] ?? '', [ 'SGL', 'PCK', 'SHT', 'VOL' ] )
+ ? $qtyTypesPost[$i] : 'SGL';
+ $lineHash = [
+ 'content_id' => $movement->mContentId,
+ 'item' => $qtype,
+ 'xref' => $cid,
+ 'xkey' => (string)$qty,
+ 'xorder' => $xorder++,
+ 'fAddXref' => 1,
+ ];
+ $movement->storeXref( $lineHash );
+ }
+ header( 'Location: '.STOCK_PKG_URL.'edit_movement.php?content_id='.$movement->mContentId );
+ die;
+ }
+ $errors = array_merge( $errors, $movement->mErrors );
+ }
+}
+
+$lines = [];
+
+if( $fromPost ) {
+ $cids = array_map( 'intval', (array)( $_POST['component_id'] ?? [] ) );
+ $types = (array)( $_POST['qty_type'] ?? [] );
+ $qs = (array)( $_POST['qty'] ?? [] );
+ $meta = [];
+ if( $cids ) {
+ $metaRows = $gBitDb->getAll(
+ "SELECT lc.`content_id`, lc.`title`,
+ (SELECT FIRST 1 sup.`xkey` FROM `".BIT_DB_PREFIX."liberty_xref` sup
+ WHERE sup.`content_id` = lc.`content_id` AND sup.`item` = '#SUP'
+ ORDER BY sup.`xorder`) AS part_number
+ FROM `".BIT_DB_PREFIX."liberty_content` lc
+ WHERE lc.`content_id` IN (".implode( ',', array_fill( 0, count( $cids ), '?' ) ).")",
+ $cids
+ );
+ foreach( $metaRows as $r ) {
+ $meta[$r['content_id']] = [ 'title' => $r['title'], 'part_number' => $r['part_number'] ];
+ }
+ }
+ foreach( $cids as $i => $cid ) {
+ if( !$cid ) continue;
+ $qtype = in_array( $types[$i] ?? '', [ 'SGL', 'PCK', 'SHT', 'VOL' ] ) ? $types[$i] : 'SGL';
+ $lines[] = [
+ 'component_id' => $cid,
+ 'title' => $meta[$cid]['title'] ?? '',
+ 'part_number' => $meta[$cid]['part_number'] ?? '',
+ 'qty_type' => $qtype,
+ 'qty' => (float)( $qs[$i] ?? 0 ),
+ ];
+ }
+ $supplierVal = trim( $_POST['ref_from'] ?? '' );
+ $supplierCidVal = (int)( $_POST['supplier_contact_id'] ?? 0 );
+ $refKeyVal = trim( $_POST['ref_key'] ?? '' );
+ $orderedDateVal = trim( $_POST['ordered_date'] ?? date( 'd/m/Y' ) );
+} else {
+ $assemblyContentId = isset( $_REQUEST['assembly_content_id'] ) && is_numeric( $_REQUEST['assembly_content_id'] )
+ ? (int)$_REQUEST['assembly_content_id'] : null;
+ $kitCount = isset( $_REQUEST['kit_count'] ) && is_numeric( $_REQUEST['kit_count'] ) && (float)$_REQUEST['kit_count'] > 0
+ ? (float)$_REQUEST['kit_count'] : 1;
+ $find = trim( $_REQUEST['find'] ?? '' );
+
+ $X = BIT_DB_PREFIX;
+ $bindVars = [];
+
+ if( $assemblyContentId ) {
+ $findSql = $find !== '' ? " AND UPPER(lc.`title`) LIKE ?" : '';
+ if( $find !== '' ) $bindVars[] = '%'.strtoupper( $find ).'%';
+
+ $query = "SELECT lc.`content_id`, lc.`title`,
+ bom.`item` AS qty_type,
+ CAST(bom.`xkey` AS DOUBLE PRECISION) AS bom_qty,
+ (SELECT FIRST 1 sup.`xkey`
+ FROM `{$X}liberty_xref` sup
+ WHERE sup.`content_id` = lc.`content_id` AND sup.`item` = '#SUP'
+ ORDER BY sup.`xorder`) AS part_number,
+ (SELECT FIRST 1 CAST(pk.`xkey` AS DOUBLE PRECISION)
+ FROM `{$X}liberty_xref` pk
+ WHERE pk.`content_id` = lc.`content_id` AND pk.`item` = 'PCK') AS pack_size,
+ (SELECT SUM( CASE WHEN EXISTS (
+ SELECT 1 FROM `{$X}liberty_xref` r
+ WHERE r.`content_id` = mx.`content_id` AND r.`item` IN ('TRANS','ORDER')
+ ) THEN CAST(mx.`xkey` AS DOUBLE PRECISION)
+ ELSE -CAST(mx.`xkey` AS DOUBLE PRECISION) END )
+ FROM `{$X}liberty_xref` mx
+ INNER JOIN `{$X}liberty_content` mc ON mc.`content_id` = mx.`content_id`
+ AND mc.`content_type_guid` = 'stockmovement'
+ WHERE mx.`xref` = lc.`content_id`
+ AND mx.`item` = bom.`item`
+ AND mx.`xkey` SIMILAR TO '[0-9]+(\.[0-9]+)?') AS stock_level
+ FROM `{$X}liberty_content` lc
+ INNER JOIN `{$X}liberty_xref` bom ON bom.`content_id` = ?
+ AND bom.`item` IN ('SGL','PCK','SHT','VOL')
+ AND bom.`xref` = lc.`content_id`
+ WHERE lc.`content_type_guid` = 'stockcomponent'
+ $findSql
+ ORDER BY bom.`xorder`";
+ $bindVars[] = $assemblyContentId;
+ } else {
+ $whereSql = '';
+ if( $find !== '' ) {
+ $whereSql = " AND UPPER(lc.`title`) LIKE ?";
+ $bindVars[] = '%'.strtoupper( $find ).'%';
+ }
+ $query = "SELECT lc.`content_id`, lc.`title`,
+ x.`item` AS qty_type,
+ CAST(NULL AS DOUBLE PRECISION) AS bom_qty,
+ (SELECT FIRST 1 sup.`xkey`
+ FROM `{$X}liberty_xref` sup
+ WHERE sup.`content_id` = lc.`content_id` AND sup.`item` = '#SUP'
+ ORDER BY sup.`xorder`) AS part_number,
+ (SELECT FIRST 1 CAST(pk.`xkey` AS DOUBLE PRECISION)
+ FROM `{$X}liberty_xref` pk
+ WHERE pk.`content_id` = lc.`content_id` AND pk.`item` = 'PCK') AS pack_size,
+ SUM( CASE WHEN EXISTS (
+ SELECT 1 FROM `{$X}liberty_xref` r
+ WHERE r.`content_id` = x.`content_id` AND r.`item` IN ('TRANS','ORDER')
+ ) THEN CAST(x.`xkey` AS DOUBLE PRECISION)
+ ELSE -CAST(x.`xkey` AS DOUBLE PRECISION) END ) AS stock_level
+ FROM `{$X}liberty_content` lc
+ INNER JOIN `{$X}liberty_xref` x ON x.`xref` = lc.`content_id`
+ AND x.`item` IN ('SGL','PCK','SHT','VOL')
+ AND x.`xkey` SIMILAR TO '[0-9]+(\.[0-9]+)?'
+ INNER JOIN `{$X}liberty_content` mc ON mc.`content_id` = x.`content_id`
+ AND mc.`content_type_guid` = 'stockmovement'
+ WHERE lc.`content_type_guid` = 'stockcomponent'
+ $whereSql
+ GROUP BY lc.`content_id`, lc.`title`, x.`item`
+ ORDER BY lc.`title`, x.`item`";
+ }
+
+ $rows = $gBitDb->query( $query, $bindVars );
+ $stockList = [];
+ foreach( $rows as $row ) {
+ $cid = $row['content_id'];
+ $level = $row['stock_level'] !== null ? (float)$row['stock_level'] : 0.0;
+ if( !isset( $stockList[$cid] ) ) {
+ $stockList[$cid] = [
+ 'content_id' => $cid,
+ 'title' => $row['title'],
+ 'part_number' => $row['part_number'],
+ ];
+ }
+ $stockList[$cid]['stock'][$row['qty_type']] = [
+ 'level' => $level,
+ 'bom_qty' => $row['bom_qty'] !== null ? (float)$row['bom_qty'] : null,
+ ];
+ }
+
+ foreach( $stockList as $comp ) {
+ foreach( $comp['stock'] as $qtype => $row ) {
+ $shortage = $assemblyContentId && $row['bom_qty'] !== null
+ ? $row['bom_qty'] * $kitCount - $row['level']
+ : -$row['level'];
+ if( $shortage <= 0 ) continue;
+ $lines[] = [
+ 'component_id' => $comp['content_id'],
+ 'title' => $comp['title'],
+ 'part_number' => $comp['part_number'],
+ 'qty_type' => $qtype,
+ 'qty' => $shortage,
+ ];
+ }
+ }
+
+ $supplierVal = '';
+ $supplierCidVal = 0;
+ $refKeyVal = '';
+ $orderedDateVal = date( 'd/m/Y' );
+}
+
+$gBitSmarty->assign( 'lines', $lines );
+$gBitSmarty->assign( 'supplierVal', $supplierVal );
+$gBitSmarty->assign( 'supplierCidVal', $supplierCidVal );
+$gBitSmarty->assign( 'refKeyVal', $refKeyVal );
+$gBitSmarty->assign( 'orderedDateVal', $orderedDateVal );
+$gBitSmarty->assign( 'contactLookupUrl', CONTACT_PKG_URL.'includes/lookup_contact.php' );
+$gBitSmarty->assign( 'errors', $errors );
+
+$gBitSystem->display( 'bitpackage:stock/add_order.tpl', KernelTools::tra( 'Create Order' ), [ 'display_mode' => 'edit' ] );
diff --git a/list_stock.php b/list_stock.php
index 4589334..75be18e 100644
--- a/list_stock.php
+++ b/list_stock.php
@@ -161,6 +161,24 @@ $assemblyListJson = json_encode( array_values( array_map(
$assemblyList
) ) );
+if( $showShortages && isset( $_REQUEST['format'] ) && $_REQUEST['format'] === 'csv' ) {
+ header( 'Content-Type: text/csv; charset=utf-8' );
+ header( 'Content-Disposition: attachment; filename="shortages.csv"' );
+ $out = fopen( 'php://output', 'w' );
+ fputcsv( $out, [ 'Part No', 'Qty' ], ',', '"', '' );
+ foreach( $stockList as $comp ) {
+ if( empty( $comp['part_number'] ) ) continue;
+ foreach( $comp['stock'] as $qtype => $row ) {
+ $qty = $qtype === 'PCK' && $row['pack_size'] > 0
+ ? abs( $row['level'] ) / $row['pack_size']
+ : abs( $row['level'] );
+ fputcsv( $out, [ $comp['part_number'], $qty ], ',', '"', '' );
+ }
+ }
+ fclose( $out );
+ exit;
+}
+
$gBitSmarty->assign( 'stockList', $stockList );
$gBitSmarty->assign( 'assemblyListJson', $assemblyListJson );
$gBitSmarty->assign( 'assemblyContentId', $assemblyContentId );
diff --git a/templates/add_order.tpl b/templates/add_order.tpl
new file mode 100644
index 0000000..db5aab8
--- /dev/null
+++ b/templates/add_order.tpl
@@ -0,0 +1,163 @@
+{strip}
+<div class="listing stock">
+ <header>
+ <h1>{tr}Create Order{/tr}</h1>
+ </header>
+
+ <section class="body">
+
+ {if $errors}
+ <div class="alert alert-danger">
+ {foreach $errors as $e}<p>{$e|escape}</p>{/foreach}
+ </div>
+ {/if}
+
+ {form ipackage="stock" ifile="add_order.php" method="post"}
+ <div class="form-horizontal">
+
+ <div class="form-group">
+ {formlabel label="Supplier" for="ref_from"}
+ {forminput}
+ <input type="hidden" name="supplier_contact_id" id="supplier_contact_id"
+ value="{$supplierCidVal|escape}" />
+ <div style="position:relative">
+ <input type="text" class="form-control" name="ref_from" id="ref_from"
+ autocomplete="off" placeholder="{tr}Type to search contacts…{/tr}"
+ value="{$supplierVal|escape}" maxlength="160" />
+ <ul id="contact_dropdown" class="dropdown-menu"
+ style="display:none;position:absolute;width:100%;z-index:1000;max-height:220px;overflow-y:auto"></ul>
+ </div>
+ {/forminput}
+ </div>
+
+ <div class="form-group">
+ {formlabel label="Order Ref" for="ref_key"}
+ {forminput}
+ <input type="text" class="form-control" name="ref_key" id="ref_key"
+ value="{$refKeyVal|escape}" maxlength="160" />
+ {/forminput}
+ </div>
+
+ <div class="form-group">
+ {formlabel label="Order Date" for="ordered_date"}
+ {forminput}
+ <input type="text" class="form-control input-small" name="ordered_date" id="ordered_date"
+ placeholder="dd/mm/yyyy" value="{$orderedDateVal|escape}" maxlength="10" />
+ {/forminput}
+ </div>
+
+ </div>
+
+ {if $lines}
+ <table class="table table-condensed" id="order-lines">
+ <thead>
+ <tr>
+ <th>{tr}Component{/tr}</th>
+ <th>{tr}Part No{/tr}</th>
+ <th>{tr}Type{/tr}</th>
+ <th class="text-right">{tr}Qty{/tr}</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {foreach $lines as $line}
+ <tr>
+ <td>
+ <input type="hidden" name="component_id[]" value="{$line.component_id|escape}" />
+ <input type="hidden" name="qty_type[]" value="{$line.qty_type|escape}" />
+ {$line.title|escape}
+ </td>
+ <td>{$line.part_number|escape}</td>
+ <td>{$line.qty_type|escape}</td>
+ <td class="text-right" style="width:7em">
+ <input type="number" class="form-control input-sm text-right" name="qty[]"
+ value="{$line.qty|string_format:'%g'|escape}" min="0.001" step="any"
+ style="width:6em;display:inline-block" />
+ </td>
+ <td style="width:2em">
+ <button type="button" class="btn btn-link btn-xs bw-del-row"
+ title="{tr}Remove line{/tr}">&#x2715;</button>
+ </td>
+ </tr>
+ {/foreach}
+ </tbody>
+ </table>
+ {else}
+ <p class="muted">{tr}No shortage lines found.{/tr}</p>
+ {/if}
+
+ <div class="form-group">
+ <button type="submit" name="fCreate" value="1" class="btn btn-primary">{tr}Create Order{/tr}</button>
+ <button type="submit" name="fCancel" value="1" class="btn btn-default">{tr}Cancel{/tr}</button>
+ </div>
+
+ {/form}
+
+ </section>
+</div>
+{/strip}
+<script>
+(function($) {
+ $(document).on('click', '.bw-del-row', function() {
+ $(this).closest('tr').remove();
+ });
+
+ var timer, contacts = [];
+ var $input = $('#ref_from');
+ var $hidden = $('#supplier_contact_id');
+ var $dd = $('#contact_dropdown');
+
+ $input.on('input', function() {
+ var q = $(this).val();
+ clearTimeout(timer);
+ $dd.hide().empty();
+ contacts = [];
+ if (q.length < 2) return;
+ timer = setTimeout(function() {
+ $.getJSON('{$contactLookupUrl}', {ldelim}q: q{rdelim}, function(data) {
+ contacts = data;
+ if (!data.length) return;
+ $.each(data, function(i, row) {
+ var label = row.title + (row.scref ? ' (' + row.scref + ')' : '');
+ $dd.append($('<li>').append(
+ $('<a>').attr('href','#').data('id', row.content_id).data('label', label).text(label)
+ ));
+ });
+ $dd.show();
+ });
+ }, 250);
+ });
+
+ $(document).on('mousedown', '#contact_dropdown a', function(e) {
+ e.preventDefault();
+ $input.val($(this).data('label'));
+ $hidden.val($(this).data('id'));
+ $dd.hide().empty();
+ contacts = [];
+ });
+
+ $input.on('blur', function() {
+ setTimeout(function() { $dd.hide(); }, 150);
+ });
+
+ $input.on('keydown', function(e) {
+ if (!$dd.is(':visible')) return;
+ var $items = $dd.find('a');
+ var idx = $dd.find('li.active a').length ? $items.index($dd.find('li.active a')) : -1;
+ if (e.key === 'ArrowDown') {
+ e.preventDefault();
+ $items.parent().removeClass('active');
+ $items.eq(idx + 1 < $items.length ? idx + 1 : 0).parent().addClass('active');
+ } else if (e.key === 'ArrowUp') {
+ e.preventDefault();
+ $items.parent().removeClass('active');
+ $items.eq(idx > 0 ? idx - 1 : $items.length - 1).parent().addClass('active');
+ } else if (e.key === 'Enter') {
+ var $a = $dd.find('li.active a');
+ if ($a.length) { e.preventDefault(); $a.trigger('mousedown'); }
+ } else if (e.key === 'Escape') {
+ $dd.hide();
+ }
+ });
+}(jQuery));
+</script>
diff --git a/templates/list_assemblies_simple.tpl b/templates/list_assemblies_simple.tpl
index 7d2eddf..4e91b8c 100644
--- a/templates/list_assemblies_simple.tpl
+++ b/templates/list_assemblies_simple.tpl
@@ -2,6 +2,7 @@
<div class="listing stock">
<header>
<div class="floaticon">
+ <button type="button" class="btn btn-link" onclick="window.print()">{biticon ipackage="icons" iname="document-print" iexplain="Print"}</button>
{minifind prompt="Assemblies" gallery_id=$smarty.request.gallery_id}
</div>
<h1>{tr}Assemblies{/tr}{if $gQueryUserId} {tr}by{/tr} {displayname user_id=$gQueryUserId}{/if}</h1>
diff --git a/templates/list_movements.tpl b/templates/list_movements.tpl
index e50979e..fe741ee 100644
--- a/templates/list_movements.tpl
+++ b/templates/list_movements.tpl
@@ -2,10 +2,30 @@
<div class="listing stock">
<header>
<div class="floaticon">
+ <button type="button" class="btn btn-link" onclick="window.print()">{biticon ipackage="icons" iname="document-print" iexplain="Print"}</button>
{if $gBitUser->hasPermission('p_stock_create')}
<a href="{$smarty.const.STOCK_PKG_URL}add_requisition.php">{biticon ipackage="icons" iname="list-add" iexplain="Add Requisition"}</a>
<a href="{$smarty.const.STOCK_PKG_URL}edit_movement.php">{biticon ipackage="icons" iname="view-task-add" iexplain="Add Movement"}</a>
{/if}
+ <form class="minifind" action="{$smarty.const.STOCK_PKG_URL}list_movements.php" method="get">
+ {if $componentContentId}<input type="hidden" name="component_content_id" value="{$componentContentId|escape}" />{/if}
+ <div class="form-inline">
+ <div class="form-group">
+ <select name="ref_type" class="form-control input-sm">
+ <option value="">{tr}All types{/tr}</option>
+ <option value="REQN"{if $filterType eq 'REQN'} selected="selected"{/if}>{tr}Requisition (out){/tr}</option>
+ <option value="TRANS"{if $filterType eq 'TRANS'} selected="selected"{/if}>{tr}Transfer (in){/tr}</option>
+ <option value="ORDER"{if $filterType eq 'ORDER'} selected="selected"{/if}>{tr}Order (in){/tr}</option>
+ </select>
+ </div>
+ <div class="form-group">
+ <input type="text" class="form-control input-sm" name="find"
+ placeholder="{tr}reference...{/tr}"
+ value="{$smarty.request.find|escape}" />
+ </div>
+ <button type="submit" class="btn btn-default btn-sm">{tr}Go{/tr}</button>
+ </div>
+ </form>
</div>
<h1>{tr}Movements{/tr}{if $componentTitle} — {$componentTitle|escape}{/if}</h1>
</header>
@@ -14,28 +34,8 @@
{if $componentContentId}
<p><a class="btn btn-xs btn-default" href="{$smarty.const.STOCK_PKG_URL}view_component.php?content_id={$componentContentId}">&larr; {tr}Back to component{/tr}</a></p>
- <input type="hidden" name="component_content_id" value="{$componentContentId}" />
{/if}
- {form ipackage="stock" ifile="list_movements.php" method="get"}
- <div class="form-inline" style="margin-bottom:1em">
- <div class="form-group">
- <select name="ref_type" class="form-control input-sm">
- <option value="">{tr}All types{/tr}</option>
- <option value="REQN"{if $filterType eq 'REQN'} selected="selected"{/if}>{tr}Requisition (out){/tr}</option>
- <option value="TRANS"{if $filterType eq 'TRANS'} selected="selected"{/if}>{tr}Transfer (in){/tr}</option>
- <option value="ORDER"{if $filterType eq 'ORDER'} selected="selected"{/if}>{tr}Order (in){/tr}</option>
- </select>
- </div>
- <div class="form-group">
- <input type="text" class="form-control input-sm" name="find"
- placeholder="{tr}reference...{/tr}"
- value="{$smarty.request.find|escape}" />
- </div>
- <button type="submit" class="btn btn-default btn-sm">{tr}Go{/tr}</button>
- </div>
- {/form}
-
<table class="table table-striped table-hover">
<thead>
<tr>
diff --git a/templates/list_stock.tpl b/templates/list_stock.tpl
index d70c606..f42f435 100644
--- a/templates/list_stock.tpl
+++ b/templates/list_stock.tpl
@@ -3,14 +3,16 @@
<header>
<div class="floaticon hidden-print">
<button type="button" class="btn btn-link" onclick="window.print()">{biticon ipackage="icons" iname="document-print" iexplain="Print"}</button>
- </div>
- <h1>{tr}Stock Levels{/tr}{if $assemblyTitle} — {$assemblyTitle|escape}{/if}</h1>
- </header>
-
- <section class="body">
-
- <form action="{$smarty.const.STOCK_PKG_URL}list_stock.php" method="get">
- <div class="form-inline" style="margin-bottom:1em">
+ {if $showShortages}
+ <a class="btn btn-link"
+ href="{$smarty.const.STOCK_PKG_URL}list_stock.php?shortages=1{if $assemblyContentId}&amp;assembly_content_id={$assemblyContentId|escape:'url'}&amp;kit_count={$kitCount|escape:'url'}{/if}&amp;format=csv">{biticon ipackage="icons" iname="text-csv" iexplain="Download CSV"}</a>
+ {if $gBitUser->hasPermission('p_stock_create')}
+ <a class="btn btn-link"
+ href="{$smarty.const.STOCK_PKG_URL}add_order.php?shortages=1{if $assemblyContentId}&amp;assembly_content_id={$assemblyContentId|escape:'url'}&amp;kit_count={$kitCount|escape:'url'}{/if}{if $find}&amp;find={$find|escape:'url'}{/if}">{biticon ipackage="icons" iname="view-task-add" iexplain="Create Order"}</a>
+ {/if}
+ {/if}
+ <form class="minifind" action="{$smarty.const.STOCK_PKG_URL}list_stock.php" method="get">
+ <div class="form-inline">
<div class="form-group">
<input type="hidden" name="assembly_content_id" id="ls_asm_id" value="{$assemblyContentId|default:''|escape}" />
<div style="position:relative;display:inline-block;vertical-align:top">
@@ -41,15 +43,17 @@
</label>
</div>
<button type="submit" class="btn btn-default btn-sm">{tr}Go{/tr}</button>
- {if $showShortages}
- <button type="button" class="btn btn-default btn-sm" onclick="window.print()">{tr}Print{/tr}</button>
- {/if}
{if $showBom && $gBitUser->hasPermission('p_stock_create')}
<a class="btn btn-warning btn-sm"
href="{$smarty.const.STOCK_PKG_URL}add_requisition.php?assembly_content_id={$assemblyContentId}&amp;kit_count={$kitCount}">{tr}Create Requisition{/tr}</a>
{/if}
</div>
</form>
+ </div>
+ <h1>{tr}Stock Levels{/tr}{if $assemblyTitle} — {$assemblyTitle|escape}{/if}</h1>
+ </header>
+
+ <section class="body">
{if $stockList}
<table class="table table-hover table-condensed">