Stock Service Updates (#421)

* viewjs consume: implement location and update stock specific

* Transfer Products

* services StockService#GetProductStockEntriesByLocation: add method

* services StockService#AddProduct: check for stock and locations

* services StockService: include location_id

* services StockService#LocationExists: add method

* services StockService#UndoBooking: fix based on stockRow

* Reimplement StockServer->TransferProduct (one loop for the whole action to preserve stock_id)

* Ensure that the location_id is never NULL in the stock and stock_log table (checked by an INSERT trigger, sets the products default location if empty)

* Only consider stock amount at the given location on consume, if supplied

* Restore more/old display text for "specific stock entry"

* Don't allow transfering tare weight enabled products

* Various small changes (code style, missing OpenAPI endpoint, remove location_id null checking)

* Updated translations strings

* Added transaction_id and correlation_id to stock_log entries to group them together

* ProductCard - location to default location label change

* Also undo correlated bookings on undo

* Added API endpoints for listing and undoing transactions and use them on purchase/consume/inventory/stockoverview

* Initial Stock detail page

* Allow Undo for Tranfers

* Price step to .01

* Some localization string changes & fixes
This commit is contained in:
kriddles
2019-12-19 12:48:36 -06:00
committed by Bernd Bestel
parent 0be1994c02
commit 6c7420ea08
27 changed files with 2614 additions and 79 deletions

View File

@@ -23,7 +23,7 @@
<strong>{{ $__t('Stock amount') . ' / ' . $__t('Quantity unit') }}:</strong> <span id="productcard-product-stock-amount" class="locale-number locale-number-quantity-amount"></span> <span id="productcard-product-stock-qu-name"></span> <span id="productcard-product-stock-opened-amount" class="small font-italic locale-number locale-number-quantity-amount"></span>
<span id="productcard-aggregated-amounts" class="pl-2 text-secondary d-none"><i class="fas fa-custom-sigma-sign"></i> <span id="productcard-product-stock-amount-aggregated" class="locale-number locale-number-quantity-amount"></span> <span id="productcard-product-stock-qu-name-aggregated"></span> <span id="productcard-product-stock-opened-amount-aggregated locale-number locale-number-quantity-amount" class="small font-italic"></span></span><br>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)<strong>{{ $__t('Location') }}:</strong> <span id="productcard-product-location"></span><br>@endif
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)<strong>{{ $__t('Default location') }}:</strong> <span id="productcard-product-location"></span><br>@endif
<strong>{{ $__t('Last purchased') }}:</strong> <span id="productcard-product-last-purchased"></span> <time id="productcard-product-last-purchased-timeago" class="timeago timeago-contextual"></time><br>
<strong>{{ $__t('Last used') }}:</strong> <span id="productcard-product-last-used"></span> <time id="productcard-product-last-used-timeago" class="timeago timeago-contextual"></time><br>
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)<strong>{{ $__t('Last price') }}:</strong> <span id="productcard-product-last-price"></span><br>@endif

View File

