Merge remote-tracking branch 'origin' into grocycode

This commit is contained in:
Katharina Bogad 2021-06-11 18:54:19 +02:00
commit 18ad5a8f12
24 changed files with 280 additions and 76 deletions

View File

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

View 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

View File

@ -25,5 +25,8 @@
"files": [
"helpers/extensions.php"
]
},
"config": {
"platform-check": false
}
}

View File

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

View File

@ -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);
}

View File

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

View File

@ -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");

View File

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

View File

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

View File

@ -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');

View File

@ -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');
});

View File

@ -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();

View File

@ -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();

View File

@ -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');

View File

@ -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 })));
}

View File

@ -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');
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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>&nbsp;
<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') }}&nbsp;</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') }}&nbsp;
<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>

View File

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