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)
This commit is contained in:
Bernd Bestel
2020-11-17 19:11:02 +01:00
parent 1316c1f25f
commit 887526c727
40 changed files with 556 additions and 178 deletions

View File

@@ -8,6 +8,7 @@
### New feature: Prefill purchase data by barcodes ### New feature: Prefill purchase data by barcodes
- Imagine you buy for example eggs in different pack sizes and they have different 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 - 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) - (Thanks @kriddles for the initial work on this)
### New feature: User permissions ### 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) - 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) - 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`) - 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) - 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 - 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) - 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) - 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) - 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) - 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 - 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 ### Recipe improvements/fixes
@@ -130,6 +133,7 @@
- Added a "Clear filter"-button on all pages (with filters) to quickly reset applied filters - 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) - 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 - 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) - 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 environment variable `GROCY_DATAPATH` (higher priority)
- Via an FastCGI parameter `GROCY_DATAPATH` (lower priority) - Via an FastCGI parameter `GROCY_DATAPATH` (lower priority)

View File

@@ -7,7 +7,7 @@ class BatteriesController extends BaseController
public function BatteriesList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) public function BatteriesList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'batteries', [ return $this->renderPage($response, 'batteries', [
'batteries' => $this->getDatabase()->batteries()->orderBy('name'), 'batteries' => $this->getDatabase()->batteries()->orderBy('name', 'COLLATE NOCASE'),
'userfields' => $this->getUserfieldsService()->GetFields('batteries'), 'userfields' => $this->getUserfieldsService()->GetFields('batteries'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('batteries') 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('batteries')
]); ]);
@@ -41,7 +41,7 @@ class BatteriesController extends BaseController
{ {
return $this->renderPage($response, 'batteriesjournal', [ return $this->renderPage($response, 'batteriesjournal', [
'chargeCycles' => $this->getDatabase()->battery_charge_cycles()->orderBy('tracked_time', 'DESC'), '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']; $nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['batteries_due_soon_days'];
return $this->renderPage($response, 'batteriesoverview', [ return $this->renderPage($response, 'batteriesoverview', [
'batteries' => $this->getDatabase()->batteries()->orderBy('name'), 'batteries' => $this->getDatabase()->batteries()->orderBy('name', 'COLLATE NOCASE'),
'current' => $this->getBatteriesService()->GetCurrent(), 'current' => $this->getBatteriesService()->GetCurrent(),
'nextXDays' => $nextXDays, 'nextXDays' => $nextXDays,
'userfields' => $this->getUserfieldsService()->GetFields('batteries'), '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) public function TrackChargeCycle(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'batterytracking', [ return $this->renderPage($response, 'batterytracking', [
'batteries' => $this->getDatabase()->batteries()->orderBy('name') 'batteries' => $this->getDatabase()->batteries()->orderBy('name', 'COLLATE NOCASE')
]); ]);
} }

View File

@@ -17,7 +17,7 @@ class ChoresController extends BaseController
'userfields' => $this->getUserfieldsService()->GetFields('chores'), 'userfields' => $this->getUserfieldsService()->GetFields('chores'),
'assignmentTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_ASSIGNMENT_TYPE_'), 'assignmentTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_ASSIGNMENT_TYPE_'),
'users' => $users, 'users' => $users,
'products' => $this->getDatabase()->products()->orderBy('name') 'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE')
]); ]);
} }
else else
@@ -29,7 +29,7 @@ class ChoresController extends BaseController
'userfields' => $this->getUserfieldsService()->GetFields('chores'), 'userfields' => $this->getUserfieldsService()->GetFields('chores'),
'assignmentTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_ASSIGNMENT_TYPE_'), 'assignmentTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_ASSIGNMENT_TYPE_'),
'users' => $users, '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) public function ChoresList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'chores', [ return $this->renderPage($response, 'chores', [
'chores' => $this->getDatabase()->chores()->orderBy('name'), 'chores' => $this->getDatabase()->chores()->orderBy('name', 'COLLATE NOCASE'),
'userfields' => $this->getUserfieldsService()->GetFields('chores'), 'userfields' => $this->getUserfieldsService()->GetFields('chores'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('chores') 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('chores')
]); ]);
@@ -52,7 +52,7 @@ class ChoresController extends BaseController
{ {
return $this->renderPage($response, 'choresjournal', [ return $this->renderPage($response, 'choresjournal', [
'choresLog' => $this->getDatabase()->chores_log()->orderBy('tracked_time', 'DESC'), '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') 'users' => $this->getDatabase()->users()->orderBy('username')
]); ]);
} }
@@ -63,7 +63,7 @@ class ChoresController extends BaseController
$nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['chores_due_soon_days']; $nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['chores_due_soon_days'];
return $this->renderPage($response, 'choresoverview', [ return $this->renderPage($response, 'choresoverview', [
'chores' => $this->getDatabase()->chores()->orderBy('name'), 'chores' => $this->getDatabase()->chores()->orderBy('name', 'COLLATE NOCASE'),
'currentChores' => $this->getChoresService()->GetCurrent(), 'currentChores' => $this->getChoresService()->GetCurrent(),
'nextXDays' => $nextXDays, 'nextXDays' => $nextXDays,
'userfields' => $this->getUserfieldsService()->GetFields('chores'), '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) public function TrackChoreExecution(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'choretracking', [ 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') 'users' => $this->getDatabase()->users()->orderBy('username')
]); ]);
} }

View File

@@ -28,7 +28,7 @@ class EquipmentController extends BaseController
public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'equipment', [ return $this->renderPage($response, 'equipment', [
'equipment' => $this->getDatabase()->equipment()->orderBy('name'), 'equipment' => $this->getDatabase()->equipment()->orderBy('name', 'COLLATE NOCASE'),
'userfields' => $this->getUserfieldsService()->GetFields('equipment'), 'userfields' => $this->getUserfieldsService()->GetFields('equipment'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('equipment') 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('equipment')
]); ]);

View File

@@ -7,7 +7,7 @@ class GenericEntityController extends BaseController
public function UserentitiesList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) public function UserentitiesList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'userentities', [ return $this->renderPage($response, 'userentities', [
'userentities' => $this->getDatabase()->userentities()->orderBy('name') 'userentities' => $this->getDatabase()->userentities()->orderBy('name', 'COLLATE NOCASE')
]); ]);
} }

View File

