diff options
| -rw-r--r-- | add_order.php | 249 | ||||
| -rw-r--r-- | list_stock.php | 18 | ||||
| -rw-r--r-- | templates/add_order.tpl | 163 | ||||
| -rw-r--r-- | templates/list_assemblies_simple.tpl | 1 | ||||
| -rw-r--r-- | templates/list_movements.tpl | 40 | ||||
| -rw-r--r-- | templates/list_stock.tpl | 26 |
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}">✕</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}">← {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}&assembly_content_id={$assemblyContentId|escape:'url'}&kit_count={$kitCount|escape:'url'}{/if}&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}&assembly_content_id={$assemblyContentId|escape:'url'}&kit_count={$kitCount|escape:'url'}{/if}{if $find}&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}&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"> |
