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
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
40 changed files with 556 additions and 178 deletions

View File

@ -8,6 +8,7 @@
### New feature: Prefill purchase data by barcodes
- Imagine you buy for example eggs in different pack sizes and they have different barcodes
- Each product barcode can be assigned an amount, quantity unit and store (on the product edit page), which is then automatically prefilled on the purchase page
- Additionally, the last price per barcode will be tracked and prefilled as a "Total price" on purchase
- (Thanks @kriddles for the initial work on this)
### New feature: User permissions
@ -58,6 +59,7 @@
- When clicking the product name on the shopping list, the product card will now be displayed (like on the stock overview page) (thanks @kriddles)
- On the product card there is now also a button to jump directly to the stock entries of the corresponding product (thanks @kriddles)
- The product picker workflows can now also be started by `ENTER` (additionally to `TAB`)
- Added more filters on the stock journal page
- Added a grouped/summarized stock journal (new button "Journal summary" at the top of the stock journal page) (thanks @fipwmaqzufheoxq92ebc)
- Provides an overview of summarized transactions per product, transaction type and user + summarized amount
- Fixed that changing the products "Factor purchase to stock quantity unit" not longer messes up historical prices (which results for example in wrong recipe costs) (thanks @kriddles)
@ -80,6 +82,7 @@
- Decimal amounts are now allowed (for any product, rounded by two decimal places)
- Added a button to add all currently in-stock but overdue and expired products to the shopping list (thanks @m-byte)
- Improved that when `FEATURE_FLAG_STOCK` is disabled, all product/stock related inputs and buttons are now hidden on the shopping list page (thanks @fipwmaqzufheoxq92ebc)
- Shopping list items can now have their own Userfields (entity `shopping_list`), on the shopping list table those fields are rendered additionally to the product Userfields
- Fixed that "Add products that are below defined min. stock amount" always rounded up the missing amount to an integral number, this now allows decimal numbers
### Recipe improvements/fixes
@ -130,6 +133,7 @@
- Added a "Clear filter"-button on all pages (with filters) to quickly reset applied filters
- Prefilled number inputs now use sensible decimal places (max. the configured decimals while hiding trailing zeros where appropriate, means if you never use partial amounts for a product, you'll never see decimals for it)
- Improved / more precise validation messages for number inputs
- Ordering now happens case-insensitive
- The data path (previously fixed to the `data` folder) is now configurable, making it possible to run multiple grocy instances from the same directory (with different `config.php` files / different database, etc.) (thanks @fgrsnau)
- Via an environment variable `GROCY_DATAPATH` (higher priority)
- Via an FastCGI parameter `GROCY_DATAPATH` (lower priority)

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

View File

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

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)
{
return $this->renderPage($response, 'equipment', [
'equipment' => $this->getDatabase()->equipment()->orderBy('name'),
'equipment' => $this->getDatabase()->equipment()->orderBy('name', 'COLLATE NOCASE'),
'userfields' => $this->getUserfieldsService()->GetFields('equipment'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('equipment')
]);

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

View File

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

View File

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

View File

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

View File

@ -889,9 +889,6 @@ msgstr ""
msgid "Shopping list to stock workflow"
msgstr ""
msgid "Automatically do the booking using the last price and the amount of the shopping list item, if the product has \"Default best before days\" set"
msgstr ""
msgid "Skip"
msgstr ""
@ -1975,3 +1972,6 @@ msgstr ""
msgid "This must between %1$s and %2$s and needs to be a valid number with max. %3$s decimal places"
msgstr ""
msgid "Automatically do the booking using the last price and the amount of the shopping list item, if the product has \"Default due days\" set"
msgstr ""

View File

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

View File

@ -424,7 +424,7 @@ Grocy.FrontendHelpers.ShowGenericError = function(message, exception)
{
bootbox.alert({
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
});
}
@ -448,6 +448,11 @@ $(document).on("change", ".user-setting-control", function()
var element = $(this);
var settingKey = element.attr("data-setting-key");
if (!element[0].checkValidity())
{
return;
}
var inputType = "unknown";
if (typeof element.attr("type") !== typeof undefined && element.attr("type") !== false)
{

View File

@ -133,7 +133,7 @@ $('.input-group-chore-period-type').on('change', function(e)
{
$("label[for='period_days']").text(__t("Period days"));
$("#period_days").attr("min", "0");
$("#period_days").attr("max", "999999");
$("#period_days").removeAttr("max");
$('#chore-period-type-info').attr("data-original-title", __t('This means the next execution of this chore is scheduled %s days after the last execution', periodDays.toString()));
}
else if (periodType === 'daily')

View File

@ -32,30 +32,38 @@ $(".numberpicker").each(function()
{
mutations.forEach(function(mutation)
{
if (mutation.type == "attributes" && (mutation.attributeName == "min" || mutation.attributeName == "max" || mutation.attributeName == "data-not-equal"))
if (mutation.type == "attributes" && (mutation.attributeName == "min" || mutation.attributeName == "max" || mutation.attributeName == "data-not-equal" || mutation.attributeName == "data-initialised"))
{
var element = $(mutation.target);
var min = element.attr("min");
var max = element.attr("max");
var decimals = element.attr("data-decimals");
var max = "";
if (element.hasAttr("max"))
{
max = element.attr("max");
}
if (element.hasAttr("data-not-equal"))
{
var notEqual = element.attr("data-not-equal");
if (max.isEmpty() || max.startsWith("999999"))
if (notEqual != "NaN")
{
element.parent().find(".invalid-feedback").text(__t("This cannot be lower than %1$s or equal %2$s and needs to be a valid number with max. %3$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(notEqual).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), decimals));
}
else
{
element.parent().find(".invalid-feedback").text(__t("This must be between %1$s and %2$s, cannot equal %3$s and needs to be a valid number with max. %4$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(max).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(notEqual).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }), decimals));
}
if (max.isEmpty())
{
element.parent().find(".invalid-feedback").text(__t("This cannot be lower than %1$s or equal %2$s and needs to be a valid number with max. %3$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(notEqual).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), decimals));
}
else
{
element.parent().find(".invalid-feedback").text(__t("This must be between %1$s and %2$s, cannot equal %3$s and needs to be a valid number with max. %4$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(max).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(notEqual).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }), decimals));
}
return;
return;
}
}
if (max.isEmpty() || max.startsWith("999999"))
if (max.isEmpty())
{
element.parent().find(".invalid-feedback").text(__t("This cannot be lower than %1$s and needs to be a valid number with max. %2$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), decimals));
}
@ -69,4 +77,4 @@ $(".numberpicker").each(function()
attributes: true
});
});
$(".numberpicker").attr("min", $(".numberpicker").attr("min")); // Dummy change to trigger MutationObserver above once
$(".numberpicker").attr("data-initialised", "true"); // Dummy change to trigger MutationObserver above once

View File

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

View File

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

View File

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

View File

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

View File

@ -55,6 +55,16 @@ $('#save-purchase-button').on('click', function(e)
Grocy.Api.Post('stock/products/' + jsonForm.product_id + '/add', jsonData,
function(result)
{
if ($("#purchase-form").hasAttr("data-used-barcode"))
{
Grocy.Api.Put('objects/product_barcodes/' + $("#purchase-form").attr("data-used-barcode"), { last_price: $("#price").val() / $("#display_amount").val() },
function(result)
{ },
function(xhr)
{ }
);
}
if (BoolVal(Grocy.UserSettings.scan_mode_purchase_enabled))
{
Grocy.UISound.Success();
@ -108,6 +118,7 @@ $('#save-purchase-button').on('click', function(e)
}
Grocy.Components.ProductAmountPicker.Reset();
$("#purchase-form").removeAttr("data-used-barcode");
$("#display_amount").attr("min", "0." + "0".repeat(parseInt(Grocy.UserSettings.stock_decimal_places_amounts) - 1) + "1");
$('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_purchase_amount));
$(".input-group-productamountpicker").trigger("change");
@ -202,7 +213,7 @@ if (Grocy.Components.ProductPicker !== undefined)
Grocy.Components.LocationPicker.SetId(productDetails.location.id);
}
if (productDetails.last_price == null)
if (productDetails.last_price == null || productDetails.last_price == 0)
{
$("#price").val("")
}
@ -282,10 +293,11 @@ if (Grocy.Components.ProductPicker !== undefined)
if (barcodeResult != null)
{
var barcode = barcodeResult[0];
$("#purchase-form").attr("data-used-barcode", barcode.id);
if (barcode != null)
{
if (barcode.amount != null)
if (barcode.amount != null && !barcode.amount.isEmpty())
{
$("#display_amount").val(barcode.amount);
$("#display_amount").select();
@ -301,6 +313,12 @@ if (Grocy.Components.ProductPicker !== undefined)
Grocy.Components.ShoppingLocationPicker.SetId(barcode.shopping_location_id);
}
if (barcode.last_price != null && !barcode.last_price.isEmpty())
{
$("#price").val(barcode.last_price);
$("#price-type-total-price").click();
}
$(".input-group-productamountpicker").trigger("change");
Grocy.FrontendHelpers.ValidateForm('purchase-form');
}
@ -312,6 +330,10 @@ if (Grocy.Components.ProductPicker !== undefined)
}
);
}
else
{
$("#purchase-form").removeAttr("data-used-barcode");
}
$('#display_amount').trigger("keyup");
},
@ -450,7 +472,7 @@ function refreshPriceHint()
price = parseFloat(price / $('#display_amount').val()).toFixed(Grocy.UserSettings.stock_decimal_places_prices);
}
$('#price-hint').text(__t('means %1$s per %2$s', price.toLocaleString(undefined, { style: "currency", currency: Grocy.Currency, minimumFractionDigits: 2, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices }), $("#qu_id").attr("data-destination-qu-name")));
$('#price-hint').text(__t('means %1$s per %2$s', price.toLocaleString(undefined, { style: "currency", currency: Grocy.Currency, minimumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices }), $("#qu_id").attr("data-destination-qu-name")));
}
else
{

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-form").data("edit-mode", "create");
Grocy.Components.RecipePicker.Clear();
Grocy.Components.RecipePicker.GetInputElement().focus();
$("#recipe-include-editform-modal").modal("show");
Grocy.FrontendHelpers.ValidateForm("recipe-include-form");
},

View File

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

View File

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

View File

@ -23,6 +23,42 @@ $("#product-filter").on("change", function()
stockJournalTable.column(1).search(text).draw();
});
$("#transaction-type-filter").on("change", function()
{
var value = $(this).val();
var text = $("#transaction-type-filter option:selected").text();
if (value === "all")
{
text = "";
}
stockJournalTable.column(4).search(text).draw();
});
$("#location-filter").on("change", function()
{
var value = $(this).val();
var text = $("#location-filter option:selected").text();
if (value === "all")
{
text = "";
}
stockJournalTable.column(5).search(text).draw();
});
$("#user-filter").on("change", function()
{
var value = $(this).val();
var text = $("#user-filter option:selected").text();
if (value === "all")
{
text = "";
}
stockJournalTable.column(6).search(text).draw();
});
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
@ -37,6 +73,12 @@ $("#search").on("keyup", Delay(function()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
$("#transaction-type-filter").val("all");
$("#location-filter").val("all");
$("#user-filter").val("all");
stockJournalTable.column(4).search("").draw();
stockJournalTable.column(5).search("").draw();
stockJournalTable.column(6).search("").draw();
stockJournalTable.search("").draw();
});

View File

@ -1,8 +1,71 @@
var journalSummaryTable = $('#journal-summary-table').DataTable({
var journalSummaryTable = $('#stock-journal-summary-table').DataTable({
'paginate': true,
'order': [[0, 'desc']]
'order': [[1, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 },
{ 'searchable': false, "targets": 0 }
]
});
$('#journal-summary-table tbody').removeClass("d-none");
$('#stock-journal-summary-table tbody').removeClass("d-none");
journalSummaryTable.columns.adjust().draw();
$('.dataTables_scrollBody').addClass("dragscroll");
dragscroll.reset();
$("#product-filter").on("change", function()
{
var value = $(this).val();
var text = $("#product-filter option:selected").text();
if (value === "all")
{
text = "";
}
journalSummaryTable.column(1).search(text).draw();
});
$("#transaction-type-filter").on("change", function()
{
var value = $(this).val();
var text = $("#transaction-type-filter option:selected").text();
if (value === "all")
{
text = "";
}
journalSummaryTable.column(2).search(text).draw();
});
$("#user-filter").on("change", function()
{
var value = $(this).val();
var text = $("#user-filter option:selected").text();
if (value === "all")
{
text = "";
}
journalSummaryTable.column(3).search(text).draw();
});
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
{
value = "";
}
journalSummaryTable.search(value).draw();
}, 200));
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
$("#transaction-type-filter").val("all");
$("#location-filter").val("all");
$("#user-filter").val("all");
journalSummaryTable.column(1).search("").draw();
journalSummaryTable.column(2).search("").draw();
journalSummaryTable.column(3).search("").draw();
journalSummaryTable.search("").draw();
});

View File

@ -90,7 +90,7 @@
Grocy.Components.ProductAmountPicker.Reset();
$("#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("max", "999999");
$("#display_amount").removeAttr("max");
$('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_transfer_amount));
RefreshLocaleNumberInput();
$(".input-group-productamountpicker").trigger("change");
@ -198,6 +198,8 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
$("#tare-weight-handling-info").addClass("d-none");
}
$('#display_amount').attr("data-stock-amount", productDetails.stock_amount);
if ((parseFloat(productDetails.stock_amount) || 0) === 0)
{
Grocy.Components.ProductPicker.Clear();
@ -274,7 +276,7 @@ $("#location_id_from").on('change', function(e)
sumValue = sumValue + parseFloat(stockEntry.amount);
}
});
$("#display_amount").attr("max", sumValue);
$("#display_amount").attr("max", sumValue * $("#qu_id option:selected").attr("data-qu-factor"));
if (sumValue == 0)
{
$("#display_amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location'));
@ -299,6 +301,11 @@ $("#location_id_to").on('change', function(e)
}
});
$("#qu_id").on('change', function(e)
{
$("#display_amount").attr("max", parseFloat($('#display_amount').attr("data-stock-amount")) * $("#qu_id option:selected").attr("data-qu-factor"));
});
$('#display_amount').on('focus', function(e)
{
$(this).select();
@ -346,7 +353,7 @@ $("#specific_stock_entry").on("change", function(e)
sumValue = sumValue + parseFloat(stockEntry.amount);
}
});
$("#display_amount").attr("max", sumValue);
$("#display_amount").attr("max", sumValue * $("#qu_id option:selected").attr("data-qu-factor"));
if (sumValue == 0)
{
$("#display_amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location'));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -187,7 +187,7 @@
$productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id);
$productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock);
$productQuConversion = FindObjectInArrayByPropertyValue($productQuConversions, 'to_qu_id', $recipePosition->qu_id);
if ($productQuConversion)
if ($productQuConversion && $recipePosition->only_check_single_unit_in_stock == 0)
{
$recipePosition->amount = $recipePosition->amount * $productQuConversion->factor;
}

View File

@ -398,7 +398,7 @@
$productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id);
$productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock);
$productQuConversion = FindObjectInArrayByPropertyValue($productQuConversions, 'to_qu_id', $selectedRecipePosition->qu_id);
if ($productQuConversion)
if ($productQuConversion && $selectedRecipePosition->only_check_single_unit_in_stock == 0)
{
$selectedRecipePosition->recipe_amount = $selectedRecipePosition->recipe_amount * $productQuConversion->factor;
}

View File

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

View File

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

View File

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

View File

@ -34,7 +34,7 @@
<div class="row collapse d-md-flex"
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-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
@ -45,7 +45,7 @@
placeholder="{{ $__t('Search') }}">
</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-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Product') }}</span>
@ -59,8 +59,50 @@
</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-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="float-right">
<div class="float-right mt-1">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">

View File

@ -8,17 +8,98 @@
<div class="row">
<div class="col">
<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>
<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">
<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">
<thead>
<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('Transaction type') }}</th>
<th>{{ $__t('User') }}</th>
@ -28,6 +109,7 @@
<tbody class="d-none">
@foreach($entries as $journalEntry)
<tr>
<td class="fit-content border-right"></td>
<td>
{{ $journalEntry->product_name }}
</td>

View File

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