@@ -46,15 +46,15 @@ class RecipesController extends BaseController
'recipes' => $recipes, 'recipes' => $recipes,
'internalRecipes' => $this->getDatabase()->recipes()->whereNot('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll(), 'internalRecipes' => $this->getDatabase()->recipes()->whereNot('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll(),
'recipesResolved' => $this->getRecipesService()->GetRecipesResolved(), 'recipesResolved' => $this->getRecipesService()->GetRecipesResolved(),
'products' => $this->getDatabase()->products()->orderBy('name'), 'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
]); ]);
} }
public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) 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(); $recipesResolved = $this->getRecipesService()->GetRecipesResolved();
$selectedRecipe = null; $selectedRecipe = null;
@@ -99,7 +99,7 @@ class RecipesController extends BaseController
if ($selectedRecipe) 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 = [];
$includedRecipeIdsAbsolute[] = $selectedRecipe->id; $includedRecipeIdsAbsolute[] = $selectedRecipe->id;
@@ -132,11 +132,11 @@ class RecipesController extends BaseController
'recipe' => $this->getDatabase()->recipes($recipeId), 'recipe' => $this->getDatabase()->recipes($recipeId),
'recipePositions' => $this->getDatabase()->recipes_pos()->where('recipe_id', $recipeId), 'recipePositions' => $this->getDatabase()->recipes_pos()->where('recipe_id', $recipeId),
'mode' => $recipeId == 'new' ? 'create' : 'edit', 'mode' => $recipeId == 'new' ? 'create' : 'edit',
'products' => $this->getDatabase()->products()->orderBy('name'), 'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
'quantityunits' => $this->getDatabase()->quantity_units(), 'quantityunits' => $this->getDatabase()->quantity_units(),
'recipePositionsResolved' => $this->getRecipesService()->GetRecipesPosResolved(), 'recipePositionsResolved' => $this->getRecipesService()->GetRecipesPosResolved(),
'recipesResolved' => $this->getRecipesService()->GetRecipesResolved(), '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), 'recipeNestings' => $this->getDatabase()->recipes_nestings()->where('recipe_id', $recipeId),
'userfields' => $this->getUserfieldsService()->GetFields('recipes'), 'userfields' => $this->getUserfieldsService()->GetFields('recipes'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
@@ -151,8 +151,8 @@ class RecipesController extends BaseController
'mode' => 'create', 'mode' => 'create',
'recipe' => $this->getDatabase()->recipes($args['recipeId']), 'recipe' => $this->getDatabase()->recipes($args['recipeId']),
'recipePos' => new \stdClass(), 'recipePos' => new \stdClass(),
'products' => $this->getDatabase()->products()->orderBy('name'), 'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
]); ]);
} }
@@ -162,8 +162,8 @@ class RecipesController extends BaseController
'mode' => 'edit', 'mode' => 'edit',
'recipe' => $this->getDatabase()->recipes($args['recipeId']), 'recipe' => $this->getDatabase()->recipes($args['recipeId']),
'recipePos' => $this->getDatabase()->recipes_pos($args['recipePosId']), 'recipePos' => $this->getDatabase()->recipes_pos($args['recipePosId']),
'products' => $this->getDatabase()->products()->orderBy('name'), 'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
]); ]);
} }

View File