@@ -22,11 +22,33 @@
'label' => 'Amount',
'hintId' => 'amount_qu_unit',
'min' => 1,
'value' => 1,
'value' => 0,
'invalidFeedback' => $__t('The amount cannot be lower than %s', '1'),
'additionalHtmlContextHelp' => '<div id="tare-weight-handling-info" class="text-info font-italic d-none">' . $__t('Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated') . '</div>'
))
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
@php /*@include('components.locationpicker', array(
'id' => 'location_id',
'locations' => $locations,
'isRequired' => true,
'label' => 'Location'
))*/ @endphp
<div class="form-group">
<label for="location_id">{{ $__t('Location') }}</label>
<select required class="form-control location-combobox" id="location_id" name="location_id">
<option></option>
@foreach($locations as $location)
<option value="{{ $location->id }}">{{ $location->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A location is required') }}</div>
</div>
@else
<input type="hidden" name="location_id" id="location_id" value="1">
@endif
<div class="form-group">
<label for="use_specific_stock_entry">
<input type="checkbox" id="use_specific_stock_entry" name="use_specific_stock_entry"> {{ $__t('Use a specific stock item') }}

View File

@@ -59,7 +59,7 @@
'id' => 'price',
'label' => 'Price',
'min' => 0,
'step' => 0.0001,
'step' => 0.01,
'value' => '',
'hint' => $__t('in %s per purchase quantity unit', GROCY_CURRENCY),
'additionalHtmlContextHelp' => '<br><span class="small text-muted">' . $__t('This will apply to added products') . '</span>',

View File

@@ -157,6 +157,14 @@
<span class="nav-link-text">{{ $__t('Consume') }}</span>
</a>
</li>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<li class="nav-item" data-toggle="tooltip" data-placement="right" title="{{ $__t('Transfer') }}" data-nav-for-page="transfer">
<a class="nav-link discrete-link" href="{{ $U('/transfer') }}">
<i class="fas fa-exchange-alt"></i>
<span class="nav-link-text">{{ $__t('Transfer') }}</span>
</a>
</li>
@endif
<li class="nav-item" data-toggle="tooltip" data-placement="right" title="{{ $__t('Inventory') }}" data-nav-for-page="inventory">
<a class="nav-link discrete-link" href="{{ $U('/inventory') }}">
<i class="fas fa-list"></i>

View File

@@ -55,7 +55,7 @@
'id' => 'price',
'label' => 'Price',
'min' => 0,
'step' => 0.0001,
'step' => 0.01,
'value' => '',
'hint' => $__t('in %s and based on the purchase quantity unit', GROCY_CURRENCY),
'invalidFeedback' => $__t('The price cannot be lower than %s', '0'),

View File

@@ -80,7 +80,7 @@
'id' => 'price_factor',
'label' => 'Price factor',
'min' => 0,
'step' => 0.0001,
'step' => 0.01,
'value' => '',
'hint' => $__t('The resulting price of this ingredient will be multiplied by this factor'),
'invalidFeedback' => $__t('This cannot be lower than %s', '0'),

197
views/stockdetail.blade.php Normal file
View File

@@ -0,0 +1,197 @@
@extends('layout.default')
@section('title', $__t('Stock entry'))
@section('activeNav', 'stockdetail')
@section('viewJsName', 'stockdetail')
@push('pageScripts')
<script src="{{ $U('/node_modules/jquery-ui-dist/jquery-ui.min.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/viewjs/purchase.js?v=', true) }}{{ $version }}"></script>
@endpush
@push('pageStyles')
<style>
.product-name-cell[data-product-has-picture='true'] {
cursor: pointer;
}
</style>
@endpush
@section('content')
<div class="row">
<div class="col">
<h1>@yield('title')</h1>
</div>
<div class="col">
@include('components.productpicker', array('products' => $products,'disallowAddProductWorkflows' => true))
</div>
</div>
<div class="row">
<div class="col">
<table id="stock-detail-table" class="table table-sm table-striped dt-responsive">
<thead>
<tr>
<th class="border-right"></th>
<th class="d-none">product_id</th> <!-- This must be in the first column for searching -->
<th>{{ $__t('Product') }}</th>
<th>{{ $__t('Amount') }}</th>
<th>{{ $__t('Best before date') }}</th>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)<th>{{ $__t('Location') }}</th>@endif
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)<th>{{ $__t('Price') }}</th>@endif
<th>{{ $__t('Purchased date') }}</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
</tr>
</thead>
<tbody class="d-none">
@foreach($currentStockDetail as $currentStockEntry)
<tr id="stock-{{ $currentStockEntry->id }}-row" class="@if(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('-1 days')) && $currentStockEntry->amount > 0) table-danger @elseif(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime("+$nextXDays days")) && $currentStockEntry->amount > 0) table-warning @elseif (FindObjectInArrayByPropertyValue($missingProducts, 'id', $currentStockEntry->product_id) !== null) table-info @endif">
<td class="fit-content border-right">
<a class="btn btn-success btn-sm stock-consume-button @if($currentStockEntry->amount < 1) disabled @endif" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Consume %1$s of %2$s', '1 ' . FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
data-stock-id="{{ $currentStockEntry->stock_id }}"
data-stockrow-id="{{ $currentStockEntry->id }}"
data-location-id="{{ $currentStockEntry->location_id }}"
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}"
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name }}"
data-consume-amount="1">
<i class="fas fa-utensils"></i> 1
</a>
<a id="stock-{{ $currentStockEntry->id }}-consume-all-button" class="btn btn-danger btn-sm stock-consume-button @if($currentStockEntry->amount == 0) disabled @endif" href="#" data-toggle="tooltip" data-placement="right" title="{{ $__t('Consume all %s for this stock entry', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
data-stock-id="{{ $currentStockEntry->stock_id }}"
data-stockrow-id="{{ $currentStockEntry->id }}"
data-location-id="{{ $currentStockEntry->location_id }}"
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}"
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name }}"
data-consume-amount="{{ $currentStockEntry->amount }}">
<i class="fas fa-utensils"></i> {{ $__t('All') }}
</a>
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
<a class="btn btn-success btn-sm product-open-button @if($currentStockEntry->amount < 1 || $currentStockEntry->amount == $currentStockEntry->amount_opened) disabled @endif" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Mark %1$s of %2$s as open', '1 ' . FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}"
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name }}">
<i class="fas fa-box-open"></i> 1
</a>
@endif
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary" type="button" data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item product-add-to-shopping-list-button" type="button" href="#"
data-product-id="{{ $currentStockEntry->product_id }}">
<i class="fas fa-shopping-cart"></i> {{ $__t('Add to shopping list') }}
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item product-purchase-button" type="button" href="#"
data-product-id="{{ $currentStockEntry->product_id }}">
<i class="fas fa-shopping-cart"></i> {{ $__t('Purchase') }}
</a>
<a class="dropdown-item product-consume-custom-amount-button @if($currentStockEntry->amount < 1) disabled @endif" type="button" href="#"
data-product-id="{{ $currentStockEntry->product_id }}"
data-location-id="{{ $currentStockEntry->location_id }}"
data-stock-id="{{ $currentStockEntry->stock_id }}">
<i class="fas fa-utensils"></i> {{ $__t('Consume') }}
</a>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<a class="dropdown-item product-transfer-button @if($currentStockEntry->amount < 1) disabled @endif" type="button" href="#"
data-product-id="{{ $currentStockEntry->product_id }}"
data-location-id="{{ $currentStockEntry->location_id }}"
data-stock-id="{{ $currentStockEntry->stock_id }}">
<i class="fas fa-exchange-alt"></i> {{ $__t('Transfer') }}
</a>
@endif
<a class="dropdown-item product-inventory-button" type="button" href="#"
data-product-id="{{ $currentStockEntry->product_id }}">
<i class="fas fa-list"></i> {{ $__t('Inventory') }}
</a>
<a class="dropdown-item product-stockedit-button" type="button" href="#"
data-product-id="{{ $currentStockEntry->product_id }}"
data-location-id="{{ $currentStockEntry->location_id }}"
data-id="{{ $currentStockEntry->id }}">
<i class="fas fa-boxes"></i> {{ $__t('Stock edit') }}
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item product-name-cell" data-product-id="{{ $currentStockEntry->product_id }}" type="button" href="#">
<i class="fas fa-info"></i> {{ $__t('Show product details') }}
</a>
<a class="dropdown-item" type="button" href="{{ $U('/stockjournal?product=') }}{{ $currentStockEntry->product_id }}">
<i class="fas fa-file-alt"></i> {{ $__t('Stock journal for this product') }}
</a>
<a class="dropdown-item" type="button" href="{{ $U('/product/') }}{{ $currentStockEntry->product_id . '?returnto=%2Fstockdetail' }}">
<i class="fas fa-edit"></i> {{ $__t('Edit product') }}
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item product-consume-button product-consume-button-spoiled @if($currentStockEntry->amount < 1) disabled @endif" type="button" href="#"
data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}"
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name }}"
data-consume-amount="1">
<i class="fas fa-utensils"></i> {{ $__t('Consume %1$s of %2$s as spoiled', '1 ' . FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name) }}
</a>
@if(GROCY_FEATURE_FLAG_RECIPES)
<a class="dropdown-item" type="button" href="{{ $U('/recipes?search=') }}{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}">
<i class="fas fa-cocktail"></i> {{ $__t('Search for recipes containing this product') }}
</a>
@endif
</div>
</div>
</td>
<td class="d-none" data-product-id="{{ $currentStockEntry->product_id }}">
{{ $currentStockEntry->product_id }}
</td>
<td class="product-name-cell cursor-link" data-product-id="{{ $currentStockEntry->product_id }}">
{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}
</td>
<td>
<span id="stock-{{ $currentStockEntry->id }}-amount" class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->amount }}</span> <span id="product-{{ $currentStockEntry->product_id }}-qu-name">{{ $__n($currentStockEntry->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name_plural) }}</span>
<span id="stock-{{ $currentStockEntry->id }}-opened-amount" class="small font-italic">@if($currentStockEntry->amount_opened > 0){{ $__t('%s opened', $currentStockEntry->amount_opened) }}@endif</span>
@if($currentStockEntry->is_aggregated_amount == 1)
<span class="pl-1 text-secondary">
<i class="fas fa-custom-sigma-sign"></i> <span id="product-{{ $currentStockEntry->product_id }}-amount-aggregated" class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->amount_aggregated }}</span> {{ $__n($currentStockEntry->amount_aggregated, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name_plural) }}
@if($currentStockEntry->amount_opened_aggregated > 0)<span id="product-{{ $currentStockEntry->product_id }}-opened-amount-aggregated" class="small font-italic">{{ $__t('%s opened', $currentStockEntry->amount_opened_aggregated) }}</span>@endif
</span>
@endif
</td>
<td>
<span id="stock-{{ $currentStockEntry->id }}-best-before-date">{{ $currentStockEntry->best_before_date }}</span>
<time id="stock-{{ $currentStockEntry->id }}-best-before-date-timeago" class="timeago timeago-contextual" datetime="{{ $currentStockEntry->best_before_date }} 23:59:59"></time>
</td>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<td id="stock-{{ $currentStockEntry->id }}-location" class="location-name-cell cursor-link" data-location-id="{{ $currentStockEntry->location_id }}">
{{ FindObjectInArrayByPropertyValue($locations, 'id', $currentStockEntry->location_id)->name }}
</td>
@endif
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
<td id="stock-{{ $currentStockEntry->id }}-price" class="price-name-cell cursor-link" data-price-id="{{ $currentStockEntry->price }}">
{{ $currentStockEntry->price }}
</td>
@endif
<td>
<span id="stock-{{ $currentStockEntry->id }}-purchased-date">{{ $currentStockEntry->purchased_date }}</span>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="modal fade" id="stockdetail-productcard-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
@include('components.productcard')
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
@stop

