From 887526c72740001957afe2eab99ca9cad28ded95 Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Tue, 17 Nov 2020 19:11:02 +0100 Subject: [PATCH] Squashed commit Fixed number input min/max amount handling Only (auto) save valid user inputs More filters on the stock journal pages Save the last price per used barcode and preselect that as a total price on purchase if not empty (closes #1131) Don't apply conversions for only_check_single_unit_in_stock ingredients (fixes #1120) Render shopping list userfields (closes #1052) Fixed Focus when adding included recipes (closes #1019) Order all base objects with NOCASE (closes #1086) --- changelog/60_UNRELEASED_2020-xx-xx.md | 4 + controllers/BatteriesController.php | 8 +- controllers/ChoresController.php | 12 +- controllers/EquipmentController.php | 2 +- controllers/GenericEntityController.php | 2 +- controllers/RecipesController.php | 20 +-- controllers/StockController.php | 170 ++++++++++++---------- controllers/TasksController.php | 10 +- grocy.openapi.json | 22 ++- localization/strings.pot | 6 +- migrations/0103.sql | 1 + public/js/grocy.js | 7 +- public/viewjs/choreform.js | 2 +- public/viewjs/components/numberpicker.js | 32 ++-- public/viewjs/consume.js | 11 +- public/viewjs/inventory.js | 1 + public/viewjs/productbarcodeform.js | 12 +- public/viewjs/productform.js | 2 +- public/viewjs/purchase.js | 28 +++- public/viewjs/recipeform.js | 1 + public/viewjs/shoppinglistform.js | 40 +++-- public/viewjs/shoppinglistitemform.js | 10 ++ public/viewjs/stockjournal.js | 42 ++++++ public/viewjs/stockjournalsummary.js | 69 ++++++++- public/viewjs/transfer.js | 13 +- services/UserfieldsService.php | 10 +- views/choreform.blade.php | 2 +- views/components/numberpicker.blade.php | 11 +- views/components/userfieldsform.blade.php | 2 +- views/productbarcodeform.blade.php | 5 + views/productform.blade.php | 11 +- views/quantityunitform.blade.php | 7 +- views/recipeform.blade.php | 2 +- views/recipes.blade.php | 2 +- views/shoppinglist.blade.php | 9 +- views/shoppinglistform.blade.php | 5 + views/shoppinglistitemform.blade.php | 5 + views/stockjournal.blade.php | 48 +++++- views/stockjournalsummary.blade.php | 86 ++++++++++- views/stocksettings.blade.php | 2 +- 40 files changed, 556 insertions(+), 178 deletions(-) diff --git a/changelog/60_UNRELEASED_2020-xx-xx.md b/changelog/60_UNRELEASED_2020-xx-xx.md index 1165fb47..bf8fac22 100644 --- a/changelog/60_UNRELEASED_2020-xx-xx.md +++ b/changelog/60_UNRELEASED_2020-xx-xx.md @@ -8,6 +8,7 @@ ### New feature: Prefill purchase data by barcodes - Imagine you buy for example eggs in different pack sizes and they have different barcodes - Each product barcode can be assigned an amount, quantity unit and store (on the product edit page), which is then automatically prefilled on the purchase page +- Additionally, the last price per barcode will be tracked and prefilled as a "Total price" on purchase - (Thanks @kriddles for the initial work on this) ### New feature: User permissions @@ -58,6 +59,7 @@ - When clicking the product name on the shopping list, the product card will now be displayed (like on the stock overview page) (thanks @kriddles) - On the product card there is now also a button to jump directly to the stock entries of the corresponding product (thanks @kriddles) - The product picker workflows can now also be started by `ENTER` (additionally to `TAB`) +- Added more filters on the stock journal page - Added a grouped/summarized stock journal (new button "Journal summary" at the top of the stock journal page) (thanks @fipwmaqzufheoxq92ebc) - Provides an overview of summarized transactions per product, transaction type and user + summarized amount - Fixed that changing the products "Factor purchase to stock quantity unit" not longer messes up historical prices (which results for example in wrong recipe costs) (thanks @kriddles) @@ -80,6 +82,7 @@ - Decimal amounts are now allowed (for any product, rounded by two decimal places) - Added a button to add all currently in-stock but overdue and expired products to the shopping list (thanks @m-byte) - Improved that when `FEATURE_FLAG_STOCK` is disabled, all product/stock related inputs and buttons are now hidden on the shopping list page (thanks @fipwmaqzufheoxq92ebc) +- Shopping list items can now have their own Userfields (entity `shopping_list`), on the shopping list table those fields are rendered additionally to the product Userfields - Fixed that "Add products that are below defined min. stock amount" always rounded up the missing amount to an integral number, this now allows decimal numbers ### Recipe improvements/fixes @@ -130,6 +133,7 @@ - Added a "Clear filter"-button on all pages (with filters) to quickly reset applied filters - Prefilled number inputs now use sensible decimal places (max. the configured decimals while hiding trailing zeros where appropriate, means if you never use partial amounts for a product, you'll never see decimals for it) - Improved / more precise validation messages for number inputs +- Ordering now happens case-insensitive - The data path (previously fixed to the `data` folder) is now configurable, making it possible to run multiple grocy instances from the same directory (with different `config.php` files / different database, etc.) (thanks @fgrsnau) - Via an environment variable `GROCY_DATAPATH` (higher priority) - Via an FastCGI parameter `GROCY_DATAPATH` (lower priority) diff --git a/controllers/BatteriesController.php b/controllers/BatteriesController.php index fa8974f3..fcdf26cf 100644 --- a/controllers/BatteriesController.php +++ b/controllers/BatteriesController.php @@ -7,7 +7,7 @@ class BatteriesController extends BaseController public function BatteriesList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'batteries', [ - 'batteries' => $this->getDatabase()->batteries()->orderBy('name'), + 'batteries' => $this->getDatabase()->batteries()->orderBy('name', 'COLLATE NOCASE'), 'userfields' => $this->getUserfieldsService()->GetFields('batteries'), 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('batteries') ]); @@ -41,7 +41,7 @@ class BatteriesController extends BaseController { return $this->renderPage($response, 'batteriesjournal', [ 'chargeCycles' => $this->getDatabase()->battery_charge_cycles()->orderBy('tracked_time', 'DESC'), - 'batteries' => $this->getDatabase()->batteries()->orderBy('name') + 'batteries' => $this->getDatabase()->batteries()->orderBy('name', 'COLLATE NOCASE') ]); } @@ -51,7 +51,7 @@ class BatteriesController extends BaseController $nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['batteries_due_soon_days']; return $this->renderPage($response, 'batteriesoverview', [ - 'batteries' => $this->getDatabase()->batteries()->orderBy('name'), + 'batteries' => $this->getDatabase()->batteries()->orderBy('name', 'COLLATE NOCASE'), 'current' => $this->getBatteriesService()->GetCurrent(), 'nextXDays' => $nextXDays, 'userfields' => $this->getUserfieldsService()->GetFields('batteries'), @@ -62,7 +62,7 @@ class BatteriesController extends BaseController public function TrackChargeCycle(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'batterytracking', [ - 'batteries' => $this->getDatabase()->batteries()->orderBy('name') + 'batteries' => $this->getDatabase()->batteries()->orderBy('name', 'COLLATE NOCASE') ]); } diff --git a/controllers/ChoresController.php b/controllers/ChoresController.php index 09f87481..a65f4bc1 100644 --- a/controllers/ChoresController.php +++ b/controllers/ChoresController.php @@ -17,7 +17,7 @@ class ChoresController extends BaseController 'userfields' => $this->getUserfieldsService()->GetFields('chores'), 'assignmentTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_ASSIGNMENT_TYPE_'), 'users' => $users, - 'products' => $this->getDatabase()->products()->orderBy('name') + 'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE') ]); } else @@ -29,7 +29,7 @@ class ChoresController extends BaseController 'userfields' => $this->getUserfieldsService()->GetFields('chores'), 'assignmentTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_ASSIGNMENT_TYPE_'), 'users' => $users, - 'products' => $this->getDatabase()->products()->orderBy('name') + 'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE') ]); } } @@ -37,7 +37,7 @@ class ChoresController extends BaseController public function ChoresList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'chores', [ - 'chores' => $this->getDatabase()->chores()->orderBy('name'), + 'chores' => $this->getDatabase()->chores()->orderBy('name', 'COLLATE NOCASE'), 'userfields' => $this->getUserfieldsService()->GetFields('chores'), 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('chores') ]); @@ -52,7 +52,7 @@ class ChoresController extends BaseController { return $this->renderPage($response, 'choresjournal', [ 'choresLog' => $this->getDatabase()->chores_log()->orderBy('tracked_time', 'DESC'), - 'chores' => $this->getDatabase()->chores()->orderBy('name'), + 'chores' => $this->getDatabase()->chores()->orderBy('name', 'COLLATE NOCASE'), 'users' => $this->getDatabase()->users()->orderBy('username') ]); } @@ -63,7 +63,7 @@ class ChoresController extends BaseController $nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['chores_due_soon_days']; return $this->renderPage($response, 'choresoverview', [ - 'chores' => $this->getDatabase()->chores()->orderBy('name'), + 'chores' => $this->getDatabase()->chores()->orderBy('name', 'COLLATE NOCASE'), 'currentChores' => $this->getChoresService()->GetCurrent(), 'nextXDays' => $nextXDays, 'userfields' => $this->getUserfieldsService()->GetFields('chores'), @@ -75,7 +75,7 @@ class ChoresController extends BaseController public function TrackChoreExecution(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'choretracking', [ - 'chores' => $this->getDatabase()->chores()->orderBy('name'), + 'chores' => $this->getDatabase()->chores()->orderBy('name', 'COLLATE NOCASE'), 'users' => $this->getDatabase()->users()->orderBy('username') ]); } diff --git a/controllers/EquipmentController.php b/controllers/EquipmentController.php index 9d9f8e9e..e64db7c6 100644 --- a/controllers/EquipmentController.php +++ b/controllers/EquipmentController.php @@ -28,7 +28,7 @@ class EquipmentController extends BaseController public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'equipment', [ - 'equipment' => $this->getDatabase()->equipment()->orderBy('name'), + 'equipment' => $this->getDatabase()->equipment()->orderBy('name', 'COLLATE NOCASE'), 'userfields' => $this->getUserfieldsService()->GetFields('equipment'), 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('equipment') ]); diff --git a/controllers/GenericEntityController.php b/controllers/GenericEntityController.php index ca1aed80..7b18cb56 100644 --- a/controllers/GenericEntityController.php +++ b/controllers/GenericEntityController.php @@ -7,7 +7,7 @@ class GenericEntityController extends BaseController public function UserentitiesList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'userentities', [ - 'userentities' => $this->getDatabase()->userentities()->orderBy('name') + 'userentities' => $this->getDatabase()->userentities()->orderBy('name', 'COLLATE NOCASE') ]); } diff --git a/controllers/RecipesController.php b/controllers/RecipesController.php index baa249b6..7c4c5230 100644 --- a/controllers/RecipesController.php +++ b/controllers/RecipesController.php @@ -46,15 +46,15 @@ class RecipesController extends BaseController 'recipes' => $recipes, 'internalRecipes' => $this->getDatabase()->recipes()->whereNot('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll(), 'recipesResolved' => $this->getRecipesService()->GetRecipesResolved(), - 'products' => $this->getDatabase()->products()->orderBy('name'), - 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'), + 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() ]); } public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - $recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name'); + $recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name', 'COLLATE NOCASE'); $recipesResolved = $this->getRecipesService()->GetRecipesResolved(); $selectedRecipe = null; @@ -99,7 +99,7 @@ class RecipesController extends BaseController if ($selectedRecipe) { - $selectedRecipeSubRecipes = $this->getDatabase()->recipes()->where('id IN (SELECT includes_recipe_id FROM recipes_nestings_resolved WHERE recipe_id = :1 AND includes_recipe_id != :1)', $selectedRecipe->id)->orderBy('name')->fetchAll(); + $selectedRecipeSubRecipes = $this->getDatabase()->recipes()->where('id IN (SELECT includes_recipe_id FROM recipes_nestings_resolved WHERE recipe_id = :1 AND includes_recipe_id != :1)', $selectedRecipe->id)->orderBy('name', 'COLLATE NOCASE')->fetchAll(); $includedRecipeIdsAbsolute = []; $includedRecipeIdsAbsolute[] = $selectedRecipe->id; @@ -132,11 +132,11 @@ class RecipesController extends BaseController 'recipe' => $this->getDatabase()->recipes($recipeId), 'recipePositions' => $this->getDatabase()->recipes_pos()->where('recipe_id', $recipeId), 'mode' => $recipeId == 'new' ? 'create' : 'edit', - 'products' => $this->getDatabase()->products()->orderBy('name'), + 'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'), 'quantityunits' => $this->getDatabase()->quantity_units(), 'recipePositionsResolved' => $this->getRecipesService()->GetRecipesPosResolved(), 'recipesResolved' => $this->getRecipesService()->GetRecipesResolved(), - 'recipes' => $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name'), + 'recipes' => $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name', 'COLLATE NOCASE'), 'recipeNestings' => $this->getDatabase()->recipes_nestings()->where('recipe_id', $recipeId), 'userfields' => $this->getUserfieldsService()->GetFields('recipes'), 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() @@ -151,8 +151,8 @@ class RecipesController extends BaseController 'mode' => 'create', 'recipe' => $this->getDatabase()->recipes($args['recipeId']), 'recipePos' => new \stdClass(), - 'products' => $this->getDatabase()->products()->orderBy('name'), - 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'), + 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() ]); } @@ -162,8 +162,8 @@ class RecipesController extends BaseController 'mode' => 'edit', 'recipe' => $this->getDatabase()->recipes($args['recipeId']), 'recipePos' => $this->getDatabase()->recipes_pos($args['recipePosId']), - 'products' => $this->getDatabase()->products()->orderBy('name'), - 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'), + 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() ]); } diff --git a/controllers/StockController.php b/controllers/StockController.php index e347c58c..73c7d8b7 100644 --- a/controllers/StockController.php +++ b/controllers/StockController.php @@ -14,9 +14,9 @@ class StockController extends BaseController return $this->renderPage($response, 'consume', [ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), 'barcodes' => $productBarcodes, - 'recipes' => $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name'), - 'locations' => $this->getDatabase()->locations()->orderBy('name'), - 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'recipes' => $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name', 'COLLATE NOCASE'), + 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'), + 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() ]); } @@ -27,29 +27,34 @@ class StockController extends BaseController $productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); return $this->renderPage($response, 'inventory', [ - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), 'barcodes' => $productBarcodes, - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), - 'locations' => $this->getDatabase()->locations()->orderBy('name'), - 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'), + 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'), + 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() ]); } public function Journal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { + $usersService = $this->getUsersService(); + return $this->renderPage($response, 'stockjournal', [ 'stockLog' => $this->getDatabase()->uihelper_stock_journal()->orderBy('row_created_timestamp', 'DESC'), - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), + 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'), + 'users' => $usersService->GetUsersAsDto(), + 'transactionTypes' => GetClassConstants('\Grocy\Services\StockService', 'TRANSACTION_TYPE_'), ]); } public function LocationContentSheet(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'locationcontentsheet', [ - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'locations' => $this->getDatabase()->locations()->orderBy('name'), + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), + 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'), 'currentStockLocationContent' => $this->getStockService()->GetCurrentStockLocationContent() ]); } @@ -76,7 +81,7 @@ class StockController extends BaseController public function LocationsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'locations', [ - 'locations' => $this->getDatabase()->locations()->orderBy('name'), + 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'), 'userfields' => $this->getUserfieldsService()->GetFields('locations'), 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('locations') ]); @@ -89,10 +94,10 @@ class StockController extends BaseController return $this->renderPage($response, 'stockoverview', [ 'currentStock' => $this->getStockService()->GetCurrentStockOverview(), - 'locations' => $this->getDatabase()->locations()->orderBy('name'), + 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'), 'currentStockLocations' => $this->getStockService()->GetCurrentStockLocations(), 'nextXDays' => $nextXDays, - 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), + 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'), 'userfields' => $this->getUserfieldsService()->GetFields('products'), 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products') ]); @@ -113,9 +118,10 @@ class StockController extends BaseController 'mode' => 'create', 'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'), 'product' => $product, - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), - 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'), + 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), + 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(), + 'userfields' => $this->getUserfieldsService()->GetFields('product_barcodes') ]); } else @@ -124,9 +130,10 @@ class StockController extends BaseController 'mode' => 'edit', 'barcode' => $this->getDatabase()->product_barcodes($args['productBarcodeId']), 'product' => $product, - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), - 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'), + 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), + 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(), + 'userfields' => $this->getUserfieldsService()->GetFields('product_barcodes') ]); } } @@ -138,11 +145,11 @@ class StockController extends BaseController return $this->renderPage($response, 'productform', [ 'locations' => $this->getDatabase()->locations()->orderBy('name'), 'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), - 'productgroups' => $this->getDatabase()->product_groups()->orderBy('name'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'), + 'productgroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'), 'userfields' => $this->getUserfieldsService()->GetFields('products'), - 'products' => $this->getDatabase()->products()->where('parent_product_id IS NULL and active = 1')->orderBy('name'), + 'products' => $this->getDatabase()->products()->where('parent_product_id IS NULL and active = 1')->orderBy('name', 'COLLATE NOCASE'), 'isSubProductOfOthers' => false, 'mode' => 'create' ]); @@ -153,16 +160,18 @@ class StockController extends BaseController return $this->renderPage($response, 'productform', [ 'product' => $product, - 'locations' => $this->getDatabase()->locations()->orderBy('name'), + 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'), 'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), - 'productgroups' => $this->getDatabase()->product_groups()->orderBy('name'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'), + 'productgroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'), 'userfields' => $this->getUserfieldsService()->GetFields('products'), - 'products' => $this->getDatabase()->products()->where('id != :1 AND parent_product_id IS NULL and active = 1', $product->id)->orderBy('name'), + 'products' => $this->getDatabase()->products()->where('id != :1 AND parent_product_id IS NULL and active = 1', $product->id)->orderBy('name', 'COLLATE NOCASE'), 'isSubProductOfOthers' => $this->getDatabase()->products()->where('parent_product_id = :1', $product->id)->count() !== 0, 'mode' => 'edit', - 'quConversions' => $this->getDatabase()->quantity_unit_conversions() + 'quConversions' => $this->getDatabase()->quantity_unit_conversions(), + 'productBarcodeUserfields' => $this->getUserfieldsService()->GetFields('product_barcodes'), + 'productBarcodeUserfieldValues' => $this->getUserfieldsService()->GetAllValues('product_barcodes') ]); } } @@ -189,8 +198,8 @@ class StockController extends BaseController public function ProductGroupsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'productgroups', [ - 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), + 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'), + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), 'userfields' => $this->getUserfieldsService()->GetFields('product_groups'), 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('product_groups') ]); @@ -199,10 +208,10 @@ class StockController extends BaseController public function ProductsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'products', [ - 'products' => $this->getDatabase()->products()->orderBy('name'), - 'locations' => $this->getDatabase()->locations()->orderBy('name'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), + 'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'), + 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), + 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'), 'userfields' => $this->getUserfieldsService()->GetFields('products'), 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products') ]); @@ -214,11 +223,11 @@ class StockController extends BaseController $productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); return $this->renderPage($response, 'purchase', [ - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), 'barcodes' => $productBarcodes, - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), - 'locations' => $this->getDatabase()->locations()->orderBy('name'), - 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'), + 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'), + 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() ]); } @@ -244,7 +253,7 @@ class StockController extends BaseController return $this->renderPage($response, 'quantityunitconversionform', [ 'mode' => 'create', 'userfields' => $this->getUserfieldsService()->GetFields('quantity_unit_conversions'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), 'product' => $product, 'defaultQuUnit' => $defaultQuUnit ]); @@ -255,7 +264,7 @@ class StockController extends BaseController 'quConversion' => $this->getDatabase()->quantity_unit_conversions($args['quConversionId']), 'mode' => 'edit', 'userfields' => $this->getUserfieldsService()->GetFields('quantity_unit_conversions'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), 'product' => $product, 'defaultQuUnit' => $defaultQuUnit ]); @@ -292,14 +301,14 @@ class StockController extends BaseController public function QuantityUnitPluralFormTesting(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'quantityunitpluraltesting', [ - 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name') + 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE') ]); } public function QuantityUnitsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'quantityunits', [ - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), 'userfields' => $this->getUserfieldsService()->GetFields('quantity_units'), 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('quantity_units') ]); @@ -316,15 +325,17 @@ class StockController extends BaseController return $this->renderPage($response, 'shoppinglist', [ 'listItems' => $this->getDatabase()->shopping_list()->where('shopping_list_id = :1', $listId), - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), 'missingProducts' => $this->getStockService()->GetMissingProducts(), - 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), - 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'), + 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'), + 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name', 'COLLATE NOCASE'), 'selectedShoppingListId' => $listId, 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(), - 'userfields' => $this->getUserfieldsService()->GetFields('products'), - 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products') + 'productUserfields' => $this->getUserfieldsService()->GetFields('products'), + 'productUserfieldValues' => $this->getUserfieldsService()->GetAllValues('products'), + 'userfields' => $this->getUserfieldsService()->GetFields('shopping_list'), + 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('shopping_list') ]); } @@ -333,14 +344,16 @@ class StockController extends BaseController if ($args['listId'] == 'new') { return $this->renderPage($response, 'shoppinglistform', [ - 'mode' => 'create' + 'mode' => 'create', + 'userfields' => $this->getUserfieldsService()->GetFields('shopping_lists') ]); } else { return $this->renderPage($response, 'shoppinglistform', [ 'shoppingList' => $this->getDatabase()->shopping_lists($args['listId']), - 'mode' => 'edit' + 'mode' => 'edit', + 'userfields' => $this->getUserfieldsService()->GetFields('shopping_lists') ]); } } @@ -350,22 +363,24 @@ class StockController extends BaseController if ($args['itemId'] == 'new') { return $this->renderPage($response, 'shoppinglistitemform', [ - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'), + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), + 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name', 'COLLATE NOCASE'), 'mode' => 'create', - 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() + 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), + 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(), + 'userfields' => $this->getUserfieldsService()->GetFields('shopping_list') ]); } else { return $this->renderPage($response, 'shoppinglistitemform', [ 'listItem' => $this->getDatabase()->shopping_list($args['itemId']), - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'), + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), + 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name', 'COLLATE NOCASE'), 'mode' => 'edit', - 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() + 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), + 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(), + 'userfields' => $this->getUserfieldsService()->GetFields('shopping_list') ]); } } @@ -397,7 +412,7 @@ class StockController extends BaseController public function ShoppingLocationsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'shoppinglocations', [ - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'), 'userfields' => $this->getUserfieldsService()->GetFields('shopping_locations'), 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('shopping_locations') ]); @@ -407,18 +422,18 @@ class StockController extends BaseController { return $this->renderPage($response, 'stockentryform', [ 'stockEntry' => $this->getDatabase()->stock()->where('id', $args['entryId'])->fetch(), - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), - 'locations' => $this->getDatabase()->locations()->orderBy('name') + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'), + 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE') ]); } public function StockSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'stocksettings', [ - 'locations' => $this->getDatabase()->locations()->orderBy('name'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name') + 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), + 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE') ]); } @@ -428,10 +443,10 @@ class StockController extends BaseController $nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['stock_due_soon_days']; return $this->renderPage($response, 'stockentries', [ - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'locations' => $this->getDatabase()->locations()->orderBy('name'), - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), + 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'), + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'), 'stockEntries' => $this->getDatabase()->stock()->orderBy('product_id'), 'currentStockLocations' => $this->getStockService()->GetCurrentStockLocations(), 'nextXDays' => $nextXDays, @@ -446,10 +461,10 @@ class StockController extends BaseController $productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); return $this->renderPage($response, 'transfer', [ - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), 'barcodes' => $productBarcodes, - 'locations' => $this->getDatabase()->locations()->orderBy('name'), - 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'), + 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'), 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() ]); } @@ -474,8 +489,13 @@ class StockController extends BaseController { $entries = $entries->where('transaction_type', $request->getQueryParams()['transaction_type']); } + + $usersService = $this->getUsersService(); return $this->renderPage($response, 'stockjournalsummary', [ - 'entries' => $entries + 'entries' => $entries, + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'), + 'users' => $usersService->GetUsersAsDto(), + 'transactionTypes' => GetClassConstants('\Grocy\Services\StockService', 'TRANSACTION_TYPE_') ]); } } diff --git a/controllers/TasksController.php b/controllers/TasksController.php index 97a680b5..7adca972 100644 --- a/controllers/TasksController.php +++ b/controllers/TasksController.php @@ -8,7 +8,7 @@ class TasksController extends BaseController { if (isset($request->getQueryParams()['include_done'])) { - $tasks = $this->getDatabase()->tasks()->orderBy('name'); + $tasks = $this->getDatabase()->tasks()->orderBy('name', 'COLLATE NOCASE'); } else { @@ -21,7 +21,7 @@ class TasksController extends BaseController return $this->renderPage($response, 'tasks', [ 'tasks' => $tasks, 'nextXDays' => $nextXDays, - 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name'), + 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name', 'COLLATE NOCASE'), 'users' => $this->getDatabase()->users(), 'userfields' => $this->getUserfieldsService()->GetFields('tasks'), 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('tasks') @@ -31,7 +31,7 @@ class TasksController extends BaseController public function TaskCategoriesList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'taskcategories', [ - 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name'), + 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name', 'COLLATE NOCASE'), 'userfields' => $this->getUserfieldsService()->GetFields('task_categories'), 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('task_categories') ]); @@ -62,7 +62,7 @@ class TasksController extends BaseController { return $this->renderPage($response, 'taskform', [ 'mode' => 'create', - 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name'), + 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name', 'COLLATE NOCASE'), 'users' => $this->getDatabase()->users()->orderBy('username'), 'userfields' => $this->getUserfieldsService()->GetFields('tasks') ]); @@ -72,7 +72,7 @@ class TasksController extends BaseController return $this->renderPage($response, 'taskform', [ 'task' => $this->getDatabase()->tasks($args['taskId']), 'mode' => 'edit', - 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name'), + 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name', 'COLLATE NOCASE'), 'users' => $this->getDatabase()->users()->orderBy('username'), 'userfields' => $this->getUserfieldsService()->GetFields('tasks') ]); diff --git a/grocy.openapi.json b/grocy.openapi.json index bdf4fd3a..53f97c4c 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -239,6 +239,9 @@ }, { "$ref": "#/components/schemas/StockEntry" + }, + { + "$ref": "#/components/schemas/ProductBarcode" } ] } @@ -311,6 +314,9 @@ }, { "$ref": "#/components/schemas/StockEntry" + }, + { + "$ref": "#/components/schemas/ProductBarcode" } ] } @@ -402,6 +408,9 @@ }, { "$ref": "#/components/schemas/StockEntry" + }, + { + "$ref": "#/components/schemas/ProductBarcode" } ] } @@ -482,6 +491,9 @@ }, { "$ref": "#/components/schemas/StockEntry" + }, + { + "$ref": "#/components/schemas/ProductBarcode" } ] } @@ -4129,7 +4141,7 @@ "$ref": "#/components/schemas/Product" }, "product_barcodes": { - "$ref": "#/components/schemas/ProductBarcodeDetailsResponse" + "$ref": "#/components/schemas/ProductBarcode" }, "default_quantity_unit_purchase": { "$ref": "#/components/schemas/QuantityUnit" @@ -4257,14 +4269,14 @@ }, "price": { "type": "number", - "format": "number" + "format": "float" }, "shopping_location": { "$ref": "#/components/schemas/ShoppingLocation" } } }, - "ProductBarcodeDetailsResponse": { + "ProductBarcode": { "type": "object", "properties": { "product_id": { @@ -4282,6 +4294,10 @@ "amount": { "type": "number", "format": "number" + }, + "last_price": { + "type": "number", + "format": "float" } } }, diff --git a/localization/strings.pot b/localization/strings.pot index 8252a03a..6b37a831 100644 --- a/localization/strings.pot +++ b/localization/strings.pot @@ -889,9 +889,6 @@ msgstr "" msgid "Shopping list to stock workflow" msgstr "" -msgid "Automatically do the booking using the last price and the amount of the shopping list item, if the product has \"Default best before days\" set" -msgstr "" - msgid "Skip" msgstr "" @@ -1975,3 +1972,6 @@ msgstr "" msgid "This must between %1$s and %2$s and needs to be a valid number with max. %3$s decimal places" msgstr "" + +msgid "Automatically do the booking using the last price and the amount of the shopping list item, if the product has \"Default due days\" set" +msgstr "" diff --git a/migrations/0103.sql b/migrations/0103.sql index 4b6c7fe9..e5f2650c 100644 --- a/migrations/0103.sql +++ b/migrations/0103.sql @@ -16,6 +16,7 @@ CREATE TABLE product_barcodes ( qu_id INT, amount REAL, shopping_location_id INTEGER, + last_price DECIMAL(15, 2), row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) ); diff --git a/public/js/grocy.js b/public/js/grocy.js index 70a4d593..67fd29ee 100644 --- a/public/js/grocy.js +++ b/public/js/grocy.js @@ -424,7 +424,7 @@ Grocy.FrontendHelpers.ShowGenericError = function(message, exception) { bootbox.alert({ title: __t('Error details'), - message: JSON.stringify(exception, null, 4), + message: '
' + JSON.stringify(exception, null, 4) + '
', closeButton: false }); } @@ -448,6 +448,11 @@ $(document).on("change", ".user-setting-control", function() var element = $(this); var settingKey = element.attr("data-setting-key"); + if (!element[0].checkValidity()) + { + return; + } + var inputType = "unknown"; if (typeof element.attr("type") !== typeof undefined && element.attr("type") !== false) { diff --git a/public/viewjs/choreform.js b/public/viewjs/choreform.js index 3d3f0a05..70bf1f3e 100644 --- a/public/viewjs/choreform.js +++ b/public/viewjs/choreform.js @@ -133,7 +133,7 @@ $('.input-group-chore-period-type').on('change', function(e) { $("label[for='period_days']").text(__t("Period days")); $("#period_days").attr("min", "0"); - $("#period_days").attr("max", "999999"); + $("#period_days").removeAttr("max"); $('#chore-period-type-info').attr("data-original-title", __t('This means the next execution of this chore is scheduled %s days after the last execution', periodDays.toString())); } else if (periodType === 'daily') diff --git a/public/viewjs/components/numberpicker.js b/public/viewjs/components/numberpicker.js index 59c0097b..c52e9d02 100644 --- a/public/viewjs/components/numberpicker.js +++ b/public/viewjs/components/numberpicker.js @@ -32,30 +32,38 @@ $(".numberpicker").each(function() { mutations.forEach(function(mutation) { - if (mutation.type == "attributes" && (mutation.attributeName == "min" || mutation.attributeName == "max" || mutation.attributeName == "data-not-equal")) + if (mutation.type == "attributes" && (mutation.attributeName == "min" || mutation.attributeName == "max" || mutation.attributeName == "data-not-equal" || mutation.attributeName == "data-initialised")) { var element = $(mutation.target); var min = element.attr("min"); - var max = element.attr("max"); var decimals = element.attr("data-decimals"); + var max = ""; + if (element.hasAttr("max")) + { + max = element.attr("max"); + } + if (element.hasAttr("data-not-equal")) { var notEqual = element.attr("data-not-equal"); - if (max.isEmpty() || max.startsWith("999999")) + if (notEqual != "NaN") { - element.parent().find(".invalid-feedback").text(__t("This cannot be lower than %1$s or equal %2$s and needs to be a valid number with max. %3$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(notEqual).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), decimals)); - } - else - { - element.parent().find(".invalid-feedback").text(__t("This must be between %1$s and %2$s, cannot equal %3$s and needs to be a valid number with max. %4$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(max).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(notEqual).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }), decimals)); - } + if (max.isEmpty()) + { + element.parent().find(".invalid-feedback").text(__t("This cannot be lower than %1$s or equal %2$s and needs to be a valid number with max. %3$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(notEqual).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), decimals)); + } + else + { + element.parent().find(".invalid-feedback").text(__t("This must be between %1$s and %2$s, cannot equal %3$s and needs to be a valid number with max. %4$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(max).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(notEqual).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }), decimals)); + } - return; + return; + } } - if (max.isEmpty() || max.startsWith("999999")) + if (max.isEmpty()) { element.parent().find(".invalid-feedback").text(__t("This cannot be lower than %1$s and needs to be a valid number with max. %2$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), decimals)); } @@ -69,4 +77,4 @@ $(".numberpicker").each(function() attributes: true }); }); -$(".numberpicker").attr("min", $(".numberpicker").attr("min")); // Dummy change to trigger MutationObserver above once +$(".numberpicker").attr("data-initialised", "true"); // Dummy change to trigger MutationObserver above once diff --git a/public/viewjs/consume.js b/public/viewjs/consume.js index 256c77f1..e3462436 100644 --- a/public/viewjs/consume.js +++ b/public/viewjs/consume.js @@ -93,7 +93,7 @@ Grocy.Components.ProductAmountPicker.Reset(); $("#display_amount").attr("min", "0." + "0".repeat(parseInt(Grocy.UserSettings.stock_decimal_places_amounts) - 1) + "1"); - $("#display_amount").attr("max", "999999"); + $("#display_amount").removeAttr("max"); $('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_consume_amount)); RefreshLocaleNumberInput(); $(".input-group-productamountpicker").trigger("change"); @@ -272,6 +272,8 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e) Grocy.Api.Get('stock/products/' + productId, function(productDetails) { + current_productDetails = productDetails; + Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id); Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.quantity_unit_stock.id); $('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_consume_amount)); @@ -473,6 +475,11 @@ $("#use_specific_stock_entry").on("change", function() Grocy.FrontendHelpers.ValidateForm("consume-form"); }); +$("#qu_id").on("change", function() +{ + RefreshForm(); +}); + function UndoStockBooking(bookingId) { Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {}, @@ -570,7 +577,7 @@ function RefreshForm() $("#tare-weight-handling-info").addClass("d-none"); $("#display_amount").attr("min", "0." + "0".repeat(parseInt(Grocy.UserSettings.stock_decimal_places_amounts) - 1) + "1"); - $('#display_amount').attr('max', sumValue); + $('#display_amount').attr('max', sumValue * $("#qu_id option:selected").attr("data-qu-factor")); if (sumValue == 0) { diff --git a/public/viewjs/inventory.js b/public/viewjs/inventory.js index 2250f79c..0e9a4dec 100644 --- a/public/viewjs/inventory.js +++ b/public/viewjs/inventory.js @@ -84,6 +84,7 @@ $("#tare-weight-handling-info").addClass("d-none"); $("#display_amount").attr("min", "0"); $('#display_amount').val(''); + $('#display_amount').removeAttr("data-not-equal"); $(".input-group-productamountpicker").trigger("change"); $('#price').val(''); Grocy.Components.DateTimePicker.Clear(); diff --git a/public/viewjs/productbarcodeform.js b/public/viewjs/productbarcodeform.js index b7cb0feb..02c7bb26 100644 --- a/public/viewjs/productbarcodeform.js +++ b/public/viewjs/productbarcodeform.js @@ -13,6 +13,11 @@ Grocy.Api.Post('objects/product_barcodes', jsonData, function(result) { + Grocy.EditObjectId = result.created_object_id; + Grocy.Components.UserfieldsForm.Save() + + window.parent.postMessage(WindowMessageBag("ProductBarcodesChanged"), U("/product/" + GetUriParam("product"))); + window.parent.postMessage(WindowMessageBag("CloseAllModals"), U("/product/" + GetUriParam("product"))); }, function(xhr) { @@ -23,9 +28,12 @@ } else { + Grocy.Components.UserfieldsForm.Save(); Grocy.Api.Put('objects/product_barcodes/' + Grocy.EditObjectId, jsonData, function(result) { + window.parent.postMessage(WindowMessageBag("ProductBarcodesChanged"), U("/product/" + GetUriParam("product"))); + window.parent.postMessage(WindowMessageBag("CloseAllModals"), U("/product/" + GetUriParam("product"))); }, function(xhr) { @@ -34,9 +42,6 @@ } ); } - - window.parent.postMessage(WindowMessageBag("ProductBarcodesChanged"), U("/product/" + GetUriParam("product"))); - window.parent.postMessage(WindowMessageBag("CloseAllModals"), U("/product/" + GetUriParam("product"))); }); $('#barcode').on('keyup', function(e) @@ -82,3 +87,4 @@ if (Grocy.EditMode == "edit") Grocy.FrontendHelpers.ValidateForm('barcode-form'); $('#barcode').focus(); RefreshLocaleNumberInput(); +Grocy.Components.UserfieldsForm.Load() diff --git a/public/viewjs/productform.js b/public/viewjs/productform.js index e845791b..53799159 100644 --- a/public/viewjs/productform.js +++ b/public/viewjs/productform.js @@ -296,7 +296,7 @@ var quConversionsTable = $('#qu-conversions-table-products').DataTable({ dataSrc: 4 } }); -$('#qu-conversions-table tbody').removeClass("d-none"); +$('#qu-conversions-table-products tbody').removeClass("d-none"); quConversionsTable.columns.adjust().draw(); var barcodeTable = $('#barcode-table').DataTable({ diff --git a/public/viewjs/purchase.js b/public/viewjs/purchase.js index 882cfffb..b3538482 100644 --- a/public/viewjs/purchase.js +++ b/public/viewjs/purchase.js @@ -55,6 +55,16 @@ $('#save-purchase-button').on('click', function(e) Grocy.Api.Post('stock/products/' + jsonForm.product_id + '/add', jsonData, function(result) { + if ($("#purchase-form").hasAttr("data-used-barcode")) + { + Grocy.Api.Put('objects/product_barcodes/' + $("#purchase-form").attr("data-used-barcode"), { last_price: $("#price").val() / $("#display_amount").val() }, + function(result) + { }, + function(xhr) + { } + ); + } + if (BoolVal(Grocy.UserSettings.scan_mode_purchase_enabled)) { Grocy.UISound.Success(); @@ -108,6 +118,7 @@ $('#save-purchase-button').on('click', function(e) } Grocy.Components.ProductAmountPicker.Reset(); + $("#purchase-form").removeAttr("data-used-barcode"); $("#display_amount").attr("min", "0." + "0".repeat(parseInt(Grocy.UserSettings.stock_decimal_places_amounts) - 1) + "1"); $('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_purchase_amount)); $(".input-group-productamountpicker").trigger("change"); @@ -202,7 +213,7 @@ if (Grocy.Components.ProductPicker !== undefined) Grocy.Components.LocationPicker.SetId(productDetails.location.id); } - if (productDetails.last_price == null) + if (productDetails.last_price == null || productDetails.last_price == 0) { $("#price").val("") } @@ -282,10 +293,11 @@ if (Grocy.Components.ProductPicker !== undefined) if (barcodeResult != null) { var barcode = barcodeResult[0]; + $("#purchase-form").attr("data-used-barcode", barcode.id); if (barcode != null) { - if (barcode.amount != null) + if (barcode.amount != null && !barcode.amount.isEmpty()) { $("#display_amount").val(barcode.amount); $("#display_amount").select(); @@ -301,6 +313,12 @@ if (Grocy.Components.ProductPicker !== undefined) Grocy.Components.ShoppingLocationPicker.SetId(barcode.shopping_location_id); } + if (barcode.last_price != null && !barcode.last_price.isEmpty()) + { + $("#price").val(barcode.last_price); + $("#price-type-total-price").click(); + } + $(".input-group-productamountpicker").trigger("change"); Grocy.FrontendHelpers.ValidateForm('purchase-form'); } @@ -312,6 +330,10 @@ if (Grocy.Components.ProductPicker !== undefined) } ); } + else + { + $("#purchase-form").removeAttr("data-used-barcode"); + } $('#display_amount').trigger("keyup"); }, @@ -450,7 +472,7 @@ function refreshPriceHint() price = parseFloat(price / $('#display_amount').val()).toFixed(Grocy.UserSettings.stock_decimal_places_prices); } - $('#price-hint').text(__t('means %1$s per %2$s', price.toLocaleString(undefined, { style: "currency", currency: Grocy.Currency, minimumFractionDigits: 2, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices }), $("#qu_id").attr("data-destination-qu-name"))); + $('#price-hint').text(__t('means %1$s per %2$s', price.toLocaleString(undefined, { style: "currency", currency: Grocy.Currency, minimumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices }), $("#qu_id").attr("data-destination-qu-name"))); } else { diff --git a/public/viewjs/recipeform.js b/public/viewjs/recipeform.js index 65aeee3c..44c22346 100644 --- a/public/viewjs/recipeform.js +++ b/public/viewjs/recipeform.js @@ -285,6 +285,7 @@ $("#recipe-include-add-button").on("click", function(e) $("#recipe-include-editform-title").text(__t("Add included recipe")); $("#recipe-include-form").data("edit-mode", "create"); Grocy.Components.RecipePicker.Clear(); + Grocy.Components.RecipePicker.GetInputElement().focus(); $("#recipe-include-editform-modal").modal("show"); Grocy.FrontendHelpers.ValidateForm("recipe-include-form"); }, diff --git a/public/viewjs/shoppinglistform.js b/public/viewjs/shoppinglistform.js index fbfe4836..62b00759 100644 --- a/public/viewjs/shoppinglistform.js +++ b/public/viewjs/shoppinglistform.js @@ -10,9 +10,13 @@ Grocy.Api.Post('objects/shopping_lists', jsonData, function(result) { - window.parent.postMessage(WindowMessageBag("ShoppingListChanged", result.created_object_id), Grocy.BaseUrl); - window.parent.postMessage(WindowMessageBag("Ready"), Grocy.BaseUrl); - window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl); + Grocy.EditObjectId = result.created_object_id; + Grocy.Components.UserfieldsForm.Save(function() + { + window.parent.postMessage(WindowMessageBag("ShoppingListChanged", result.created_object_id), Grocy.BaseUrl); + window.parent.postMessage(WindowMessageBag("Ready"), Grocy.BaseUrl); + window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl); + }); }, function(xhr) { @@ -23,19 +27,22 @@ } else { - Grocy.Api.Put('objects/shopping_lists/' + Grocy.EditObjectId, jsonData, - function(result) - { - window.parent.postMessage(WindowMessageBag("ShoppingListChanged", Grocy.EditObjectId), Grocy.BaseUrl); - window.parent.postMessage(WindowMessageBag("Ready"), Grocy.BaseUrl); - window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl); - }, - function(xhr) - { - Grocy.FrontendHelpers.EndUiBusy("shopping-list-form"); - Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response); - } - ); + Grocy.Components.UserfieldsForm.Save(function() + { + Grocy.Api.Put('objects/shopping_lists/' + Grocy.EditObjectId, jsonData, + function(result) + { + window.parent.postMessage(WindowMessageBag("ShoppingListChanged", Grocy.EditObjectId), Grocy.BaseUrl); + window.parent.postMessage(WindowMessageBag("Ready"), Grocy.BaseUrl); + window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl); + }, + function(xhr) + { + Grocy.FrontendHelpers.EndUiBusy("shopping-list-form"); + Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response); + } + ); + }); } }); @@ -61,5 +68,6 @@ $('#shopping-list-form input').keydown(function(event) } }); +Grocy.Components.UserfieldsForm.Load(); $('#name').focus(); Grocy.FrontendHelpers.ValidateForm('shopping-list-form'); diff --git a/public/viewjs/shoppinglistitemform.js b/public/viewjs/shoppinglistitemform.js index 76daeac9..1c817a29 100644 --- a/public/viewjs/shoppinglistitemform.js +++ b/public/viewjs/shoppinglistitemform.js @@ -21,6 +21,9 @@ $('#save-shoppinglist-button').on('click', function(e) Grocy.Api.Post('stock/shoppinglist/add-product', jsonData, function(result) { + Grocy.EditObjectId = result.created_object_id; + Grocy.Components.UserfieldsForm.Save(); + if (GetUriParam("embedded") !== undefined) { Grocy.Api.Get('stock/products/' + jsonData.product_id, @@ -54,6 +57,9 @@ $('#save-shoppinglist-button').on('click', function(e) Grocy.Api.Post('objects/shopping_list', jsonData, function(result) { + Grocy.EditObjectId = result.created_object_id; + Grocy.Components.UserfieldsForm.Save(); + if (GetUriParam("embedded") !== undefined) { if (jsonData.product_id) @@ -94,6 +100,8 @@ $('#save-shoppinglist-button').on('click', function(e) Grocy.Api.Put('objects/shopping_list/' + Grocy.EditObjectId, jsonData, function(result) { + Grocy.Components.UserfieldsForm.Save(); + if (GetUriParam("embedded") !== undefined) { if (jsonData.product_id) @@ -216,3 +224,5 @@ if (GetUriParam("amount") !== undefined) $(".input-group-productamountpicker").trigger("change"); Grocy.FrontendHelpers.ValidateForm('shoppinglist-form'); } + +Grocy.Components.UserfieldsForm.Load(); diff --git a/public/viewjs/stockjournal.js b/public/viewjs/stockjournal.js index f3615892..15df5599 100644 --- a/public/viewjs/stockjournal.js +++ b/public/viewjs/stockjournal.js @@ -23,6 +23,42 @@ $("#product-filter").on("change", function() stockJournalTable.column(1).search(text).draw(); }); +$("#transaction-type-filter").on("change", function() +{ + var value = $(this).val(); + var text = $("#transaction-type-filter option:selected").text(); + if (value === "all") + { + text = ""; + } + + stockJournalTable.column(4).search(text).draw(); +}); + +$("#location-filter").on("change", function() +{ + var value = $(this).val(); + var text = $("#location-filter option:selected").text(); + if (value === "all") + { + text = ""; + } + + stockJournalTable.column(5).search(text).draw(); +}); + +$("#user-filter").on("change", function() +{ + var value = $(this).val(); + var text = $("#user-filter option:selected").text(); + if (value === "all") + { + text = ""; + } + + stockJournalTable.column(6).search(text).draw(); +}); + $("#search").on("keyup", Delay(function() { var value = $(this).val(); @@ -37,6 +73,12 @@ $("#search").on("keyup", Delay(function() $("#clear-filter-button").on("click", function() { $("#search").val(""); + $("#transaction-type-filter").val("all"); + $("#location-filter").val("all"); + $("#user-filter").val("all"); + stockJournalTable.column(4).search("").draw(); + stockJournalTable.column(5).search("").draw(); + stockJournalTable.column(6).search("").draw(); stockJournalTable.search("").draw(); }); diff --git a/public/viewjs/stockjournalsummary.js b/public/viewjs/stockjournalsummary.js index 369d286e..61aace52 100644 --- a/public/viewjs/stockjournalsummary.js +++ b/public/viewjs/stockjournalsummary.js @@ -1,8 +1,71 @@ -var journalSummaryTable = $('#journal-summary-table').DataTable({ +var journalSummaryTable = $('#stock-journal-summary-table').DataTable({ 'paginate': true, - 'order': [[0, 'desc']] + 'order': [[1, 'asc']], + 'columnDefs': [ + { 'orderable': false, 'targets': 0 }, + { 'searchable': false, "targets": 0 } + ] }); -$('#journal-summary-table tbody').removeClass("d-none"); +$('#stock-journal-summary-table tbody').removeClass("d-none"); journalSummaryTable.columns.adjust().draw(); $('.dataTables_scrollBody').addClass("dragscroll"); dragscroll.reset(); + +$("#product-filter").on("change", function() +{ + var value = $(this).val(); + var text = $("#product-filter option:selected").text(); + if (value === "all") + { + text = ""; + } + + journalSummaryTable.column(1).search(text).draw(); +}); + +$("#transaction-type-filter").on("change", function() +{ + var value = $(this).val(); + var text = $("#transaction-type-filter option:selected").text(); + if (value === "all") + { + text = ""; + } + + journalSummaryTable.column(2).search(text).draw(); +}); + +$("#user-filter").on("change", function() +{ + var value = $(this).val(); + var text = $("#user-filter option:selected").text(); + if (value === "all") + { + text = ""; + } + + journalSummaryTable.column(3).search(text).draw(); +}); + +$("#search").on("keyup", Delay(function() +{ + var value = $(this).val(); + if (value === "all") + { + value = ""; + } + + journalSummaryTable.search(value).draw(); +}, 200)); + +$("#clear-filter-button").on("click", function() +{ + $("#search").val(""); + $("#transaction-type-filter").val("all"); + $("#location-filter").val("all"); + $("#user-filter").val("all"); + journalSummaryTable.column(1).search("").draw(); + journalSummaryTable.column(2).search("").draw(); + journalSummaryTable.column(3).search("").draw(); + journalSummaryTable.search("").draw(); +}); diff --git a/public/viewjs/transfer.js b/public/viewjs/transfer.js index db2ea93a..a4a44f22 100644 --- a/public/viewjs/transfer.js +++ b/public/viewjs/transfer.js @@ -90,7 +90,7 @@ Grocy.Components.ProductAmountPicker.Reset(); $("#location_id_from").find("option").remove().end().append(""); $("#display_amount").attr("min", "0." + "0".repeat(parseInt(Grocy.UserSettings.stock_decimal_places_amounts) - 1) + "1"); - $("#display_amount").attr("max", "999999"); + $("#display_amount").removeAttr("max"); $('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_transfer_amount)); RefreshLocaleNumberInput(); $(".input-group-productamountpicker").trigger("change"); @@ -198,6 +198,8 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e) $("#tare-weight-handling-info").addClass("d-none"); } + $('#display_amount').attr("data-stock-amount", productDetails.stock_amount); + if ((parseFloat(productDetails.stock_amount) || 0) === 0) { Grocy.Components.ProductPicker.Clear(); @@ -274,7 +276,7 @@ $("#location_id_from").on('change', function(e) sumValue = sumValue + parseFloat(stockEntry.amount); } }); - $("#display_amount").attr("max", sumValue); + $("#display_amount").attr("max", sumValue * $("#qu_id option:selected").attr("data-qu-factor")); if (sumValue == 0) { $("#display_amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location')); @@ -299,6 +301,11 @@ $("#location_id_to").on('change', function(e) } }); +$("#qu_id").on('change', function(e) +{ + $("#display_amount").attr("max", parseFloat($('#display_amount').attr("data-stock-amount")) * $("#qu_id option:selected").attr("data-qu-factor")); +}); + $('#display_amount').on('focus', function(e) { $(this).select(); @@ -346,7 +353,7 @@ $("#specific_stock_entry").on("change", function(e) sumValue = sumValue + parseFloat(stockEntry.amount); } }); - $("#display_amount").attr("max", sumValue); + $("#display_amount").attr("max", sumValue * $("#qu_id option:selected").attr("data-qu-factor")); if (sumValue == 0) { $("#display_amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location')); diff --git a/services/UserfieldsService.php b/services/UserfieldsService.php index fe5b8372..8383e9b3 100644 --- a/services/UserfieldsService.php +++ b/services/UserfieldsService.php @@ -21,7 +21,7 @@ class UserfieldsService extends BaseService public function GetAllFields() { - return $this->getDatabase()->userfields()->orderBy('name')->fetchAll(); + return $this->getDatabase()->userfields()->orderBy('name', 'COLLATE NOCASE')->fetchAll(); } public function GetAllValues($entity) @@ -31,7 +31,7 @@ class UserfieldsService extends BaseService throw new \Exception('Entity does not exist or is not exposed'); } - return $this->getDatabase()->userfield_values_resolved()->where('entity', $entity)->orderBy('name')->fetchAll(); + return $this->getDatabase()->userfield_values_resolved()->where('entity', $entity)->orderBy('name', 'COLLATE NOCASE')->fetchAll(); } public function GetEntities() @@ -40,7 +40,7 @@ class UserfieldsService extends BaseService $userentities = []; - foreach ($this->getDatabase()->userentities()->orderBy('name') as $userentity) + foreach ($this->getDatabase()->userentities()->orderBy('name', 'COLLATE NOCASE') as $userentity) { $userentities[] = 'userentity-' . $userentity->name; } @@ -65,7 +65,7 @@ class UserfieldsService extends BaseService throw new \Exception('Entity does not exist or is not exposed'); } - return $this->getDatabase()->userfields()->where('entity', $entity)->orderBy('name')->fetchAll(); + return $this->getDatabase()->userfields()->where('entity', $entity)->orderBy('name', 'COLLATE NOCASE')->fetchAll(); } public function GetValues($entity, $objectId) @@ -75,7 +75,7 @@ class UserfieldsService extends BaseService throw new \Exception('Entity does not exist or is not exposed'); } - $userfields = $this->getDatabase()->userfield_values_resolved()->where('entity = :1 AND object_id = :2', $entity, $objectId)->orderBy('name')->fetchAll(); + $userfields = $this->getDatabase()->userfield_values_resolved()->where('entity = :1 AND object_id = :2', $entity, $objectId)->orderBy('name', 'COLLATE NOCASE')->fetchAll(); $userfieldKeyValuePairs = []; foreach ($userfields as $userfield) diff --git a/views/choreform.blade.php b/views/choreform.blade.php index d0aad6dc..53145fe9 100644 --- a/views/choreform.blade.php +++ b/views/choreform.blade.php @@ -177,7 +177,7 @@ diff --git a/views/productbarcodeform.blade.php b/views/productbarcodeform.blade.php index 90edaa0b..fae74ea7 100644 --- a/views/productbarcodeform.blade.php +++ b/views/productbarcodeform.blade.php @@ -90,6 +90,11 @@ value="1"> @endif + @include('components.userfieldsform', array( + 'userfields' => $userfields, + 'entity' => 'product_barcodes' + )) + diff --git a/views/productform.blade.php b/views/productform.blade.php index 39d602e9..0c399dcb 100644 --- a/views/productform.blade.php +++ b/views/productform.blade.php @@ -469,6 +469,10 @@ @endif {{ $__t('Quantity unit') }} {{ $__t('Amount') }} + + @include('components.userfields_thead', array( + 'userfields' => $productBarcodeUserfields + )) @@ -510,6 +514,11 @@ {{ $barcode->amount }} @endif + + @include('components.userfields_tbody', array( + 'userfields' => $productBarcodeUserfields, + 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($productBarcodeUserfieldValues, 'object_id', $barcode->id) + )) @endif @endforeach @@ -595,7 +604,7 @@ @endif - {{ $__t('This means 1 %1$s is the same as %2$s %3$s', FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->from_qu_id)->name, $quConversion->factor, FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->to_qu_id)->name) }} + {!! $__t('This means 1 %1$s is the same as %2$s %3$s', FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->from_qu_id)->name, '' . $quConversion->factor . '', FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->to_qu_id)->name) !!} @endif diff --git a/views/quantityunitform.blade.php b/views/quantityunitform.blade.php index 614ce14c..fdd11da5 100644 --- a/views/quantityunitform.blade.php +++ b/views/quantityunitform.blade.php @@ -103,6 +103,8 @@ -
- @@ -154,7 +153,7 @@ @@ -259,6 +262,10 @@ @include('components.userfields_tbody', array( 'userfields' => $userfields, + 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $listItem->id) + )) + @include('components.userfields_tbody', array( + 'userfields' => $productUserfields, 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $listItem->product_id) )) diff --git a/views/shoppinglistform.blade.php b/views/shoppinglistform.blade.php index 4bd997d4..b807067d 100644 --- a/views/shoppinglistform.blade.php +++ b/views/shoppinglistform.blade.php @@ -43,6 +43,11 @@
{{ $__t('A name is required') }}
+ @include('components.userfieldsform', array( + 'userfields' => $userfields, + 'entity' => 'shopping_lists' + )) + diff --git a/views/shoppinglistitemform.blade.php b/views/shoppinglistitemform.blade.php index 55549453..6ced9e4d 100644 --- a/views/shoppinglistitemform.blade.php +++ b/views/shoppinglistitemform.blade.php @@ -84,6 +84,11 @@ name="note">@if($mode == 'edit'){{ $listItem->note }}@endif + @include('components.userfieldsform', array( + 'userfields' => $userfields, + 'entity' => 'shopping_list' + )) + diff --git a/views/stockjournal.blade.php b/views/stockjournal.blade.php index c2414017..2e737d5d 100644 --- a/views/stockjournal.blade.php +++ b/views/stockjournal.blade.php @@ -34,7 +34,7 @@
-
+
@@ -45,7 +45,7 @@ placeholder="{{ $__t('Search') }}">
-
+
 {{ $__t('Product') }} @@ -59,8 +59,50 @@
