From 4fb70f8a89b7c16112c2ce56f6eacf98f833d379 Mon Sep 17 00:00:00 2001 From: Riccardo Sacchetto Date: Sun, 13 Jul 2025 01:31:33 +0200 Subject: [PATCH] Support associating a Shopping List with a Store --- controllers/StockApiController.php | 9 ++++-- controllers/StockController.php | 2 ++ localization/strings.pot | 3 ++ migrations/0254.sql | 2 ++ public/viewjs/shoppinglist.js | 14 +++++++++ services/StockService.php | 47 +++++++++++++++++------------- views/shoppinglist.blade.php | 3 ++ views/shoppinglistform.blade.php | 14 +++++++++ 8 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 migrations/0254.sql diff --git a/controllers/StockApiController.php b/controllers/StockApiController.php index bcbc4080..e28133f6 100644 --- a/controllers/StockApiController.php +++ b/controllers/StockApiController.php @@ -20,13 +20,18 @@ class StockApiController extends BaseApiController $requestBody = $this->GetParsedAndFilteredRequestBody($request); $listId = 1; - if (array_key_exists('list_id', $requestBody) && !empty($requestBody['list_id']) && is_numeric($requestBody['list_id'])) { $listId = intval($requestBody['list_id']); } - $this->getStockService()->AddMissingProductsToShoppingList($listId); + $checkDefaultShoppingLocation = false; + if (array_key_exists('check_default_shopping_location', $requestBody) && filter_var($requestBody['check_default_shopping_location'], FILTER_VALIDATE_BOOLEAN) !== false) + { + $checkDefaultShoppingLocation = boolval($requestBody['check_default_shopping_location']); + } + + $this->getStockService()->AddMissingProductsToShoppingList($listId, $checkDefaultShoppingLocation); return $this->EmptyApiResponse($response); } catch (\Exception $ex) diff --git a/controllers/StockController.php b/controllers/StockController.php index 408c29c7..94bdf3ce 100644 --- a/controllers/StockController.php +++ b/controllers/StockController.php @@ -441,6 +441,7 @@ class StockController extends BaseController { return $this->renderPage($response, 'shoppinglistform', [ 'mode' => 'create', + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), 'userfields' => $this->getUserfieldsService()->GetFields('shopping_lists') ]); } @@ -449,6 +450,7 @@ class StockController extends BaseController return $this->renderPage($response, 'shoppinglistform', [ 'shoppingList' => $this->getDatabase()->shopping_lists($args['listId']), 'mode' => 'edit', + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), 'userfields' => $this->getUserfieldsService()->GetFields('shopping_lists') ]); } diff --git a/localization/strings.pot b/localization/strings.pot index bdb68303..7d55ae09 100644 --- a/localization/strings.pot +++ b/localization/strings.pot @@ -2470,3 +2470,6 @@ msgstr "" msgid "List actions" msgstr "" + +msgid "Add products that are below defined min. stock amount from matching store" +msgstr "" diff --git a/migrations/0254.sql b/migrations/0254.sql new file mode 100644 index 00000000..e0a6b002 --- /dev/null +++ b/migrations/0254.sql @@ -0,0 +1,2 @@ +ALTER TABLE shopping_lists +ADD shopping_location_id REFERENCES shopping_locations(id); diff --git a/public/viewjs/shoppinglist.js b/public/viewjs/shoppinglist.js index 8da79494..d6cfb31d 100644 --- a/public/viewjs/shoppinglist.js +++ b/public/viewjs/shoppinglist.js @@ -156,6 +156,20 @@ $(document).on('click', '#add-products-below-min-stock-amount', function(e) ); }); +$(document).on('click', '#add-products-below-min-stock-amount-by-shopping-location', function(e) + { + Grocy.Api.Post('stock/shoppinglist/add-missing-products', { "list_id": $("#selected-shopping-list").val(), "check_default_shopping_location": true }, + function(result) + { + window.location.href = U('/shoppinglist?list=' + $("#selected-shopping-list").val()); + }, + function(xhr) + { + console.error(xhr); + } + ); +}); + $(document).on('click', '#add-overdue-expired-products', function(e) { Grocy.Api.Post('stock/shoppinglist/add-overdue-products', { "list_id": $("#selected-shopping-list").val() }, diff --git a/services/StockService.php b/services/StockService.php index aba6ef2b..36ecc815 100644 --- a/services/StockService.php +++ b/services/StockService.php @@ -18,41 +18,46 @@ class StockService extends BaseService const TRANSACTION_TYPE_TRANSFER_FROM = 'transfer_from'; const TRANSACTION_TYPE_TRANSFER_TO = 'transfer_to'; - public function AddMissingProductsToShoppingList($listId = 1) + public function AddMissingProductsToShoppingList($listId = 1, $checkDefaultShoppingLocation = False) { if (!$this->ShoppingListExists($listId)) { throw new \Exception('Shopping list does not exist'); } + $shoppingList = $this->getDatabase()->shopping_lists()->where('id', $listId)->fetch(); $missingProducts = $this->GetMissingProducts(); foreach ($missingProducts as $missingProduct) { $product = $this->getDatabase()->products()->where('id', $missingProduct->id)->fetch(); - $amountToAdd = round($missingProduct->amount_missing, 2); - $alreadyExistingEntry = $this->getDatabase()->shopping_list()->where('product_id', $missingProduct->id)->fetch(); - if ($alreadyExistingEntry) + if (!$checkDefaultShoppingLocation || ($product->shopping_location_id == $shoppingList->shopping_location_id)) { - // Update - if ($alreadyExistingEntry->amount < $amountToAdd) + $amountToAdd = round($missingProduct->amount_missing, 2); + + $alreadyExistingEntry = $this->getDatabase()->shopping_list()->where('product_id', $missingProduct->id)->fetch(); + if ($alreadyExistingEntry) { - $alreadyExistingEntry->update([ - 'amount' => $amountToAdd, - 'shopping_list_id' => $listId - ]); + // Update + if ($alreadyExistingEntry->amount < $amountToAdd) + { + $alreadyExistingEntry->update([ + 'amount' => $amountToAdd, + 'shopping_list_id' => $listId + ]); + } + } + else + { + // Insert + $shoppinglistRow = $this->getDatabase()->shopping_list()->createRow([ + 'product_id' => $missingProduct->id, + 'amount' => $amountToAdd, + 'shopping_list_id' => $listId, + 'qu_id' => $product->qu_id_purchase + ]); + $shoppinglistRow->save(); } - } - else - { - // Insert - $shoppinglistRow = $this->getDatabase()->shopping_list()->createRow([ - 'product_id' => $missingProduct->id, - 'amount' => $amountToAdd, - 'shopping_list_id' => $listId, - 'qu_id' => $product->qu_id_purchase - ]); - $shoppinglistRow->save(); } } } diff --git a/views/shoppinglist.blade.php b/views/shoppinglist.blade.php index 9ae03fd4..16c3a476 100644 --- a/views/shoppinglist.blade.php +++ b/views/shoppinglist.blade.php @@ -144,6 +144,9 @@ $listItem->last_price_total = $listItem->price * $listItem->amount; {{ $__t('Add products that are below defined min. stock amount') }} + {{ $__t('Add products that are below defined min. stock amount from matching store') }} @endif {{ $__t('A name is required') }} + @php $prefillById = ''; if($mode=='edit') { $prefillById = $shoppingList->shopping_location_id; } @endphp + @if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) + @include('components.shoppinglocationpicker', array( + 'label' => 'Associated store', + 'prefillById' => $prefillById, + 'shoppinglocations' => $shoppinglocations + )) + @else + + @endif + @include('components.userfieldsform', array( 'userfields' => $userfields, 'entity' => 'shopping_lists'