81
views/stockedit.blade.php Normal file
View File

@@ -0,0 +1,81 @@
@extends('layout.default')
@section('title', $__t('Stock edit'))
@section('activeNav', 'stockedit')
@section('viewJsName', 'stockedit')
@section('content')
<div class="row">
<div class="col-xs-12 col-md-6 col-xl-4 pb-3">
<h1>@yield('title')</h1>
<form id="stockedit-form" novalidate>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
@include('components.locationpicker', array(
'locations' => $locations
))
@else
<input type="hidden" name="location_id" id="location_id" value="1">
@endif
@include('components.numberpicker', array(
'id' => 'amount',
'label' => 'Amount',
'hintId' => 'amount_qu_unit',
'invalidFeedback' => $__t('The amount cannot be lower than %s', '0'),
'additionalAttributes' => 'data-not-equal="-1"',
'additionalHtmlElements' => '<div id="stockedit-change-info" class="form-text text-muted small d-none"></div>',
'additionalHtmlContextHelp' => '<div id="tare-weight-handling-info" class="text-small text-info font-italic d-none">' . $__t('Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated') . '</div>'
))
@php
$additionalGroupCssClasses = '';
if (!GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
$additionalGroupCssClasses = 'd-none';
}
@endphp
@include('components.datetimepicker', array(
'id' => 'best_before_date',
'label' => 'Best before',
'format' => 'YYYY-MM-DD',
'initWithNow' => false,
'limitEndToNow' => false,
'limitStartToNow' => false,
'invalidFeedback' => $__t('A best before date is required'),
'nextInputSelector' => '#best_before_date',
'additionalGroupCssClasses' => 'date-only-datetimepicker',
'shortcutValue' => '2999-12-31',
'shortcutLabel' => 'Never expires',
'earlierThanInfoLimit' => date('Y-m-d'),
'earlierThanInfoText' => $__t('The given date is earlier than today, are you sure?'),
'additionalGroupCssClasses' => $additionalGroupCssClasses
))
@php $additionalGroupCssClasses = ''; @endphp
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
@include('components.numberpicker', array(
'id' => 'price',
'label' => 'Price',
'min' => 0,
'step' => 0.01,
'value' => '',
'hint' => $__t('in %s per purchase quantity unit', GROCY_CURRENCY),
'invalidFeedback' => $__t('The price cannot be lower than %s', '0'),
'isRequired' => false
))
@else
<input type="hidden" name="price" id="price" value="0">
@endif
<button id="save-stockedit-button" class="btn btn-success">{{ $__t('OK') }}</button>
</form>
</div>
<div class="col-xs-12 col-md-6 col-xl-4 hide-when-embedded">
@include('components.productcard')
</div>
</div>
@stop

