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 @@