@@ -14,9 +14,9 @@ class StockController extends BaseController
return $this->renderPage($response, 'consume', [ return $this->renderPage($response, 'consume', [
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
'barcodes' => $productBarcodes, 'barcodes' => $productBarcodes,
'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'),
'locations' => $this->getDatabase()->locations()->orderBy('name'), 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
]); ]);
} }
@@ -27,29 +27,34 @@ class StockController extends BaseController
$productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); $productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
return $this->renderPage($response, 'inventory', [ 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, 'barcodes' => $productBarcodes,
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'),
'locations' => $this->getDatabase()->locations()->orderBy('name'), 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
]); ]);
} }
public function Journal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) public function Journal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
$usersService = $this->getUsersService();
return $this->renderPage($response, 'stockjournal', [ return $this->renderPage($response, 'stockjournal', [
'stockLog' => $this->getDatabase()->uihelper_stock_journal()->orderBy('row_created_timestamp', 'DESC'), '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) public function LocationContentSheet(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'locationcontentsheet', [ return $this->renderPage($response, 'locationcontentsheet', [
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'locations' => $this->getDatabase()->locations()->orderBy('name'), 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'currentStockLocationContent' => $this->getStockService()->GetCurrentStockLocationContent() '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) public function LocationsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'locations', [ return $this->renderPage($response, 'locations', [
'locations' => $this->getDatabase()->locations()->orderBy('name'), 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'userfields' => $this->getUserfieldsService()->GetFields('locations'), 'userfields' => $this->getUserfieldsService()->GetFields('locations'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('locations') 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('locations')
]); ]);
@@ -89,10 +94,10 @@ class StockController extends BaseController
return $this->renderPage($response, 'stockoverview', [ return $this->renderPage($response, 'stockoverview', [
'currentStock' => $this->getStockService()->GetCurrentStockOverview(), 'currentStock' => $this->getStockService()->GetCurrentStockOverview(),
'locations' => $this->getDatabase()->locations()->orderBy('name'), 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'currentStockLocations' => $this->getStockService()->GetCurrentStockLocations(), 'currentStockLocations' => $this->getStockService()->GetCurrentStockLocations(),
'nextXDays' => $nextXDays, 'nextXDays' => $nextXDays,
'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'),
'userfields' => $this->getUserfieldsService()->GetFields('products'), 'userfields' => $this->getUserfieldsService()->GetFields('products'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products') 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products')
]); ]);
@@ -113,9 +118,10 @@ class StockController extends BaseController
'mode' => 'create', 'mode' => 'create',
'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'), 'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'),
'product' => $product, 'product' => $product,
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(),
'userfields' => $this->getUserfieldsService()->GetFields('product_barcodes')
]); ]);
} }
else else
@@ -124,9 +130,10 @@ class StockController extends BaseController
'mode' => 'edit', 'mode' => 'edit',
'barcode' => $this->getDatabase()->product_barcodes($args['productBarcodeId']), 'barcode' => $this->getDatabase()->product_barcodes($args['productBarcodeId']),
'product' => $product, 'product' => $product,
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() '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', [ return $this->renderPage($response, 'productform', [
'locations' => $this->getDatabase()->locations()->orderBy('name'), 'locations' => $this->getDatabase()->locations()->orderBy('name'),
'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'), 'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'),
'productgroups' => $this->getDatabase()->product_groups()->orderBy('name'), 'productgroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'),
'userfields' => $this->getUserfieldsService()->GetFields('products'), '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, 'isSubProductOfOthers' => false,
'mode' => 'create' 'mode' => 'create'
]); ]);
@@ -153,16 +160,18 @@ class StockController extends BaseController
return $this->renderPage($response, 'productform', [ return $this->renderPage($response, 'productform', [
'product' => $product, 'product' => $product,
'locations' => $this->getDatabase()->locations()->orderBy('name'), 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'), 'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'),
'productgroups' => $this->getDatabase()->product_groups()->orderBy('name'), 'productgroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'),
'userfields' => $this->getUserfieldsService()->GetFields('products'), '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, 'isSubProductOfOthers' => $this->getDatabase()->products()->where('parent_product_id = :1', $product->id)->count() !== 0,
'mode' => 'edit', '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) public function ProductGroupsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'productgroups', [ return $this->renderPage($response, 'productgroups', [
'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'),
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'userfields' => $this->getUserfieldsService()->GetFields('product_groups'), 'userfields' => $this->getUserfieldsService()->GetFields('product_groups'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('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) public function ProductsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'products', [ return $this->renderPage($response, 'products', [
'products' => $this->getDatabase()->products()->orderBy('name'), 'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
'locations' => $this->getDatabase()->locations()->orderBy('name'), 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'),
'userfields' => $this->getUserfieldsService()->GetFields('products'), 'userfields' => $this->getUserfieldsService()->GetFields('products'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products') 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products')
]); ]);
@@ -214,11 +223,11 @@ class StockController extends BaseController
$productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); $productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
return $this->renderPage($response, 'purchase', [ 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, 'barcodes' => $productBarcodes,
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'),
'locations' => $this->getDatabase()->locations()->orderBy('name'), 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
]); ]);
} }
@@ -244,7 +253,7 @@ class StockController extends BaseController
return $this->renderPage($response, 'quantityunitconversionform', [ return $this->renderPage($response, 'quantityunitconversionform', [
'mode' => 'create', 'mode' => 'create',
'userfields' => $this->getUserfieldsService()->GetFields('quantity_unit_conversions'), '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, 'product' => $product,
'defaultQuUnit' => $defaultQuUnit 'defaultQuUnit' => $defaultQuUnit
]); ]);
@@ -255,7 +264,7 @@ class StockController extends BaseController
'quConversion' => $this->getDatabase()->quantity_unit_conversions($args['quConversionId']), 'quConversion' => $this->getDatabase()->quantity_unit_conversions($args['quConversionId']),
'mode' => 'edit', 'mode' => 'edit',
'userfields' => $this->getUserfieldsService()->GetFields('quantity_unit_conversions'), '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, 'product' => $product,
'defaultQuUnit' => $defaultQuUnit '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) public function QuantityUnitPluralFormTesting(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'quantityunitpluraltesting', [ 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) public function QuantityUnitsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'quantityunits', [ 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'), 'userfields' => $this->getUserfieldsService()->GetFields('quantity_units'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('quantity_units') 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('quantity_units')
]); ]);
@@ -316,15 +325,17 @@ class StockController extends BaseController
return $this->renderPage($response, 'shoppinglist', [ return $this->renderPage($response, 'shoppinglist', [
'listItems' => $this->getDatabase()->shopping_list()->where('shopping_list_id = :1', $listId), 'listItems' => $this->getDatabase()->shopping_list()->where('shopping_list_id = :1', $listId),
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'missingProducts' => $this->getStockService()->GetMissingProducts(), 'missingProducts' => $this->getStockService()->GetMissingProducts(),
'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'),
'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'), 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name', 'COLLATE NOCASE'),
'selectedShoppingListId' => $listId, 'selectedShoppingListId' => $listId,
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(), 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(),
'userfields' => $this->getUserfieldsService()->GetFields('products'), 'productUserfields' => $this->getUserfieldsService()->GetFields('products'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('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') if ($args['listId'] == 'new')
{ {
return $this->renderPage($response, 'shoppinglistform', [ return $this->renderPage($response, 'shoppinglistform', [
'mode' => 'create' 'mode' => 'create',
'userfields' => $this->getUserfieldsService()->GetFields('shopping_lists')
]); ]);
} }
else else
{ {
return $this->renderPage($response, 'shoppinglistform', [ return $this->renderPage($response, 'shoppinglistform', [
'shoppingList' => $this->getDatabase()->shopping_lists($args['listId']), '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') if ($args['itemId'] == 'new')
{ {
return $this->renderPage($response, 'shoppinglistitemform', [ return $this->renderPage($response, 'shoppinglistitemform', [
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'), 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name', 'COLLATE NOCASE'),
'mode' => 'create', 'mode' => 'create',
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(),
'userfields' => $this->getUserfieldsService()->GetFields('shopping_list')
]); ]);
} }
else else
{ {
return $this->renderPage($response, 'shoppinglistitemform', [ return $this->renderPage($response, 'shoppinglistitemform', [
'listItem' => $this->getDatabase()->shopping_list($args['itemId']), 'listItem' => $this->getDatabase()->shopping_list($args['itemId']),
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'), 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name', 'COLLATE NOCASE'),
'mode' => 'edit', 'mode' => 'edit',
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() '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) public function ShoppingLocationsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'shoppinglocations', [ 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'), 'userfields' => $this->getUserfieldsService()->GetFields('shopping_locations'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('shopping_locations') 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('shopping_locations')
]); ]);
@@ -407,18 +422,18 @@ class StockController extends BaseController
{ {
return $this->renderPage($response, 'stockentryform', [ return $this->renderPage($response, 'stockentryform', [
'stockEntry' => $this->getDatabase()->stock()->where('id', $args['entryId'])->fetch(), 'stockEntry' => $this->getDatabase()->stock()->where('id', $args['entryId'])->fetch(),
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'),
'locations' => $this->getDatabase()->locations()->orderBy('name') 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE')
]); ]);
} }
public function StockSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) public function StockSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'stocksettings', [ return $this->renderPage($response, 'stocksettings', [
'locations' => $this->getDatabase()->locations()->orderBy('name'), 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'productGroups' => $this->getDatabase()->product_groups()->orderBy('name') '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']; $nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['stock_due_soon_days'];
return $this->renderPage($response, 'stockentries', [ return $this->renderPage($response, 'stockentries', [
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'locations' => $this->getDatabase()->locations()->orderBy('name'), 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'),
'stockEntries' => $this->getDatabase()->stock()->orderBy('product_id'), 'stockEntries' => $this->getDatabase()->stock()->orderBy('product_id'),
'currentStockLocations' => $this->getStockService()->GetCurrentStockLocations(), 'currentStockLocations' => $this->getStockService()->GetCurrentStockLocations(),
'nextXDays' => $nextXDays, 'nextXDays' => $nextXDays,
@@ -446,10 +461,10 @@ class StockController extends BaseController
$productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); $productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
return $this->renderPage($response, 'transfer', [ 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, 'barcodes' => $productBarcodes,
'locations' => $this->getDatabase()->locations()->orderBy('name'), 'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
]); ]);
} }
@@ -474,8 +489,13 @@ class StockController extends BaseController
{ {
$entries = $entries->where('transaction_type', $request->getQueryParams()['transaction_type']); $entries = $entries->where('transaction_type', $request->getQueryParams()['transaction_type']);
} }
$usersService = $this->getUsersService();
return $this->renderPage($response, 'stockjournalsummary', [ 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_')
]); ]);
} }
} }

View File

@@ -8,7 +8,7 @@ class TasksController extends BaseController
{ {
if (isset($request->getQueryParams()['include_done'])) if (isset($request->getQueryParams()['include_done']))
{ {
$tasks = $this->getDatabase()->tasks()->orderBy('name'); $tasks = $this->getDatabase()->tasks()->orderBy('name', 'COLLATE NOCASE');
} }
else else
{ {
@@ -21,7 +21,7 @@ class TasksController extends BaseController
return $this->renderPage($response, 'tasks', [ return $this->renderPage($response, 'tasks', [
'tasks' => $tasks, 'tasks' => $tasks,
'nextXDays' => $nextXDays, 'nextXDays' => $nextXDays,
'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name'), 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name', 'COLLATE NOCASE'),
'users' => $this->getDatabase()->users(), 'users' => $this->getDatabase()->users(),
'userfields' => $this->getUserfieldsService()->GetFields('tasks'), 'userfields' => $this->getUserfieldsService()->GetFields('tasks'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('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) public function TaskCategoriesList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
return $this->renderPage($response, 'taskcategories', [ 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'), 'userfields' => $this->getUserfieldsService()->GetFields('task_categories'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('task_categories') 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('task_categories')
]); ]);
@@ -62,7 +62,7 @@ class TasksController extends BaseController
{ {
return $this->renderPage($response, 'taskform', [ return $this->renderPage($response, 'taskform', [
'mode' => 'create', 'mode' => 'create',
'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name'), 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name', 'COLLATE NOCASE'),
'users' => $this->getDatabase()->users()->orderBy('username'), 'users' => $this->getDatabase()->users()->orderBy('username'),
'userfields' => $this->getUserfieldsService()->GetFields('tasks') 'userfields' => $this->getUserfieldsService()->GetFields('tasks')
]); ]);
@@ -72,7 +72,7 @@ class TasksController extends BaseController
return $this->renderPage($response, 'taskform', [ return $this->renderPage($response, 'taskform', [
'task' => $this->getDatabase()->tasks($args['taskId']), 'task' => $this->getDatabase()->tasks($args['taskId']),
'mode' => 'edit', 'mode' => 'edit',
'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name'), 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name', 'COLLATE NOCASE'),
'users' => $this->getDatabase()->users()->orderBy('username'), 'users' => $this->getDatabase()->users()->orderBy('username'),
'userfields' => $this->getUserfieldsService()->GetFields('tasks') 'userfields' => $this->getUserfieldsService()->GetFields('tasks')
]); ]);

View File

@@ -239,6 +239,9 @@
}, },
{ {
"$ref": "#/components/schemas/StockEntry" "$ref": "#/components/schemas/StockEntry"
},
{
"$ref": "#/components/schemas/ProductBarcode"
} }
] ]
} }
@@ -311,6 +314,9 @@
}, },
{ {
"$ref": "#/components/schemas/StockEntry" "$ref": "#/components/schemas/StockEntry"
},
{
"$ref": "#/components/schemas/ProductBarcode"
} }
] ]
} }
@@ -402,6 +408,9 @@
}, },
{ {
"$ref": "#/components/schemas/StockEntry" "$ref": "#/components/schemas/StockEntry"
},
{
"$ref": "#/components/schemas/ProductBarcode"
} }
] ]
} }
@@ -482,6 +491,9 @@
}, },
{ {
"$ref": "#/components/schemas/StockEntry" "$ref": "#/components/schemas/StockEntry"
},
{
"$ref": "#/components/schemas/ProductBarcode"
} }
] ]
} }
@@ -4129,7 +4141,7 @@
"$ref": "#/components/schemas/Product" "$ref": "#/components/schemas/Product"
}, },
"product_barcodes": { "product_barcodes": {
"$ref": "#/components/schemas/ProductBarcodeDetailsResponse" "$ref": "#/components/schemas/ProductBarcode"
}, },
"default_quantity_unit_purchase": { "default_quantity_unit_purchase": {
"$ref": "#/components/schemas/QuantityUnit" "$ref": "#/components/schemas/QuantityUnit"
@@ -4257,14 +4269,14 @@
}, },
"price": { "price": {
"type": "number", "type": "number",
"format": "number" "format": "float"
}, },
"shopping_location": { "shopping_location": {
"$ref": "#/components/schemas/ShoppingLocation" "$ref": "#/components/schemas/ShoppingLocation"
} }
} }
}, },
"ProductBarcodeDetailsResponse": { "ProductBarcode": {
"type": "object", "type": "object",
"properties": { "properties": {
"product_id": { "product_id": {
@@ -4282,6 +4294,10 @@
"amount": { "amount": {
"type": "number", "type": "number",
"format": "number" "format": "number"
},
"last_price": {
"type": "number",
"format": "float"
} }
} }
}, },

View File

@@ -889,9 +889,6 @@ msgstr ""
msgid "Shopping list to stock workflow" msgid "Shopping list to stock workflow"
msgstr "" 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" msgid "Skip"
msgstr "" 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" msgid "This must between %1$s and %2$s and needs to be a valid number with max. %3$s decimal places"
msgstr "" 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 ""

View File

@@ -16,6 +16,7 @@ CREATE TABLE product_barcodes (
qu_id INT, qu_id INT,
amount REAL, amount REAL,
shopping_location_id INTEGER, shopping_location_id INTEGER,
last_price DECIMAL(15, 2),
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
); );

View File

@@ -424,7 +424,7 @@ Grocy.FrontendHelpers.ShowGenericError = function(message, exception)
{ {
bootbox.alert({ bootbox.alert({
title: __t('Error details'), title: __t('Error details'),
message: JSON.stringify(exception, null, 4), message: '<pre class="my-0"><code>' + JSON.stringify(exception, null, 4) + '</code></pre>',
closeButton: false closeButton: false
}); });
} }
@@ -448,6 +448,11 @@ $(document).on("change", ".user-setting-control", function()
var element = $(this); var element = $(this);
var settingKey = element.attr("data-setting-key"); var settingKey = element.attr("data-setting-key");
if (!element[0].checkValidity())
{
return;
}
var inputType = "unknown"; var inputType = "unknown";
if (typeof element.attr("type") !== typeof undefined && element.attr("type") !== false) if (typeof element.attr("type") !== typeof undefined && element.attr("type") !== false)
{ {

View File

@@ -133,7 +133,7 @@ $('.input-group-chore-period-type').on('change', function(e)
{ {
$("label[for='period_days']").text(__t("Period days")); $("label[for='period_days']").text(__t("Period days"));
$("#period_days").attr("min", "0"); $("#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())); $('#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') else if (periodType === 'daily')

View File

@@ -32,30 +32,38 @@ $(".numberpicker").each(function()
{ {
mutations.forEach(function(mutation) 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 element = $(mutation.target);
var min = element.attr("min"); var min = element.attr("min");
var max = element.attr("max");
var decimals = element.attr("data-decimals"); var decimals = element.attr("data-decimals");
var max = "";
if (element.hasAttr("max"))
{
max = element.attr("max");
}
if (element.hasAttr("data-not-equal")) if (element.hasAttr("data-not-equal"))
{ {
var notEqual = element.attr("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)); if (max.isEmpty())
} {
else 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));
{ }
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)); 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)); 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 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

View File

@@ -93,7 +93,7 @@
Grocy.Components.ProductAmountPicker.Reset(); Grocy.Components.ProductAmountPicker.Reset();
$("#display_amount").attr("min", "0." + "0".repeat(parseInt(Grocy.UserSettings.stock_decimal_places_amounts) - 1) + "1"); $("#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)); $('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_consume_amount));
RefreshLocaleNumberInput(); RefreshLocaleNumberInput();
$(".input-group-productamountpicker").trigger("change"); $(".input-group-productamountpicker").trigger("change");
@@ -272,6 +272,8 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
Grocy.Api.Get('stock/products/' + productId, Grocy.Api.Get('stock/products/' + productId,
function(productDetails) function(productDetails)
{ {
current_productDetails = productDetails;
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id); Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id);
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.quantity_unit_stock.id); Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.quantity_unit_stock.id);
$('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_consume_amount)); $('#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"); Grocy.FrontendHelpers.ValidateForm("consume-form");
}); });
$("#qu_id").on("change", function()
{
RefreshForm();
});
function UndoStockBooking(bookingId) function UndoStockBooking(bookingId)
{ {
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {}, Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {},
@@ -570,7 +577,7 @@ function RefreshForm()
$("#tare-weight-handling-info").addClass("d-none"); $("#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("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) if (sumValue == 0)
{ {

View File

@@ -84,6 +84,7 @@
$("#tare-weight-handling-info").addClass("d-none"); $("#tare-weight-handling-info").addClass("d-none");
$("#display_amount").attr("min", "0"); $("#display_amount").attr("min", "0");
$('#display_amount').val(''); $('#display_amount').val('');
$('#display_amount').removeAttr("data-not-equal");
$(".input-group-productamountpicker").trigger("change"); $(".input-group-productamountpicker").trigger("change");
$('#price').val(''); $('#price').val('');
Grocy.Components.DateTimePicker.Clear(); Grocy.Components.DateTimePicker.Clear();

View File

@@ -13,6 +13,11 @@
Grocy.Api.Post('objects/product_barcodes', jsonData, Grocy.Api.Post('objects/product_barcodes', jsonData,
function(result) 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) function(xhr)
{ {
@@ -23,9 +28,12 @@
} }
else else
{ {
Grocy.Components.UserfieldsForm.Save();
Grocy.Api.Put('objects/product_barcodes/' + Grocy.EditObjectId, jsonData, Grocy.Api.Put('objects/product_barcodes/' + Grocy.EditObjectId, jsonData,
function(result) function(result)
{ {
window.parent.postMessage(WindowMessageBag("ProductBarcodesChanged"), U("/product/" + GetUriParam("product")));
window.parent.postMessage(WindowMessageBag("CloseAllModals"), U("/product/" + GetUriParam("product")));
}, },
function(xhr) 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) $('#barcode').on('keyup', function(e)
@@ -82,3 +87,4 @@ if (Grocy.EditMode == "edit")
Grocy.FrontendHelpers.ValidateForm('barcode-form'); Grocy.FrontendHelpers.ValidateForm('barcode-form');
$('#barcode').focus(); $('#barcode').focus();
RefreshLocaleNumberInput(); RefreshLocaleNumberInput();
Grocy.Components.UserfieldsForm.Load()

View File

@@ -296,7 +296,7 @@ var quConversionsTable = $('#qu-conversions-table-products').DataTable({
dataSrc: 4 dataSrc: 4
} }
}); });
$('#qu-conversions-table tbody').removeClass("d-none"); $('#qu-conversions-table-products tbody').removeClass("d-none");
quConversionsTable.columns.adjust().draw(); quConversionsTable.columns.adjust().draw();
var barcodeTable = $('#barcode-table').DataTable({ var barcodeTable = $('#barcode-table').DataTable({

View File

@@ -55,6 +55,16 @@ $('#save-purchase-button').on('click', function(e)
Grocy.Api.Post('stock/products/' + jsonForm.product_id + '/add', jsonData, Grocy.Api.Post('stock/products/' + jsonForm.product_id + '/add', jsonData,
function(result) 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)) if (BoolVal(Grocy.UserSettings.scan_mode_purchase_enabled))
{ {
Grocy.UISound.Success(); Grocy.UISound.Success();
@@ -108,6 +118,7 @@ $('#save-purchase-button').on('click', function(e)
} }
Grocy.Components.ProductAmountPicker.Reset(); 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").attr("min", "0." + "0".repeat(parseInt(Grocy.UserSettings.stock_decimal_places_amounts) - 1) + "1");
$('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_purchase_amount)); $('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_purchase_amount));
$(".input-group-productamountpicker").trigger("change"); $(".input-group-productamountpicker").trigger("change");
@@ -202,7 +213,7 @@ if (Grocy.Components.ProductPicker !== undefined)
Grocy.Components.LocationPicker.SetId(productDetails.location.id); Grocy.Components.LocationPicker.SetId(productDetails.location.id);
} }
if (productDetails.last_price == null) if (productDetails.last_price == null || productDetails.last_price == 0)
{ {
$("#price").val("") $("#price").val("")
} }
@@ -282,10 +293,11 @@ if (Grocy.Components.ProductPicker !== undefined)
if (barcodeResult != null) if (barcodeResult != null)
{ {
var barcode = barcodeResult[0]; var barcode = barcodeResult[0];
$("#purchase-form").attr("data-used-barcode", barcode.id);
if (barcode != null) if (barcode != null)
{ {
if (barcode.amount != null) if (barcode.amount != null && !barcode.amount.isEmpty())
{ {
$("#display_amount").val(barcode.amount); $("#display_amount").val(barcode.amount);
$("#display_amount").select(); $("#display_amount").select();
@@ -301,6 +313,12 @@ if (Grocy.Components.ProductPicker !== undefined)
Grocy.Components.ShoppingLocationPicker.SetId(barcode.shopping_location_id); 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"); $(".input-group-productamountpicker").trigger("change");
Grocy.FrontendHelpers.ValidateForm('purchase-form'); 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"); $('#display_amount').trigger("keyup");
}, },
@@ -450,7 +472,7 @@ function refreshPriceHint()
price = parseFloat(price / $('#display_amount').val()).toFixed(Grocy.UserSettings.stock_decimal_places_prices); 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 else
{ {

View File

@@ -285,6 +285,7 @@ $("#recipe-include-add-button").on("click", function(e)
$("#recipe-include-editform-title").text(__t("Add included recipe")); $("#recipe-include-editform-title").text(__t("Add included recipe"));
$("#recipe-include-form").data("edit-mode", "create"); $("#recipe-include-form").data("edit-mode", "create");
Grocy.Components.RecipePicker.Clear(); Grocy.Components.RecipePicker.Clear();
Grocy.Components.RecipePicker.GetInputElement().focus();
$("#recipe-include-editform-modal").modal("show"); $("#recipe-include-editform-modal").modal("show");
Grocy.FrontendHelpers.ValidateForm("recipe-include-form"); Grocy.FrontendHelpers.ValidateForm("recipe-include-form");
}, },

View File

@@ -10,9 +10,13 @@
Grocy.Api.Post('objects/shopping_lists', jsonData, Grocy.Api.Post('objects/shopping_lists', jsonData,
function(result) function(result)
{ {
window.parent.postMessage(WindowMessageBag("ShoppingListChanged", result.created_object_id), Grocy.BaseUrl); Grocy.EditObjectId = result.created_object_id;
window.parent.postMessage(WindowMessageBag("Ready"), Grocy.BaseUrl); Grocy.Components.UserfieldsForm.Save(function()
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl); {
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) function(xhr)
{ {
@@ -23,19 +27,22 @@
} }
else else
{ {
Grocy.Api.Put('objects/shopping_lists/' + Grocy.EditObjectId, jsonData, Grocy.Components.UserfieldsForm.Save(function()
function(result) {
{ Grocy.Api.Put('objects/shopping_lists/' + Grocy.EditObjectId, jsonData,
window.parent.postMessage(WindowMessageBag("ShoppingListChanged", Grocy.EditObjectId), Grocy.BaseUrl); function(result)
window.parent.postMessage(WindowMessageBag("Ready"), Grocy.BaseUrl); {
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl); window.parent.postMessage(WindowMessageBag("ShoppingListChanged", Grocy.EditObjectId), Grocy.BaseUrl);
}, window.parent.postMessage(WindowMessageBag("Ready"), Grocy.BaseUrl);
function(xhr) window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
{ },
Grocy.FrontendHelpers.EndUiBusy("shopping-list-form"); function(xhr)
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response); {
} 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(); $('#name').focus();
Grocy.FrontendHelpers.ValidateForm('shopping-list-form'); Grocy.FrontendHelpers.ValidateForm('shopping-list-form');

View File

@@ -21,6 +21,9 @@ $('#save-shoppinglist-button').on('click', function(e)
Grocy.Api.Post('stock/shoppinglist/add-product', jsonData, Grocy.Api.Post('stock/shoppinglist/add-product', jsonData,
function(result) function(result)
{ {
Grocy.EditObjectId = result.created_object_id;
Grocy.Components.UserfieldsForm.Save();
if (GetUriParam("embedded") !== undefined) if (GetUriParam("embedded") !== undefined)
{ {
Grocy.Api.Get('stock/products/' + jsonData.product_id, 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, Grocy.Api.Post('objects/shopping_list', jsonData,
function(result) function(result)
{ {
Grocy.EditObjectId = result.created_object_id;
Grocy.Components.UserfieldsForm.Save();
if (GetUriParam("embedded") !== undefined) if (GetUriParam("embedded") !== undefined)
{ {
if (jsonData.product_id) if (jsonData.product_id)
@@ -94,6 +100,8 @@ $('#save-shoppinglist-button').on('click', function(e)
Grocy.Api.Put('objects/shopping_list/' + Grocy.EditObjectId, jsonData, Grocy.Api.Put('objects/shopping_list/' + Grocy.EditObjectId, jsonData,
function(result) function(result)
{ {
Grocy.Components.UserfieldsForm.Save();
if (GetUriParam("embedded") !== undefined) if (GetUriParam("embedded") !== undefined)
{ {
if (jsonData.product_id) if (jsonData.product_id)
@@ -216,3 +224,5 @@ if (GetUriParam("amount") !== undefined)
$(".input-group-productamountpicker").trigger("change"); $(".input-group-productamountpicker").trigger("change");
Grocy.FrontendHelpers.ValidateForm('shoppinglist-form'); Grocy.FrontendHelpers.ValidateForm('shoppinglist-form');
} }
Grocy.Components.UserfieldsForm.Load();

View File

@@ -23,6 +23,42 @@ $("#product-filter").on("change", function()
stockJournalTable.column(1).search(text).draw(); 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() $("#search").on("keyup", Delay(function()
{ {
var value = $(this).val(); var value = $(this).val();
@@ -37,6 +73,12 @@ $("#search").on("keyup", Delay(function()
$("#clear-filter-button").on("click", function() $("#clear-filter-button").on("click", function()
{ {
$("#search").val(""); $("#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(); stockJournalTable.search("").draw();
}); });

View File

@@ -1,8 +1,71 @@
var journalSummaryTable = $('#journal-summary-table').DataTable({ var journalSummaryTable = $('#stock-journal-summary-table').DataTable({
'paginate': true, '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(); journalSummaryTable.columns.adjust().draw();
$('.dataTables_scrollBody').addClass("dragscroll"); $('.dataTables_scrollBody').addClass("dragscroll");
dragscroll.reset(); 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();
});

View File

@@ -90,7 +90,7 @@
Grocy.Components.ProductAmountPicker.Reset(); Grocy.Components.ProductAmountPicker.Reset();
$("#location_id_from").find("option").remove().end().append("<option></option>"); $("#location_id_from").find("option").remove().end().append("<option></option>");
$("#display_amount").attr("min", "0." + "0".repeat(parseInt(Grocy.UserSettings.stock_decimal_places_amounts) - 1) + "1"); $("#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)); $('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_transfer_amount));
RefreshLocaleNumberInput(); RefreshLocaleNumberInput();
$(".input-group-productamountpicker").trigger("change"); $(".input-group-productamountpicker").trigger("change");
@@ -198,6 +198,8 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
$("#tare-weight-handling-info").addClass("d-none"); $("#tare-weight-handling-info").addClass("d-none");
} }
$('#display_amount').attr("data-stock-amount", productDetails.stock_amount);
if ((parseFloat(productDetails.stock_amount) || 0) === 0) if ((parseFloat(productDetails.stock_amount) || 0) === 0)
{ {
Grocy.Components.ProductPicker.Clear(); Grocy.Components.ProductPicker.Clear();
@@ -274,7 +276,7 @@ $("#location_id_from").on('change', function(e)
sumValue = sumValue + parseFloat(stockEntry.amount); 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) if (sumValue == 0)
{ {
$("#display_amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location')); $("#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) $('#display_amount').on('focus', function(e)
{ {
$(this).select(); $(this).select();
@@ -346,7 +353,7 @@ $("#specific_stock_entry").on("change", function(e)
sumValue = sumValue + parseFloat(stockEntry.amount); 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) if (sumValue == 0)
{ {
$("#display_amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location')); $("#display_amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location'));

View File

@@ -21,7 +21,7 @@ class UserfieldsService extends BaseService
public function GetAllFields() public function GetAllFields()
{ {
return $this->getDatabase()->userfields()->orderBy('name')->fetchAll(); return $this->getDatabase()->userfields()->orderBy('name', 'COLLATE NOCASE')->fetchAll();
} }
public function GetAllValues($entity) public function GetAllValues($entity)
@@ -31,7 +31,7 @@ class UserfieldsService extends BaseService
throw new \Exception('Entity does not exist or is not exposed'); 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() public function GetEntities()
@@ -40,7 +40,7 @@ class UserfieldsService extends BaseService
$userentities = []; $userentities = [];
foreach ($this->getDatabase()->userentities()->orderBy('name') as $userentity) foreach ($this->getDatabase()->userentities()->orderBy('name', 'COLLATE NOCASE') as $userentity)
{ {
$userentities[] = 'userentity-' . $userentity->name; $userentities[] = 'userentity-' . $userentity->name;
} }
@@ -65,7 +65,7 @@ class UserfieldsService extends BaseService
throw new \Exception('Entity does not exist or is not exposed'); 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) public function GetValues($entity, $objectId)
@@ -75,7 +75,7 @@ class UserfieldsService extends BaseService
throw new \Exception('Entity does not exist or is not exposed'); 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 = []; $userfieldKeyValuePairs = [];
foreach ($userfields as $userfield) foreach ($userfields as $userfield)

View File

@@ -177,7 +177,7 @@
<label for="assignment_config">{{ $__t('Assign to') }}</label> <label for="assignment_config">{{ $__t('Assign to') }}</label>
<select required <select required
multiple multiple
class="custom-control custom-select input-group-chore-assignment-type selectpicker" class="form-control input-group-chore-assignment-type selectpicker"
id="assignment_config" id="assignment_config"
name="assignment_config" name="assignment_config"
data-actions-Box="true" data-actions-Box="true"

View File

@@ -4,7 +4,7 @@
@php if(!isset($value)) { $value = 1; } @endphp @php if(!isset($value)) { $value = 1; } @endphp
@php if(empty($min)) { $min = 0; } @endphp @php if(empty($min)) { $min = 0; } @endphp
@php if(empty($max)) { $max = 999999; } @endphp @php if(!isset($max)) { $max = ''; } @endphp
@php if(empty($decimals)) { $decimals = 0; } @endphp @php if(empty($decimals)) { $decimals = 0; } @endphp
@php if(empty($hint)) { $hint = ''; } @endphp @php if(empty($hint)) { $hint = ''; } @endphp
@php if(empty($hintId)) { $hintId = ''; } @endphp @php if(empty($hintId)) { $hintId = ''; } @endphp
@@ -20,7 +20,8 @@
<div id="group-{{ $id }}" <div id="group-{{ $id }}"
class="form-group {{ $additionalGroupCssClasses }}"> class="form-group {{ $additionalGroupCssClasses }}">
<label for="{{ $id }}"> <label class="w-100"
for="{{ $id }}">
{{ $__t($label) }} {{ $__t($label) }}
@if(!empty($hint) || !empty($hintId)) @if(!empty($hint) || !empty($hintId))
<i id="{{ $hintId }}" <i id="{{ $hintId }}"
@@ -31,7 +32,7 @@
{!! $additionalHtmlContextHelp !!} {!! $additionalHtmlContextHelp !!}
@if(!empty($contextInfoId)) @if(!empty($contextInfoId))
<span id="{{ $contextInfoId }}" <span id="{{ $contextInfoId }}"
class="small text-muted"></span> class="small text-muted float-right mt-1"></span>
@endif @endif
</label> </label>
<div class="input-group"> <div class="input-group">
@@ -46,8 +47,10 @@
@endif @endif
value="{{ $value }}" value="{{ $value }}"
min="{{ number_format($min, $decimals, '.', '') }}" min="{{ number_format($min, $decimals, '.', '') }}"
@if(!empty($max))
max="{{ number_format($max, $decimals, '.', '') }}" max="{{ number_format($max, $decimals, '.', '') }}"
step="@if($decimals == 0){{1}}@else{{'.' . str_repeat('0', $userSettings['stock_decimal_places_amounts'] - 1) . '1'}}@endif" @endif
step="@if($decimals == 0){{1}}@else{{'.' . str_repeat('0', $decimals - 1) . '1'}}@endif"
data-decimals="{{ $decimals }}" data-decimals="{{ $decimals }}"
@if($isRequired) @if($isRequired)
required required

View File

@@ -101,7 +101,7 @@
<div class="form-group"> <div class="form-group">
<label for="{{ $userfield->name }}">{{ $userfield->caption }}</label> <label for="{{ $userfield->name }}">{{ $userfield->caption }}</label>
<select multiple <select multiple
class="custom-control custom-select userfield-input selectpicker" class="form-control userfield-input selectpicker"
data-userfield-name="{{ $userfield->name }}" data-userfield-name="{{ $userfield->name }}"
data-actions-Box="true" data-actions-Box="true"
data-live-search="true"> data-live-search="true">

View File

@@ -90,6 +90,11 @@
value="1"> value="1">
@endif @endif
@include('components.userfieldsform', array(
'userfields' => $userfields,
'entity' => 'product_barcodes'
))
<button id="save-barcode-button" <button id="save-barcode-button"
class="btn btn-success">{{ $__t('Save') }}</button> class="btn btn-success">{{ $__t('Save') }}</button>

View File

@@ -469,6 +469,10 @@
@endif @endif
<th>{{ $__t('Quantity unit') }}</th> <th>{{ $__t('Quantity unit') }}</th>
<th>{{ $__t('Amount') }}</th> <th>{{ $__t('Amount') }}</th>
@include('components.userfields_thead', array(
'userfields' => $productBarcodeUserfields
))
</tr> </tr>
</thead> </thead>
<tbody class="d-none"> <tbody class="d-none">
@@ -510,6 +514,11 @@
<span class="locale-number locale-number-quantity-amount">{{ $barcode->amount }}</span> <span class="locale-number locale-number-quantity-amount">{{ $barcode->amount }}</span>
@endif @endif
</td> </td>
@include('components.userfields_tbody', array(
'userfields' => $productBarcodeUserfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($productBarcodeUserfieldValues, 'object_id', $barcode->id)
))
</tr> </tr>
@endif @endif
@endforeach @endforeach
@@ -595,7 +604,7 @@
@endif @endif
</td> </td>
<td class="font-italic"> <td class="font-italic">
{{ $__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, '<span class="locale-number locale-number-quantity-amount">' . $quConversion->factor . '</span>', FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->to_qu_id)->name) !!}
</td> </td>
</tr> </tr>
@endif @endif

View File

@@ -103,6 +103,8 @@
<div class="title-related-links"> <div class="title-related-links">
<h4> <h4>
{{ $__t('Default conversions') }} {{ $__t('Default conversions') }}
<small id="qu-conversion-headline-info"
class="text-muted font-italic"></small>
</h4> </h4>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3" <button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3"
type="button" type="button"
@@ -119,9 +121,6 @@
</div> </div>
</div> </div>
<h5 id="qu-conversion-headline-info"
class="text-muted font-italic"></h5>
<table id="qu-conversions-table" <table id="qu-conversions-table"
class="table table-sm table-striped nowrap w-100"> class="table table-sm table-striped nowrap w-100">
<thead> <thead>
@@ -154,7 +153,7 @@
</a> </a>
</td> </td>
<td> <td>
{{ $defaultQuConversion->factor }} <span class="locale-number locale-number-quantity-amount">{{ $defaultQuConversion->factor }}</span>
</td> </td>
<td> <td>
{{ FindObjectInArrayByPropertyValue($quantityUnits, 'id', $defaultQuConversion->to_qu_id)->name }} {{ FindObjectInArrayByPropertyValue($quantityUnits, 'id', $defaultQuConversion->to_qu_id)->name }}

View File

@@ -187,7 +187,7 @@
$productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id); $productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id);
$productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock); $productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock);
$productQuConversion = FindObjectInArrayByPropertyValue($productQuConversions, 'to_qu_id', $recipePosition->qu_id); $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; $recipePosition->amount = $recipePosition->amount * $productQuConversion->factor;
} }

View File

@@ -398,7 +398,7 @@
$productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id); $productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id);
$productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock); $productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock);
$productQuConversion = FindObjectInArrayByPropertyValue($productQuConversions, 'to_qu_id', $selectedRecipePosition->qu_id); $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; $selectedRecipePosition->recipe_amount = $selectedRecipePosition->recipe_amount * $productQuConversion->factor;
} }

View File

@@ -49,7 +49,7 @@
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" <div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links"> id="related-links">
<div class="my-auto float-right"> <div class="my-auto float-right">
<select class="custom-control custom-select" <select class="custom-control custom-select custom-select-sm"
id="selected-shopping-list"> id="selected-shopping-list">
@foreach($shoppingLists as $shoppingList) @foreach($shoppingLists as $shoppingList)
<option @if($shoppingList->id == $selectedShoppingListId) selected="selected" @endif value="{{ $shoppingList->id }}">{{ $shoppingList->name }}</option> <option @if($shoppingList->id == $selectedShoppingListId) selected="selected" @endif value="{{ $shoppingList->id }}">{{ $shoppingList->name }}</option>
@@ -190,6 +190,9 @@
@include('components.userfields_thead', array( @include('components.userfields_thead', array(
'userfields' => $userfields 'userfields' => $userfields
)) ))
@include('components.userfields_thead', array(
'userfields' => $productUserfields
))
</tr> </tr>
</thead> </thead>
@@ -259,6 +262,10 @@
@include('components.userfields_tbody', array( @include('components.userfields_tbody', array(
'userfields' => $userfields, 'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $listItem->id)
))
@include('components.userfields_tbody', array(
'userfields' => $productUserfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $listItem->product_id) 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $listItem->product_id)
)) ))

View File

@@ -43,6 +43,11 @@
<div class="invalid-feedback">{{ $__t('A name is required') }}</div> <div class="invalid-feedback">{{ $__t('A name is required') }}</div>
</div> </div>
@include('components.userfieldsform', array(
'userfields' => $userfields,
'entity' => 'shopping_lists'
))
<button id="save-shopping-list-button" <button id="save-shopping-list-button"
class="btn btn-success">{{ $__t('Save') }}</button> class="btn btn-success">{{ $__t('Save') }}</button>

View File

@@ -84,6 +84,11 @@
name="note">@if($mode == 'edit'){{ $listItem->note }}@endif</textarea> name="note">@if($mode == 'edit'){{ $listItem->note }}@endif</textarea>
</div> </div>
@include('components.userfieldsform', array(
'userfields' => $userfields,
'entity' => 'shopping_list'
))
<button id="save-shoppinglist-button" <button id="save-shoppinglist-button"
class="btn btn-success">{{ $__t('Save') }}</button> class="btn btn-success">{{ $__t('Save') }}</button>

View File

@@ -34,7 +34,7 @@
<div class="row collapse d-md-flex" <div class="row collapse d-md-flex"
id="table-filter-row"> id="table-filter-row">
<div class="col-xs-12 col-md-6 col-xl-3"> <div class="col-xs-12 col-md-6 col-xl-2">
<div class="input-group"> <div class="input-group">
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span> <span class="input-group-text"><i class="fas fa-search"></i></span>
@@ -45,7 +45,7 @@
placeholder="{{ $__t('Search') }}"> placeholder="{{ $__t('Search') }}">
</div> </div>
</div> </div>
<div class="col-xs-12 col-md-6 col-xl-3"> <div class="col-xs-12 col-md-6 col-xl-2">
<div class="input-group"> <div class="input-group">
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Product') }}</span> <span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Product') }}</span>
@@ -59,8 +59,50 @@
</select> </select>
</div> </div>
</div> </div>
<div class="col-xs-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Transaction type') }}</span>
</div>
<select class="custom-control custom-select"
id="transaction-type-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($transactionTypes as $transactionType)
<option value="{{ $transactionType }}">{{ $__t($transactionType) }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-xs-12 col-md-6 col-xl-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Location') }}</span>
</div>
<select class="custom-control custom-select"
id="location-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($locations as $location)
<option value="{{ $location->id }}">{{ $location->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-xs-12 col-md-6 col-xl-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('User') }}</span>
</div>
<select class="custom-control custom-select"
id="user-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($users as $user)
<option value="{{ $user->id }}">{{ $user->display_name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col"> <div class="col">
<div class="float-right"> <div class="float-right mt-1">
<a id="clear-filter-button" <a id="clear-filter-button"
class="btn btn-sm btn-outline-info" class="btn btn-sm btn-outline-info"
href="#"> href="#">

View File

@@ -8,17 +8,98 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<h2 class="title">@yield('title')</h2> <h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
</div>
</div> </div>
</div> </div>
<hr class="my-2"> <hr class="my-2">
<div class="row"> <div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-xs-12 col-md-6 col-xl-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-xs-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Product') }}</span>
</div>
<select class="custom-control custom-select"
id="product-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($products as $product)
<option value="{{ $product->id }}">{{ $product->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-xs-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Transaction type') }}</span>
</div>
<select class="custom-control custom-select"
id="transaction-type-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($transactionTypes as $transactionType)
<option value="{{ $transactionType }}">{{ $__t($transactionType) }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-xs-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('User') }}</span>
</div>
<select class="custom-control custom-select"
id="user-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($users as $user)
<option value="{{ $user->id }}">{{ $user->display_name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col"> <div class="col">
<table id="journal-summary-table" <div class="float-right mt-1">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col">
<table id="stock-journal-summary-table"
class="table table-sm table-striped nowrap w-100"> class="table table-sm table-striped nowrap w-100">
<thead> <thead>
<tr> <tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Hide/view columns') }}"
data-table-selector="#stock-journal-summary-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Product') }}</th> <th>{{ $__t('Product') }}</th>
<th>{{ $__t('Transaction type') }}</th> <th>{{ $__t('Transaction type') }}</th>
<th>{{ $__t('User') }}</th> <th>{{ $__t('User') }}</th>
@@ -28,6 +109,7 @@
<tbody class="d-none"> <tbody class="d-none">
@foreach($entries as $journalEntry) @foreach($entries as $journalEntry)
<tr> <tr>
<td class="fit-content border-right"></td>
<td> <td>
{{ $journalEntry->product_name }} {{ $journalEntry->product_name }}
</td> </td>

View File

@@ -84,7 +84,7 @@
'id' => 'stock_default_purchase_amount', 'id' => 'stock_default_purchase_amount',
'additionalAttributes' => 'data-setting-key="stock_default_purchase_amount"', 'additionalAttributes' => 'data-setting-key="stock_default_purchase_amount"',
'label' => 'Default amount for purchase', 'label' => 'Default amount for purchase',
'min' => 0, 'min' => '0.',
'decimals' => $userSettings['stock_decimal_places_amounts'], 'decimals' => $userSettings['stock_decimal_places_amounts'],
'additionalCssClasses' => 'user-setting-control locale-number-input locale-number-quantity-amount', 'additionalCssClasses' => 'user-setting-control locale-number-input locale-number-quantity-amount',
)) ))