+
+
+
+  {{ $__t('Transaction type') }} +
+ +
+
+
+
+
+  {{ $__t('Location') }} +
+ +
+
+
+
+
+  {{ $__t('User') }} +
+ +
+
- {{ $defaultQuConversion->factor }} + {{ $defaultQuConversion->factor }} {{ FindObjectInArrayByPropertyValue($quantityUnits, 'id', $defaultQuConversion->to_qu_id)->name }} diff --git a/views/recipeform.blade.php b/views/recipeform.blade.php index 21bd0eaa..ad17f57a 100644 --- a/views/recipeform.blade.php +++ b/views/recipeform.blade.php @@ -187,7 +187,7 @@ $productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id); $productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock); $productQuConversion = FindObjectInArrayByPropertyValue($productQuConversions, 'to_qu_id', $recipePosition->qu_id); - if ($productQuConversion) + if ($productQuConversion && $recipePosition->only_check_single_unit_in_stock == 0) { $recipePosition->amount = $recipePosition->amount * $productQuConversion->factor; } diff --git a/views/recipes.blade.php b/views/recipes.blade.php index dd6877a5..53a785ad 100644 --- a/views/recipes.blade.php +++ b/views/recipes.blade.php @@ -398,7 +398,7 @@ $productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id); $productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock); $productQuConversion = FindObjectInArrayByPropertyValue($productQuConversions, 'to_qu_id', $selectedRecipePosition->qu_id); - if ($productQuConversion) + if ($productQuConversion && $selectedRecipePosition->only_check_single_unit_in_stock == 0) { $selectedRecipePosition->recipe_amount = $selectedRecipePosition->recipe_amount * $productQuConversion->factor; } diff --git a/views/shoppinglist.blade.php b/views/shoppinglist.blade.php index 98255827..35b67cbe 100644 --- a/views/shoppinglist.blade.php +++ b/views/shoppinglist.blade.php @@ -49,7 +49,7 @@
+ + {{ $__t('Clear filter') }} + + + + + +
+
+
+ @@ -28,6 +109,7 @@ @foreach($entries as $journalEntry) + diff --git a/views/stocksettings.blade.php b/views/stocksettings.blade.php index 73918454..71a9ddf9 100644 --- a/views/stocksettings.blade.php +++ b/views/stocksettings.blade.php @@ -84,7 +84,7 @@ 'id' => 'stock_default_purchase_amount', 'additionalAttributes' => 'data-setting-key="stock_default_purchase_amount"', 'label' => 'Default amount for purchase', - 'min' => 0, + 'min' => '0.', 'decimals' => $userSettings['stock_decimal_places_amounts'], 'additionalCssClasses' => 'user-setting-control locale-number-input locale-number-quantity-amount', ))
+ {{ $__t('Product') }} {{ $__t('Transaction type') }} {{ $__t('User') }}
{{ $journalEntry->product_name }}