mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 01:32:38 +00:00
Allow different locations per product in stock (closes #124)
Kind of basic for now, a different location can be set on purchase, the filters on the stock overview page handles different locations
This commit is contained in:
parent
32e878afc9
commit
b89643ddb1
@ -66,13 +66,19 @@ class StockApiController extends BaseApiController
|
||||
$price = $requestBody['price'];
|
||||
}
|
||||
|
||||
$locationId = null;
|
||||
if (array_key_exists('location_id', $requestBody) && is_numeric($requestBody['location_id']))
|
||||
{
|
||||
$locationId = $requestBody['location_id'];
|
||||
}
|
||||
|
||||
$transactionType = StockService::TRANSACTION_TYPE_PURCHASE;
|
||||
if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype']))
|
||||
{
|
||||
$transactionType = $requestBody['transactiontype'];
|
||||
}
|
||||
|
||||
$bookingId = $this->StockService->AddProduct($args['productId'], $requestBody['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price);
|
||||
$bookingId = $this->StockService->AddProduct($args['productId'], $requestBody['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price, $locationId);
|
||||
return $this->ApiResponse(array('booking_id' => $bookingId));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
|
@ -22,6 +22,7 @@ class StockController extends BaseController
|
||||
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
||||
'locations' => $this->Database->locations()->orderBy('name'),
|
||||
'currentStock' => $this->StockService->GetCurrentStock(),
|
||||
'currentStockLocations' => $this->StockService->GetCurrentStockLocations(),
|
||||
'missingProducts' => $this->StockService->GetMissingProducts(),
|
||||
'nextXDays' => 5,
|
||||
'productGroups' => $this->Database->product_groups()->orderBy('name')
|
||||
@ -31,7 +32,8 @@ class StockController extends BaseController
|
||||
public function Purchase(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'purchase', [
|
||||
'products' => $this->Database->products()->orderBy('name')
|
||||
'products' => $this->Database->products()->orderBy('name'),
|
||||
'locations' => $this->Database->locations()->orderBy('name')
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -1021,7 +1021,8 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "double"
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"best_before_date": {
|
||||
"type": "string",
|
||||
@ -1035,6 +1036,11 @@
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "The price per purchase quantity unit in configured currency"
|
||||
},
|
||||
"location_id": {
|
||||
"type": "number",
|
||||
"format": "integer",
|
||||
"description": "If omitted, the default location of the product is used"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
|
15
migrations/0051.sql
Normal file
15
migrations/0051.sql
Normal file
@ -0,0 +1,15 @@
|
||||
ALTER TABLE stock
|
||||
ADD location_id INTEGER;
|
||||
|
||||
ALTER TABLE stock_log
|
||||
ADD location_id INTEGER;
|
||||
|
||||
CREATE VIEW stock_current_locations
|
||||
AS
|
||||
SELECT
|
||||
s.product_id,
|
||||
IFNULL(s.location_id, p.location_id) AS location_id
|
||||
FROM stock s
|
||||
JOIN products p
|
||||
ON s.product_id = p.id
|
||||
GROUP BY s.product_id, IFNULL(s.location_id, p.location_id);
|
@ -20,6 +20,7 @@
|
||||
jsonData.amount = amount;
|
||||
jsonData.best_before_date = Grocy.Components.DateTimePicker.GetValue();
|
||||
jsonData.price = price;
|
||||
jsonData.location_id = jsonForm.location_id;
|
||||
|
||||
Grocy.Api.Post('stock/products/' + jsonForm.product_id + '/add', jsonData,
|
||||
function(result)
|
||||
@ -65,6 +66,7 @@
|
||||
toastr.success(successMessage);
|
||||
$('#amount').val(0);
|
||||
$('#price').val('');
|
||||
$('#location_id').val('');
|
||||
Grocy.Components.DateTimePicker.Clear();
|
||||
Grocy.Components.ProductPicker.SetValue('');
|
||||
Grocy.Components.ProductPicker.GetInputElement().focus();
|
||||
@ -99,6 +101,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
||||
{
|
||||
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name);
|
||||
$('#price').val(productDetails.last_price);
|
||||
$('#location_id').val(productDetails.product.location_id);
|
||||
|
||||
if (productDetails.product.allow_partial_units_in_stock == 1)
|
||||
{
|
||||
|
@ -11,18 +11,24 @@ class StockService extends BaseService
|
||||
|
||||
public function GetCurrentStock($includeNotInStockButMissingProducts = false)
|
||||
{
|
||||
$sql = 'SELECT * from stock_current';
|
||||
$sql = 'SELECT * FROM stock_current';
|
||||
if ($includeNotInStockButMissingProducts)
|
||||
{
|
||||
$sql = 'SELECT * from stock_current WHERE best_before_date IS NOT NULL';
|
||||
$sql = 'SELECT * FROM stock_current WHERE best_before_date IS NOT NULL';
|
||||
}
|
||||
|
||||
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
|
||||
}
|
||||
|
||||
public function GetCurrentStockLocations()
|
||||
{
|
||||
$sql = 'SELECT * FROM stock_current_locations';
|
||||
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
|
||||
}
|
||||
|
||||
public function GetMissingProducts()
|
||||
{
|
||||
$sql = 'SELECT * from stock_missing_products';
|
||||
$sql = 'SELECT * FROM stock_missing_products';
|
||||
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
|
||||
}
|
||||
|
||||
@ -109,7 +115,7 @@ class StockService extends BaseService
|
||||
}
|
||||
}
|
||||
|
||||
public function AddProduct(int $productId, float $amount, string $bestBeforeDate, $transactionType, $purchasedDate, $price)
|
||||
public function AddProduct(int $productId, float $amount, string $bestBeforeDate, $transactionType, $purchasedDate, $price, $locationId = null)
|
||||
{
|
||||
if (!$this->ProductExists($productId))
|
||||
{
|
||||
@ -127,7 +133,8 @@ class StockService extends BaseService
|
||||
'purchased_date' => $purchasedDate,
|
||||
'stock_id' => $stockId,
|
||||
'transaction_type' => $transactionType,
|
||||
'price' => $price
|
||||
'price' => $price,
|
||||
'location_id' => $locationId
|
||||
));
|
||||
$logRow->save();
|
||||
|
||||
@ -139,7 +146,8 @@ class StockService extends BaseService
|
||||
'best_before_date' => $bestBeforeDate,
|
||||
'purchased_date' => $purchasedDate,
|
||||
'stock_id' => $stockId,
|
||||
'price' => $price
|
||||
'price' => $price,
|
||||
'location_id' => $locationId
|
||||
));
|
||||
$stockRow->save();
|
||||
|
||||
|
@ -49,6 +49,17 @@
|
||||
'isRequired' => false
|
||||
))
|
||||
|
||||
<div class="form-group">
|
||||
<label for="location_id">{{ $L('Location') }}</label>
|
||||
<select required class="form-control" 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">{{ $L('A location is required') }}</div>
|
||||
</div>
|
||||
|
||||
<button id="save-purchase-button" class="btn btn-success">{{ $L('OK') }}</button>
|
||||
|
||||
</form>
|
||||
|
@ -119,7 +119,9 @@
|
||||
<time id="product-{{ $currentStockEntry->product_id }}-next-best-before-date-timeago" class="timeago timeago-contextual" datetime="{{ $currentStockEntry->best_before_date }} 23:59:59"></time>
|
||||
</td>
|
||||
<td class="d-none">
|
||||
{{ FindObjectInArrayByPropertyValue($locations, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->location_id)->name }}
|
||||
@foreach(FindAllObjectsInArrayByPropertyValue($currentStockLocations, 'product_id', $currentStockEntry->product_id) as $locationsForProduct)
|
||||
{{ FindObjectInArrayByPropertyValue($locations, 'id', $locationsForProduct->location_id)->name }}
|
||||
@endforeach
|
||||
</td>
|
||||
<td class="d-none">
|
||||
@if($currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('-1 days')) && $currentStockEntry->amount > 0) expired @elseif($currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime("+$nextXDays days")) && $currentStockEntry->amount > 0) expiring @elseif (FindObjectInArrayByPropertyValue($missingProducts, 'id', $currentStockEntry->product_id) !== null) belowminstockamount @endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user