mirror of
https://github.com/grocy/grocy.git
synced 2026-04-05 12:26:15 +02:00
Merge remote-tracking branch 'origin' into grocycode
This commit is contained in:
commit
18ad5a8f12
|
|
@ -21,10 +21,10 @@ Please don't send me private messages regarding grocy help. I check the issue tr
|
|||
See the website for a list of community contributed Add-ons / Tools: [https://grocy.info/addons](https://grocy.info/addons)
|
||||
|
||||
## Motivation
|
||||
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete household management"-thing. ERP your fridge!
|
||||
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# Windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete household management"-thing. ERP your fridge!
|
||||
|
||||
## How to install
|
||||
> Checkout [grocy-desktop](https://github.com/grocy/grocy-desktop), if you want to run grocy without having to manage a webserver just like a normal ("indows) desktop application.
|
||||
> Checkout [grocy-desktop](https://github.com/grocy/grocy-desktop), if you want to run grocy without having to manage a webserver just like a normal (Windows) desktop application.
|
||||
>
|
||||
> Directly download the [latest release](https://releases.grocy.info/latest-desktop) - the installation is nothing more than just clicking 2 times "next".
|
||||
|
||||
|
|
|
|||
29
changelog/62_UNRELEASED_xxxx-xx-xx.md
Normal file
29
changelog/62_UNRELEASED_xxxx-xx-xx.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
### Stock improvements/fixes
|
||||
- Product barcodes are now enforced to be unique across products
|
||||
- Fixed that editing stock entries was not possible
|
||||
- Fixed that consuming with Scan Mode was not possible
|
||||
- Fixed that the current stock total value (header of the stock overview page) didn't include decimal amounts (thanks @Ape)
|
||||
- Fixed that the transfer page was not fully populated when opening it from the stock entries page
|
||||
|
||||
### Shopping list improvements/fixes
|
||||
- The amount now defaults to `1` for adding items quicker
|
||||
- Fixed that shopping list prints had a grey background (thanks @Forceu)
|
||||
- Fixed the form validation on the shopping list item page (thanks @Forceu)
|
||||
|
||||
### Recipe improvements/fixes
|
||||
- Recipe printing improvements (thanks @Ape)
|
||||
- Calories are now always displayed per single serving (on the recipe and meal plan page)
|
||||
- Fixed that "Only check if any amount is in stock" (recipe ingredient option) didn't work for stock amounts < 1
|
||||
|
||||
### Chores fixes
|
||||
- Fixed that tracking chores with "Done by" a different user was not possible
|
||||
|
||||
### Userfield fixes
|
||||
- Fixed that numeric Userfields were initialised with `1.0`
|
||||
|
||||
### General & other improvements/fixes
|
||||
- Some night mode style improvements (thanks @BlizzWave and @KTibow)
|
||||
- Fixed that the number picker up/down buttons did not work when the input field was empty or contained an invalid number
|
||||
|
||||
### API fixes
|
||||
- Fixed that due soon products with `due_type` = "Expiration date" were missing in `due_products` of the `/stock/volatile` endpoint
|
||||
|
|
@ -25,5 +25,8 @@
|
|||
"files": [
|
||||
"helpers/extensions.php"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"platform-check": false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ DefaultUserSetting('stock_default_consume_amount_use_quick_consume_amount', fals
|
|||
DefaultUserSetting('scan_mode_consume_enabled', false);
|
||||
DefaultUserSetting('scan_mode_purchase_enabled', false);
|
||||
DefaultUserSetting('show_icon_on_stock_overview_page_when_product_is_on_shopping_list', true);
|
||||
DefaultUserSetting('show_purchased_date_on_purchase', false); // Wheter the purchased date should be editable on purchase (defaults to today otherwise)
|
||||
DefaultUserSetting('show_purchased_date_on_purchase', false); // Whether the purchased date should be editable on purchase (defaults to today otherwise)
|
||||
DefaultUserSetting('show_warning_on_purchase_when_due_date_is_earlier_than_next', true); // Show a warning on purchase when the due date of the purchased product is earlier than the next due date in stock
|
||||
|
||||
// Shopping list settings
|
||||
|
|
@ -201,4 +201,4 @@ Setting('FEATURE_FLAG_CHORES_ASSIGNMENTS', true);
|
|||
|
||||
// Feature settings
|
||||
Setting('FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT', true); // When set to true, opened items will be counted as missing for calculating if a product is below its minimum stock amount
|
||||
Setting('FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA', true); // Enables the torch automaticaly (if the device has one)
|
||||
Setting('FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA', true); // Enables the torch automatically (if the device has one)
|
||||
|
|
|
|||
|
|
@ -157,18 +157,18 @@ class BaseController
|
|||
if (GROCY_AUTHENTICATED)
|
||||
{
|
||||
$this->View->set('permissions', User::PermissionList());
|
||||
}
|
||||
|
||||
$decimalPlacesAmounts = intval($this->getUsersService()->GetUserSetting(GROCY_USER_ID, 'stock_decimal_places_amounts'));
|
||||
if ($decimalPlacesAmounts <= 0)
|
||||
{
|
||||
$defaultMinAmount = 1;
|
||||
$decimalPlacesAmounts = intval($this->getUsersService()->GetUserSetting(GROCY_USER_ID, 'stock_decimal_places_amounts'));
|
||||
if ($decimalPlacesAmounts <= 0)
|
||||
{
|
||||
$defaultMinAmount = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
$defaultMinAmount = '0.' . str_repeat('0', $decimalPlacesAmounts - 1) . '1';
|
||||
}
|
||||
$this->View->set('DEFAULT_MIN_AMOUNT', $defaultMinAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
$defaultMinAmount = '0.' . str_repeat('0', $decimalPlacesAmounts - 1) . '1';
|
||||
}
|
||||
$this->View->set('DEFAULT_MIN_AMOUNT', $defaultMinAmount);
|
||||
|
||||
return $this->View->render($response, $page, $data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class ChoresApiController extends BaseApiController
|
|||
|
||||
if ($doneBy != GROCY_USER_ID)
|
||||
{
|
||||
User::checkPermission($request, User::PERMISSION_CHORE_TRACK_EXECUTION_EXECUTION);
|
||||
User::checkPermission($request, User::PERMISSION_CHORE_TRACK_EXECUTION);
|
||||
}
|
||||
|
||||
$choreExecutionId = $this->getChoresService()->TrackChore($args['choreId'], $trackedTime, $doneBy);
|
||||
|
|
|
|||
|
|
@ -156,6 +156,11 @@ function BoolToString(bool $bool)
|
|||
return $bool ? 'true' : 'false';
|
||||
}
|
||||
|
||||
function BoolToInt(bool $bool)
|
||||
{
|
||||
return $bool ? 1 : 0;
|
||||
}
|
||||
|
||||
function ExternalSettingValue(string $value)
|
||||
{
|
||||
$tvalue = rtrim($value, "\r\n");
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ abstract class AuthMiddleware extends BaseMiddleware
|
|||
/**
|
||||
* @param array $postParams
|
||||
* @return bool True/False if the provided credentials were valid
|
||||
* @throws \Exception Throws an \Exception if an error happended during credentials processing or if this AuthMiddleware doesn't provide credentials processing (e. g. handles this externally)
|
||||
* @throws \Exception Throws an \Exception if an error happened during credentials processing or if this AuthMiddleware doesn't provide credentials processing (e. g. handles this externally)
|
||||
*/
|
||||
abstract public static function ProcessLogin(array $postParams);
|
||||
|
||||
|
|
|
|||
13
migrations/0128.sql
Normal file
13
migrations/0128.sql
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
-- Duplicate product barcodes were most probably not created on purpose,
|
||||
-- so just keep the newer one for any duplicates
|
||||
DELETE FROM product_barcodes
|
||||
WHERE id IN (
|
||||
SELECT MIN(id)
|
||||
FROM product_barcodes
|
||||
GROUP BY barcode
|
||||
HAVING COUNT(*) > 1
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX ix_product_barcodes ON product_barcodes (
|
||||
barcode
|
||||
);
|
||||
99
migrations/0129.sql
Normal file
99
migrations/0129.sql
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
DROP VIEW recipes_pos_resolved;
|
||||
CREATE VIEW recipes_pos_resolved
|
||||
AS
|
||||
|
||||
-- Multiplication by 1.0 to force conversion to float (REAL)
|
||||
|
||||
SELECT
|
||||
r.id AS recipe_id,
|
||||
rp.id AS recipe_pos_id,
|
||||
rp.product_id AS product_id,
|
||||
rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 * CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN r.base_servings * rnrr.base_servings*1.0 ELSE 1 END) AS recipe_amount,
|
||||
IFNULL(sc.amount_aggregated, 0) AS stock_amount,
|
||||
CASE WHEN IFNULL(sc.amount_aggregated, 0) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 0.00000001 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END THEN 1 ELSE 0 END AS need_fulfilled,
|
||||
CASE WHEN IFNULL(sc.amount_aggregated, 0) - CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 0.00000001 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END < 0 THEN ABS(IFNULL(sc.amount_aggregated, 0) - (CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 * CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN r.base_servings * rnrr.base_servings*1.0 ELSE 1 END) END)) ELSE 0 END AS missing_amount,
|
||||
IFNULL(sl.amount, 0) * p.qu_factor_purchase_to_stock AS amount_on_shopping_list,
|
||||
CASE WHEN IFNULL(sc.amount_aggregated, 0) + (CASE WHEN r.not_check_shoppinglist = 1 THEN 0 ELSE IFNULL(sl.amount, 0) END * p.qu_factor_purchase_to_stock) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 0.00000001 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END THEN 1 ELSE 0 END AS need_fulfilled_with_shopping_list,
|
||||
rp.qu_id,
|
||||
(CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END) * rp.amount * pop.price * rp.price_factor AS costs,
|
||||
CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN 0 ELSE 1 END AS is_nested_recipe_pos,
|
||||
rp.ingredient_group,
|
||||
pg.name as product_group,
|
||||
rp.id, -- Just a dummy id column
|
||||
r.type as recipe_type,
|
||||
rnr.includes_recipe_id as child_recipe_id,
|
||||
rp.note,
|
||||
rp.variable_amount AS recipe_variable_amount,
|
||||
rp.only_check_single_unit_in_stock,
|
||||
rp.amount / r.base_servings*1.0 * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) * IFNULL(p.calories, 0) AS calories,
|
||||
p.active AS product_active
|
||||
FROM recipes r
|
||||
JOIN recipes_nestings_resolved rnr
|
||||
ON r.id = rnr.recipe_id
|
||||
JOIN recipes rnrr
|
||||
ON rnr.includes_recipe_id = rnrr.id
|
||||
JOIN recipes_pos rp
|
||||
ON rnr.includes_recipe_id = rp.recipe_id
|
||||
JOIN products p
|
||||
ON rp.product_id = p.id
|
||||
LEFT JOIN product_groups pg
|
||||
ON p.product_group_id = pg.id
|
||||
LEFT JOIN (
|
||||
SELECT product_id, SUM(amount) AS amount
|
||||
FROM shopping_list
|
||||
GROUP BY product_id) sl
|
||||
ON rp.product_id = sl.product_id
|
||||
LEFT JOIN stock_current sc
|
||||
ON rp.product_id = sc.product_id
|
||||
LEFT JOIN products_oldest_stock_unit_price pop
|
||||
ON rp.product_id = pop.product_id
|
||||
WHERE rp.not_check_stock_fulfillment = 0
|
||||
|
||||
UNION
|
||||
|
||||
-- Just add all recipe positions which should not be checked against stock with fulfilled need
|
||||
|
||||
SELECT
|
||||
r.id AS recipe_id,
|
||||
rp.id AS recipe_pos_id,
|
||||
rp.product_id AS product_id,
|
||||
rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 * CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN r.base_servings * rnrr.base_servings*1.0 ELSE 1 END) AS recipe_amount,
|
||||
IFNULL(sc.amount_aggregated, 0) AS stock_amount,
|
||||
1 AS need_fulfilled,
|
||||
0 AS missing_amount,
|
||||
IFNULL(sl.amount, 0) * p.qu_factor_purchase_to_stock AS amount_on_shopping_list,
|
||||
1 AS need_fulfilled_with_shopping_list,
|
||||
rp.qu_id,
|
||||
(CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END) * rp.amount * IFNULL(pop.price, 0) * rp.price_factor AS costs,
|
||||
CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN 0 ELSE 1 END AS is_nested_recipe_pos,
|
||||
rp.ingredient_group,
|
||||
pg.name as product_group,
|
||||
rp.id, -- Just a dummy id column
|
||||
r.type as recipe_type,
|
||||
rnr.includes_recipe_id as child_recipe_id,
|
||||
rp.note,
|
||||
rp.variable_amount AS recipe_variable_amount,
|
||||
rp.only_check_single_unit_in_stock,
|
||||
rp.amount / r.base_servings*1.0 * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) * IFNULL(p.calories, 0) AS calories,
|
||||
p.active AS product_active
|
||||
FROM recipes r
|
||||
JOIN recipes_nestings_resolved rnr
|
||||
ON r.id = rnr.recipe_id
|
||||
JOIN recipes rnrr
|
||||
ON rnr.includes_recipe_id = rnrr.id
|
||||
JOIN recipes_pos rp
|
||||
ON rnr.includes_recipe_id = rp.recipe_id
|
||||
JOIN products p
|
||||
ON rp.product_id = p.id
|
||||
LEFT JOIN product_groups pg
|
||||
ON p.product_group_id = pg.id
|
||||
LEFT JOIN (
|
||||
SELECT product_id, SUM(amount) AS amount
|
||||
FROM shopping_list
|
||||
GROUP BY product_id) sl
|
||||
ON rp.product_id = sl.product_id
|
||||
LEFT JOIN stock_current sc
|
||||
ON rp.product_id = sc.product_id
|
||||
LEFT JOIN products_oldest_stock_unit_price pop
|
||||
ON rp.product_id = pop.product_id
|
||||
WHERE rp.not_check_stock_fulfillment = 1;
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
body.night-mode {
|
||||
body.night-mode {
|
||||
color: #c1c1c1;
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
.night-mode .navbar-brand img {
|
||||
filter: invert(0.9) hue-rotate(176deg);
|
||||
}
|
||||
|
||||
.night-mode .table-info,
|
||||
.night-mode .table-info > td,
|
||||
.night-mode .table-info > th,
|
||||
|
|
@ -12,7 +16,7 @@
|
|||
}
|
||||
|
||||
.night-mode .table {
|
||||
color: #6c757d;
|
||||
color: #c1c1c1;
|
||||
}
|
||||
|
||||
.night-mode table.dataTable tr.dtrg-group td,
|
||||
|
|
@ -24,6 +28,7 @@
|
|||
.night-mode .btn,
|
||||
.night-mode .nav-link,
|
||||
.night-mode #mainNav.navbar-light .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link::after,
|
||||
.night-mode #mainNav.navbar-light .navbar-collapse .navbar-sidenav .nav-link-collapse::after,
|
||||
.night-mode .dropdown-item {
|
||||
color: #c1c1c1 !important;
|
||||
}
|
||||
|
|
@ -32,6 +37,11 @@
|
|||
border-color: #c1c1c1;
|
||||
}
|
||||
|
||||
.night-mode .btn-outline-info {
|
||||
color: #1ed1ee !important;
|
||||
border-color: #1ed1ee !important;
|
||||
}
|
||||
|
||||
.night-mode .btn-info {
|
||||
color: #c1c1c1;
|
||||
background-color: #07373f;
|
||||
|
|
@ -46,14 +56,14 @@
|
|||
|
||||
.night-mode .btn-danger {
|
||||
color: #c1c1c1;
|
||||
background-color: #471116;
|
||||
border-color: #471116;
|
||||
background-color: #6f1b23;
|
||||
border-color: #6f1b23;
|
||||
}
|
||||
|
||||
.night-mode .btn-success {
|
||||
color: #c1c1c1;
|
||||
background-color: #0d3a18;
|
||||
border-color: #0d3a18;
|
||||
background-color: #17642a;
|
||||
border-color: #17642a;
|
||||
}
|
||||
|
||||
.night-mode .btn-light,
|
||||
|
|
@ -71,6 +81,10 @@
|
|||
border: 1px solid #ced4da;
|
||||
}
|
||||
|
||||
.night-mode ::placeholder {
|
||||
color: #b1bac4;
|
||||
}
|
||||
|
||||
.night-mode .content-wrapper {
|
||||
background: #333131;
|
||||
}
|
||||
|
|
@ -110,6 +124,11 @@
|
|||
border-color: #80bdff;
|
||||
}
|
||||
|
||||
.night-mode select {
|
||||
color: #ced4da;
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
.night-mode .dropdown-item:focus,
|
||||
.night-mode .dropdown-item:hover {
|
||||
color: #16181b;
|
||||
|
|
@ -139,13 +158,13 @@
|
|||
}
|
||||
|
||||
.night-mode a.discrete-link:hover {
|
||||
color: #16354f !important;
|
||||
color: #83c6ff !important;
|
||||
text-decoration: none !important;
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
.night-mode a.discrete-link:focus {
|
||||
color: #3a0b0f !important;
|
||||
color: #ffa8af !important;
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
|
|
@ -204,7 +223,7 @@
|
|||
}
|
||||
|
||||
.night-mode .active-page {
|
||||
box-shadow: inset 5px 0 0 #350a0f !important;
|
||||
box-shadow: inset 5px 0 0 #ff7585 !important;
|
||||
background-color: #383838 !important;
|
||||
}
|
||||
|
||||
|
|
@ -241,3 +260,20 @@
|
|||
.night-mode .dropdown-menu {
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
.night-mode .table-secondary td,
|
||||
.night-mode .table-secondary th {
|
||||
background-color: #4c4e50;
|
||||
}
|
||||
|
||||
.night-mode .secondary-message {
|
||||
border-top-color: #4c4e50;
|
||||
}
|
||||
|
||||
.night-mode .normal-message {
|
||||
border-top-color: #07373f;
|
||||
}
|
||||
|
||||
.night-mode .text-muted {
|
||||
color: #8f9ba5 !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ Grocy.Components.BarcodeScanner.CheckCapabilities = async function()
|
|||
Grocy.Components.BarcodeScanner.TorchOn(track);
|
||||
}
|
||||
|
||||
// Reduce the height of the video, if it's heigher than then the viewport
|
||||
// Reduce the height of the video, if it's higher than then the viewport
|
||||
if (!Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted)
|
||||
{
|
||||
var bc = document.getElementById('barcodescanner-container');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
$(".numberpicker-down-button").unbind('click').on("click", function()
|
||||
{
|
||||
var inputElement = $(this).parent().parent().find('input[type="number"]');
|
||||
inputElement.val(parseFloat(inputElement.val()) - 1);
|
||||
inputElement.val(parseFloat(inputElement.val() || 1) - 1);
|
||||
inputElement.trigger('keyup');
|
||||
inputElement.trigger('change');
|
||||
});
|
||||
|
|
@ -9,7 +9,7 @@ $(".numberpicker-down-button").unbind('click').on("click", function()
|
|||
$(".numberpicker-up-button").unbind('click').on("click", function()
|
||||
{
|
||||
var inputElement = $(this).parent().parent().find('input[type="number"]');
|
||||
inputElement.val(parseFloat(inputElement.val()) + 1);
|
||||
inputElement.val(parseFloat(inputElement.val() || 0) + 1);
|
||||
inputElement.trigger('keyup');
|
||||
inputElement.trigger('change');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -278,6 +278,11 @@ $("#location_id").on('change', function(e)
|
|||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
|
||||
if (document.getElementById("product_id").getAttribute("barcode") == "null")
|
||||
{
|
||||
ScanModeSubmit();
|
||||
}
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
|
|
@ -382,10 +387,9 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
|||
$(".input-group-productamountpicker").trigger("change");
|
||||
Grocy.FrontendHelpers.ValidateForm('consume-form');
|
||||
RefreshLocaleNumberInput();
|
||||
ScanModeSubmit(false);
|
||||
}
|
||||
}
|
||||
|
||||
ScanModeSubmit(false);
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
|
|
@ -393,10 +397,6 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
|||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScanModeSubmit();
|
||||
}
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
|
|
@ -625,6 +625,11 @@ var current_productDetails;
|
|||
function RefreshForm()
|
||||
{
|
||||
var productDetails = current_productDetails;
|
||||
if (!productDetails)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (productDetails.product.enable_tare_weight_handling == 1)
|
||||
{
|
||||
$("#consume-exact-amount-group").removeClass("d-none");
|
||||
|
|
@ -663,12 +668,10 @@ function ScanModeSubmit(singleUnit = true)
|
|||
if (singleUnit)
|
||||
{
|
||||
$("#display_amount").val(1);
|
||||
$(".input-group-productamountpicker").trigger("change");
|
||||
}
|
||||
|
||||
RefreshLocaleNumberInput();
|
||||
$(".input-group-productamountpicker").trigger("change");
|
||||
Grocy.FrontendHelpers.ValidateForm("consume-form");
|
||||
|
||||
if (document.getElementById("consume-form").checkValidity() === true)
|
||||
{
|
||||
$('#save-consume-button').click();
|
||||
|
|
|
|||
|
|
@ -449,6 +449,7 @@ $(document).on("click", "#print-shopping-list-button", function(e)
|
|||
callback: function()
|
||||
{
|
||||
bootbox.hideAll();
|
||||
$('.modal-backdrop').remove();
|
||||
|
||||
$(".print-timestamp").text(moment().format("l LT"));
|
||||
|
||||
|
|
@ -470,6 +471,7 @@ $(document).on("click", "#print-shopping-list-button", function(e)
|
|||
shoppingListPrintShadowTable.draw();
|
||||
}
|
||||
|
||||
$(".print-layout-container").addClass("d-none");
|
||||
$("." + $("input[name='print-layout-type']:checked").val()).removeClass("d-none");
|
||||
|
||||
window.print();
|
||||
|
|
|
|||
|
|
@ -162,6 +162,12 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
|||
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.default_quantity_unit_purchase.id);
|
||||
}
|
||||
|
||||
if ($("#display_amount").val().toString().isEmpty())
|
||||
{
|
||||
$("#display_amount").val(1);
|
||||
$("#display_amount").trigger("change");
|
||||
}
|
||||
|
||||
$('#display_amount').focus();
|
||||
Grocy.FrontendHelpers.ValidateForm('shoppinglist-form');
|
||||
Grocy.ShoppingListItemFormInitialLoadDone = true;
|
||||
|
|
@ -244,17 +250,14 @@ if (GetUriParam("embedded") !== undefined)
|
|||
}
|
||||
|
||||
var eitherRequiredFields = $("#product_id,#product_id_text_input,#note");
|
||||
eitherRequiredFields.on("input", function()
|
||||
eitherRequiredFields.prop('required', "");
|
||||
eitherRequiredFields.on('input', function()
|
||||
{
|
||||
eitherRequiredFields.attr("required", "");
|
||||
if (!$(this).val().isEmpty())
|
||||
{
|
||||
eitherRequiredFields.not(this).removeAttr("required");
|
||||
}
|
||||
|
||||
eitherRequiredFields.not(this).prop('required', !$(this).val().length);
|
||||
Grocy.FrontendHelpers.ValidateForm('shoppinglist-form');
|
||||
});
|
||||
|
||||
|
||||
if (GetUriParam("product-name") != null)
|
||||
{
|
||||
Grocy.Components.ProductPicker.GetPicker().trigger('change');
|
||||
|
|
|
|||
|
|
@ -229,12 +229,6 @@ function RefreshStatistics()
|
|||
Grocy.Api.Get('stock',
|
||||
function(result)
|
||||
{
|
||||
var amountSum = 0;
|
||||
result.forEach(element =>
|
||||
{
|
||||
amountSum += parseInt(element.amount);
|
||||
});
|
||||
|
||||
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
|
||||
{
|
||||
$("#info-current-stock").text(__n(result.length, '%s Product', '%s Products'));
|
||||
|
|
@ -244,7 +238,7 @@ function RefreshStatistics()
|
|||
var valueSum = 0;
|
||||
result.forEach(element =>
|
||||
{
|
||||
valueSum += parseInt(element.value);
|
||||
valueSum += parseFloat(element.value);
|
||||
});
|
||||
$("#info-current-stock").text(__n(result.length, '%s Product', '%s Products') + ", " + __t('%s total value', valueSum.toLocaleString(undefined, { style: "currency", currency: Grocy.Currency })));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,12 +126,15 @@
|
|||
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
||||
{
|
||||
$("#specific_stock_entry").find("option").remove().end().append("<option></option>");
|
||||
if ($("#use_specific_stock_entry").is(":checked"))
|
||||
if ($("#use_specific_stock_entry").is(":checked") && GetUriParam("stockId") == null)
|
||||
{
|
||||
$("#use_specific_stock_entry").click();
|
||||
}
|
||||
$("#location_id_to").val("");
|
||||
$("#location_id_from").val("");
|
||||
if (GetUriParam("stockId") == null)
|
||||
{
|
||||
$("#location_id_from").val("");
|
||||
}
|
||||
|
||||
var productId = $(e.target).val();
|
||||
|
||||
|
|
@ -185,6 +188,12 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
|||
$("#location_id_from").trigger('change');
|
||||
}
|
||||
});
|
||||
|
||||
if (GetUriParam("locationId") != null)
|
||||
{
|
||||
$("#location_id_from").val(GetUriParam("locationId"));
|
||||
$("#location_id_from").trigger("change");
|
||||
}
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
|
|
@ -284,7 +293,7 @@ $("#location_id_from").on('change', function(e)
|
|||
}
|
||||
|
||||
$("#specific_stock_entry").find("option").remove().end().append("<option></option>");
|
||||
if ($("#use_specific_stock_entry").is(":checked"))
|
||||
if ($("#use_specific_stock_entry").is(":checked") && GetUriParam("stockId") == null)
|
||||
{
|
||||
$("#use_specific_stock_entry").click();
|
||||
}
|
||||
|
|
@ -475,6 +484,7 @@ if (GetUriParam("embedded") !== undefined)
|
|||
$("#location_id_from").trigger('change');
|
||||
$("#use_specific_stock_entry").click();
|
||||
$("#use_specific_stock_entry").trigger('change');
|
||||
Grocy.Components.ProductPicker.GetPicker().trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -431,11 +431,11 @@ class StockService extends BaseService
|
|||
|
||||
$openedDate = $stockRow->opened_date;
|
||||
|
||||
if ($open && $openedDate == null)
|
||||
if (boolval($open) && $openedDate == null)
|
||||
{
|
||||
$openedDate = date('Y-m-d');
|
||||
}
|
||||
elseif (!$open)
|
||||
elseif (!boolval($open))
|
||||
{
|
||||
$openedDate = null;
|
||||
}
|
||||
|
|
@ -447,7 +447,7 @@ class StockService extends BaseService
|
|||
'location_id' => $locationId,
|
||||
'shopping_location_id' => $shoppingLocationId,
|
||||
'opened_date' => $openedDate,
|
||||
'open' => $open,
|
||||
'open' => BoolToInt($open),
|
||||
'purchased_date' => $purchasedDate
|
||||
]);
|
||||
|
||||
|
|
@ -549,7 +549,6 @@ class StockService extends BaseService
|
|||
{
|
||||
$currentStock = $this->GetCurrentStock(false);
|
||||
$currentStock = FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d 23:59:59', strtotime("+$days days")), '<');
|
||||
$currentStock = FindAllObjectsInArrayByPropertyValue($currentStock, 'due_type', 1);
|
||||
|
||||
if ($excludeOverdue)
|
||||
{
|
||||
|
|
@ -995,8 +994,8 @@ class StockService extends BaseService
|
|||
|
||||
if ($productDetails->product->enable_tare_weight_handling == 1)
|
||||
{
|
||||
// Hard fail for now, as we not yet support transfering tare weight enabled products
|
||||
throw new \Exception('Transfering tare weight enabled products is not yet possible');
|
||||
// Hard fail for now, as we not yet support transferring tare weight enabled products
|
||||
throw new \Exception('Transferring tare weight enabled products is not yet possible');
|
||||
if ($amount < floatval($productDetails->product->tare_weight))
|
||||
{
|
||||
throw new \Exception('The amount cannot be lower than the defined tare weight');
|
||||
|
|
@ -1010,7 +1009,7 @@ class StockService extends BaseService
|
|||
|
||||
if ($amount > $productStockAmountAtFromLocation)
|
||||
{
|
||||
throw new \Exception('Amount to be transfered cannot be > current stock amount at the source location');
|
||||
throw new \Exception('Amount to be transferred cannot be > current stock amount at the source location');
|
||||
}
|
||||
|
||||
if ($specificStockEntryId !== 'default')
|
||||
|
|
@ -1141,7 +1140,7 @@ class StockService extends BaseService
|
|||
'amount' => $restStockAmount
|
||||
]);
|
||||
|
||||
// The transfered amount gets into a new stock entry
|
||||
// The transferred amount gets into a new stock entry
|
||||
$stockEntryNew = $this->getDatabase()->stock()->createRow([
|
||||
'product_id' => $stockEntry->product_id,
|
||||
'amount' => $amount,
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@
|
|||
'min' => 0,
|
||||
'isRequired' => false,
|
||||
'additionalCssClasses' => 'userfield-input',
|
||||
'additionalAttributes' => 'data-userfield-name="' . $userfield->name . '"'
|
||||
'additionalAttributes' => 'data-userfield-name="' . $userfield->name . '"',
|
||||
'value' => ''
|
||||
))
|
||||
@elseif($userfield->type == \Grocy\Services\UserfieldsService::USERFIELD_TYPE_DECIMAL_NUMBER)
|
||||
@include('components.numberpicker', array(
|
||||
|
|
@ -45,7 +46,8 @@
|
|||
'decimals' => 4,
|
||||
'isRequired' => false,
|
||||
'additionalCssClasses' => 'userfield-input',
|
||||
'additionalAttributes' => 'data-userfield-name="' . $userfield->name . '"'
|
||||
'additionalAttributes' => 'data-userfield-name="' . $userfield->name . '"',
|
||||
'value' => ''
|
||||
))
|
||||
@elseif($userfield->type == \Grocy\Services\UserfieldsService::USERFIELD_TYPE_DATE)
|
||||
@include('components.datetimepicker', array(
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
'id' => 'purchased_date',
|
||||
'label' => 'Purchased date',
|
||||
'format' => 'YYYY-MM-DD',
|
||||
'hint' => 'This will apply to added products',
|
||||
'hint' => $__t('This will apply to added products'),
|
||||
'initWithNow' => true,
|
||||
'limitEndToNow' => false,
|
||||
'limitStartToNow' => false,
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
@include('components.datetimepicker', array(
|
||||
'id' => 'best_before_date',
|
||||
'label' => 'Due date',
|
||||
'hint' => 'This will apply to added products',
|
||||
'hint' => $__t('This will apply to added products'),
|
||||
'format' => 'YYYY-MM-DD',
|
||||
'initWithNow' => false,
|
||||
'limitEndToNow' => false,
|
||||
|
|
|
|||
|
|
@ -688,7 +688,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, '<span class="locale-number locale-number-quantity-amount">' . $quConversion->factor . '</span>', 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>', $__n($quConversion->factor, FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->to_qu_id)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->to_qu_id)->name_plural)) !!}
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -311,16 +311,20 @@
|
|||
<div class="row ml-1">
|
||||
@if(!empty($calories) && intval($calories) > 0)
|
||||
<div class="col-6 col-xl-3">
|
||||
<label>{{ $__t('Calories') }}</label>
|
||||
<label>{{ $__t('Energy (kcal)') }}</label>
|
||||
<i class="fas fa-question-circle text-muted d-print-none"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('per serving') }}"></i>
|
||||
<h3 class="locale-number locale-number-generic pt-0">{{ $calories }}</h3>
|
||||
</div>
|
||||
@endif
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
|
||||
<div class="col-5">
|
||||
<label>{{ $__t('Costs') }} </label>
|
||||
<i class="fas fa-question-circle text-muted"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('Based on the prices of the default consume rule which is "Opened first, then first due first, then first in first out"') }}"></i>
|
||||
<label>{{ $__t('Costs') }}
|
||||
<i class="fas fa-question-circle text-muted d-print-none"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('Based on the prices of the default consume rule which is "Opened first, then first due first, then first in first out"') }}"></i>
|
||||
</label>
|
||||
<h3 class="locale-number locale-number-currency pt-0">{{ $costs }}</h3>
|
||||
</div>
|
||||
@endif
|
||||
|
|
@ -410,8 +414,10 @@
|
|||
@endif
|
||||
{{ $__n($selectedRecipePosition->recipe_amount, FindObjectInArrayByPropertyValue($quantityUnits, 'id', $selectedRecipePosition->qu_id)->name, FindObjectInArrayByPropertyValue($quantityUnits, 'id', $selectedRecipePosition->qu_id)->name_plural) }} {{ FindObjectInArrayByPropertyValue($products, 'id', $selectedRecipePosition->product_id)->name }}
|
||||
@if(GROCY_FEATURE_FLAG_STOCK)
|
||||
@if($selectedRecipePosition->need_fulfilled == 1)<i class="fas fa-check text-success"></i>@elseif($selectedRecipePosition->need_fulfilled_with_shopping_list == 1)<i class="fas fa-exclamation text-warning"></i>@else<i class="fas fa-times text-danger"></i>@endif
|
||||
<span class="timeago-contextual">@if(FindObjectInArrayByPropertyValue($recipePositionsResolved, 'recipe_pos_id', $selectedRecipePosition->id)->need_fulfilled == 1) {{ $__t('Enough in stock') }} @else {{ $__t('Not enough in stock (not included in costs), %1$s missing, %2$s already on shopping list', round($selectedRecipePosition->missing_amount, 2), round($selectedRecipePosition->amount_on_shopping_list, 2)) }} @endif</span>
|
||||
<span class="d-print-none">
|
||||
@if($selectedRecipePosition->need_fulfilled == 1)<i class="fas fa-check text-success"></i>@elseif($selectedRecipePosition->need_fulfilled_with_shopping_list == 1)<i class="fas fa-exclamation text-warning"></i>@else<i class="fas fa-times text-danger"></i>@endif
|
||||
<span class="timeago-contextual">@if(FindObjectInArrayByPropertyValue($recipePositionsResolved, 'recipe_pos_id', $selectedRecipePosition->id)->need_fulfilled == 1) {{ $__t('Enough in stock') }} @else {{ $__t('Not enough in stock (not included in costs), %1$s missing, %2$s already on shopping list', round($selectedRecipePosition->missing_amount, 2), round($selectedRecipePosition->amount_on_shopping_list, 2)) }} @endif</span>
|
||||
</span>
|
||||
@endif
|
||||
@if($selectedRecipePosition->need_fulfilled == 1 && GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) <span class="float-right font-italic ml-2 locale-number locale-number-currency">{{ $selectedRecipePosition->costs }}</span> @endif
|
||||
<span class="float-right font-italic"><span class="locale-number locale-number-quantity-amount">{{ $selectedRecipePosition->calories }} {{ $__t('Calories') }}</span></span>
|
||||
|
|
|
|||
|
|
@ -339,7 +339,7 @@
|
|||
<span class="d-inline print-timestamp"></span>
|
||||
</h6>
|
||||
</div>
|
||||
<div class="w-75 print-layout-type-table d-none">
|
||||
<div class="w-75 print-layout-container print-layout-type-table d-none">
|
||||
<div>
|
||||
<table id="shopping-list-print-shadow-table"
|
||||
class="table table-sm table-striped nowrap">
|
||||
|
|
@ -386,7 +386,7 @@
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-75 print-layout-type-list d-none">
|
||||
<div class="w-75 print-layout-container print-layout-type-list d-none">
|
||||
@foreach($listItems as $listItem)
|
||||
<div class="py-0">
|
||||
<span class="locale-number locale-number-quantity-amount">{{ $listItem->amount }}</span> @if(!empty($listItem->product_id)){{ $__n($listItem->amount, $listItem->qu_name, $listItem->qu_name_plural) }}@endif
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user