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'];
|
$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;
|
$transactionType = StockService::TRANSACTION_TYPE_PURCHASE;
|
||||||
if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype']))
|
if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype']))
|
||||||
{
|
{
|
||||||
$transactionType = $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));
|
return $this->ApiResponse(array('booking_id' => $bookingId));
|
||||||
}
|
}
|
||||||
catch (\Exception $ex)
|
catch (\Exception $ex)
|
||||||
|
@ -22,6 +22,7 @@ class StockController extends BaseController
|
|||||||
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
||||||
'locations' => $this->Database->locations()->orderBy('name'),
|
'locations' => $this->Database->locations()->orderBy('name'),
|
||||||
'currentStock' => $this->StockService->GetCurrentStock(),
|
'currentStock' => $this->StockService->GetCurrentStock(),
|
||||||
|
'currentStockLocations' => $this->StockService->GetCurrentStockLocations(),
|
||||||
'missingProducts' => $this->StockService->GetMissingProducts(),
|
'missingProducts' => $this->StockService->GetMissingProducts(),
|
||||||
'nextXDays' => 5,
|
'nextXDays' => 5,
|
||||||
'productGroups' => $this->Database->product_groups()->orderBy('name')
|
'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)
|
public function Purchase(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
return $this->AppContainer->view->render($response, 'purchase', [
|
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",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"amount": {
|
"amount": {
|
||||||
"type": "double"
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
},
|
},
|
||||||
"best_before_date": {
|
"best_before_date": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -1035,6 +1036,11 @@
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"format": "double",
|
"format": "double",
|
||||||
"description": "The price per purchase quantity unit in configured currency"
|
"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": {
|
"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.amount = amount;
|
||||||
jsonData.best_before_date = Grocy.Components.DateTimePicker.GetValue();
|
jsonData.best_before_date = Grocy.Components.DateTimePicker.GetValue();
|
||||||
jsonData.price = price;
|
jsonData.price = price;
|
||||||
|
jsonData.location_id = jsonForm.location_id;
|
||||||
|
|
||||||
Grocy.Api.Post('stock/products/' + jsonForm.product_id + '/add', jsonData,
|
Grocy.Api.Post('stock/products/' + jsonForm.product_id + '/add', jsonData,
|
||||||
function(result)
|
function(result)
|
||||||
@ -65,6 +66,7 @@
|
|||||||
toastr.success(successMessage);
|
toastr.success(successMessage);
|
||||||
$('#amount').val(0);
|
$('#amount').val(0);
|
||||||
$('#price').val('');
|
$('#price').val('');
|
||||||
|
$('#location_id').val('');
|
||||||
Grocy.Components.DateTimePicker.Clear();
|
Grocy.Components.DateTimePicker.Clear();
|
||||||
Grocy.Components.ProductPicker.SetValue('');
|
Grocy.Components.ProductPicker.SetValue('');
|
||||||
Grocy.Components.ProductPicker.GetInputElement().focus();
|
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);
|
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name);
|
||||||
$('#price').val(productDetails.last_price);
|
$('#price').val(productDetails.last_price);
|
||||||
|
$('#location_id').val(productDetails.product.location_id);
|
||||||
|
|
||||||
if (productDetails.product.allow_partial_units_in_stock == 1)
|
if (productDetails.product.allow_partial_units_in_stock == 1)
|
||||||
{
|
{
|
||||||
|
@ -11,18 +11,24 @@ class StockService extends BaseService
|
|||||||
|
|
||||||
public function GetCurrentStock($includeNotInStockButMissingProducts = false)
|
public function GetCurrentStock($includeNotInStockButMissingProducts = false)
|
||||||
{
|
{
|
||||||
$sql = 'SELECT * from stock_current';
|
$sql = 'SELECT * FROM stock_current';
|
||||||
if ($includeNotInStockButMissingProducts)
|
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);
|
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()
|
public function GetMissingProducts()
|
||||||
{
|
{
|
||||||
$sql = 'SELECT * from stock_missing_products';
|
$sql = 'SELECT * FROM stock_missing_products';
|
||||||
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
|
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))
|
if (!$this->ProductExists($productId))
|
||||||
{
|
{
|
||||||
@ -127,7 +133,8 @@ class StockService extends BaseService
|
|||||||
'purchased_date' => $purchasedDate,
|
'purchased_date' => $purchasedDate,
|
||||||
'stock_id' => $stockId,
|
'stock_id' => $stockId,
|
||||||
'transaction_type' => $transactionType,
|
'transaction_type' => $transactionType,
|
||||||
'price' => $price
|
'price' => $price,
|
||||||
|
'location_id' => $locationId
|
||||||
));
|
));
|
||||||
$logRow->save();
|
$logRow->save();
|
||||||
|
|
||||||
@ -139,7 +146,8 @@ class StockService extends BaseService
|
|||||||
'best_before_date' => $bestBeforeDate,
|
'best_before_date' => $bestBeforeDate,
|
||||||
'purchased_date' => $purchasedDate,
|
'purchased_date' => $purchasedDate,
|
||||||
'stock_id' => $stockId,
|
'stock_id' => $stockId,
|
||||||
'price' => $price
|
'price' => $price,
|
||||||
|
'location_id' => $locationId
|
||||||
));
|
));
|
||||||
$stockRow->save();
|
$stockRow->save();
|
||||||
|
|
||||||
|
@ -49,6 +49,17 @@
|
|||||||
'isRequired' => false
|
'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>
|
<button id="save-purchase-button" class="btn btn-success">{{ $L('OK') }}</button>
|
||||||
|
|
||||||
</form>
|
</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>
|
<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>
|
||||||
<td class="d-none">
|
<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>
|
||||||
<td class="d-none">
|
<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
|
@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