View File

@@ -37,6 +37,7 @@
<th>{{ $__t('Amount') }}</th>
<th>{{ $__t('Booking time') }}</th>
<th>{{ $__t('Booking type') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif">{{ $__t('Location') }}</th>
</tr>
</thead>
<tbody class="d-none">
@@ -65,6 +66,9 @@
<td>
{{ $__t($stockLogEntry->transaction_type) }}
</td>
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif">
{{ FindObjectInArrayByPropertyValue($locations, 'id', $stockLogEntry->location_id)->name }}
</td>
</tr>
@endforeach
</tbody>

View File

@@ -140,6 +140,12 @@
data-product-id="{{ $currentStockEntry->product_id }}">
<i class="fas fa-utensils"></i> {{ $__t('Consume') }}
</a>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<a class="dropdown-item product-transfer-button @if($currentStockEntry->amount < 1) disabled @endif" type="button" href="#"
data-product-id="{{ $currentStockEntry->product_id }}">
<i class="fas fa-exchange-alt"></i> {{ $__t('Transfer') }}
</a>
@endif
<a class="dropdown-item product-inventory-button" type="button" href="#"
data-product-id="{{ $currentStockEntry->product_id }}">
<i class="fas fa-list"></i> {{ $__t('Inventory') }}
@@ -148,6 +154,10 @@
<a class="dropdown-item product-name-cell" data-product-id="{{ $currentStockEntry->product_id }}" type="button" href="#">
<i class="fas fa-info"></i> {{ $__t('Show product details') }}
</a>
<a class="dropdown-item" type="button" href="{{ $U('/stockdetail?product=') }}{{ $currentStockEntry->product_id }}"
data-product-id="{{ $currentStockEntry->product_id }}">
<i class="fas fa-boxes"></i> {{ $__t('Show stock entries') }}
</a>
<a class="dropdown-item" type="button" href="{{ $U('/stockjournal?product=') }}{{ $currentStockEntry->product_id }}">
<i class="fas fa-file-alt"></i> {{ $__t('Stock journal for this product') }}
</a>

84
views/transfer.blade.php Normal file
View File

@@ -0,0 +1,84 @@
@extends('layout.default')
@section('title', $__t('Transfer'))
@section('activeNav', 'transfer')
@section('viewJsName', 'transfer')
@section('content')
<div class="row">
<div class="col-xs-12 col-md-6 col-xl-4 pb-3">
<h1>@yield('title')</h1>
<form id="transfer-form" novalidate>
@include('components.productpicker', array(
'products' => $products,
'nextInputSelector' => '#location_id_from',
'disallowAddProductWorkflows' => true
))
@php /*@include('components.locationpicker', array(
'id' => 'location_from',
'locations' => $locations,
'isRequired' => true,
'label' => 'Transfer From Location'
))*/ @endphp
<div class="form-group">
<label for="location_id_from">{{ $__t('From location') }}</label>
<select required class="form-control location-combobox" id="location_id_from" name="location_id_from">
<option></option>
@foreach($locations as $location)
<option value="{{ $location->id }}">{{ $location->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A location is required') }}</div>
</div>
@include('components.numberpicker', array(
'id' => 'amount',
'label' => 'Amount',
'hintId' => 'amount_qu_unit',
'min' => 1,
'value' => 1,
'invalidFeedback' => $__t('The amount cannot be lower than %s', '1'),
'additionalHtmlContextHelp' => '<div id="tare-weight-handling-info" class="text-info font-italic d-none">' . $__t('Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated') . '</div>'
))
<div class="form-group">
<label for="use_specific_stock_entry">
<input type="checkbox" id="use_specific_stock_entry" name="use_specific_stock_entry"> {{ $__t('Use a specific stock item') }}
<span class="small text-muted">{{ $__t('The first item in this list would be picked by the default rule which is "First expiring first, then first in first out"') }}</span>
</label>
<select disabled class="form-control" id="specific_stock_entry" name="specific_stock_entry">
<option></option>
</select>
</div>
@php /*@include('components.locationpicker', array(
'locations' => $locations,
'isRequired' => true,
'label' => 'Transfer to Location'
))*/ @endphp
<div class="form-group">
<label for="location_id_to">{{ $__t('To location') }}</label>
<select required class="form-control location-combobox" id="location_id_to" name="location_id_to">
<option></option>
@foreach($locations as $location)
<option value="{{ $location->id }}">{{ $location->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A location is required') }}</div>
</div>
<button id="save-transfer-button" class="btn btn-success">{{ $__t('OK') }}</button>
</form>
</div>
<div class="col-xs-12 col-md-6 col-xl-4 hide-when-embedded">
@include('components.productcard')
</div>
</div>
@stop