mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 01:32:38 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
94e2ec5e15
9
app.php
9
app.php
@ -40,6 +40,15 @@ require_once __DIR__ . '/vendor/autoload.php';
|
||||
require_once GROCY_DATAPATH . '/config.php';
|
||||
require_once __DIR__ . '/config-dist.php'; //For not in own config defined values we use the default ones
|
||||
|
||||
// Definitions for disabled authentication mode
|
||||
if (GROCY_DISABLE_AUTH === true)
|
||||
{
|
||||
if (!defined('GROCY_USER_ID'))
|
||||
{
|
||||
define('GROCY_USER_ID', 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup base application
|
||||
$appContainer = new \Slim\Container([
|
||||
'settings' => [
|
||||
|
16
changelog/50_2.4.3_2019-07-06.md
Normal file
16
changelog/50_2.4.3_2019-07-06.md
Normal file
@ -0,0 +1,16 @@
|
||||
- Fixed the messed up message/toast after consuming a product from the stock overview page
|
||||
- Fixed that "Track date only" chores were always tracked today, regardless of the given date
|
||||
- Fixed that the "week costs" were wrong after removing a meal plan entry
|
||||
- Fixed wrong recipes costs calculation with nested recipes when the base recipe servings are > 1 (also affected the meal plan when adding such a recipe there)
|
||||
- Fixed consuming recipes did not consume ingredients of the nested recipes
|
||||
- Improved recipes API - added new endpoints to get stock fulfillment information (thanks @Aerex)
|
||||
- Improved date display for products that never expires (instead of "2999-12-31" now just "Never" will be shown)
|
||||
- Improved date display for dates of today and no time (instead of the hours since midnight now just "Today" will be shown)
|
||||
- Improved shopping list handling
|
||||
- Items can now be switched between lists (there is a shopping list dropdown on the item edit page)
|
||||
- Items can now be marked as "done" (new check mark button per item, when clicked, the item will be displayed greyed out, when clicked again the item will be displayed normally again)
|
||||
- Improved that products can now also be consumed as spoiled from the stock overview page (option in the more/context menu per line)
|
||||
- Added a "consume this recipe"-button to the meal plan (and also a button to consume all recipes for a whole week)
|
||||
- Added the possibility to undo a task (new button per task, only visible when task is already completed) and also a corresponding API endpoint
|
||||
- Added a new `config.php` setting `DISABLE_AUTH` to be able to disable authentication / the login screen, defaults to `false`
|
||||
- Added a new `config.php` setting `CALENDAR_FIRST_DAY_OF_WEEK` to be able to change the first day of a week used for calendar views (meal plan for example) in the frontend, defaults to locale default
|
6
changelog/51_2.4.4_2019-07-07.md
Normal file
6
changelog/51_2.4.4_2019-07-07.md
Normal file
@ -0,0 +1,6 @@
|
||||
- Fixed that price data (last price & chart) was not taken from inventory correction bookings, only purchases
|
||||
- Fixed weekly chores were scheduled on the same day after execution
|
||||
- Fixed that undone chores were also included in "Last tracked"
|
||||
- Fixed the date-time-picker width was too narrow sometimes
|
||||
- Improved that execution dates of "Track date only" chores will never display the time part
|
||||
- Improved date display for products that never expire (again, there was a display problem after consuming an item on the stock overview page)
|
@ -21,6 +21,11 @@ Setting('MODE', 'production');
|
||||
# one of the other available localization files in the "/localization" directory
|
||||
Setting('CULTURE', 'en');
|
||||
|
||||
# This is used to define the first day of a week for calendar views in the frontend,
|
||||
# leave empty to use the locale default
|
||||
# Needs to be a number where Sunday = 0, Monday = 1 and so forth
|
||||
Setting('CALENDAR_FIRST_DAY_OF_WEEK', '');
|
||||
|
||||
# To keep it simple: grocy does not handle any currency conversions,
|
||||
# this here is used to format all money values,
|
||||
# so doesn't matter really matter, but should be the
|
||||
@ -45,6 +50,10 @@ Setting('DISABLE_URL_REWRITING', false);
|
||||
# You can set this to any overview you want. Example: Use recipes to set the homepage to the recipes overview.
|
||||
Setting('ENTRY_PAGE', 'stock');
|
||||
|
||||
# Set this to true if you want to disable authentication / the login screen,
|
||||
# places where user context is needed will then use the default (first existing) user
|
||||
Setting('DISABLE_AUTH', false);
|
||||
|
||||
# Default user settings
|
||||
# These settings can be changed per user, here the defaults
|
||||
# are defined which are used when the user has not changed the setting so far
|
||||
|
@ -21,7 +21,7 @@ class ChoresApiController extends BaseApiController
|
||||
try
|
||||
{
|
||||
$trackedTime = date('Y-m-d H:i:s');
|
||||
if (array_key_exists('tracked_time', $requestBody) && IsIsoDateTime($requestBody['tracked_time']))
|
||||
if (array_key_exists('tracked_time', $requestBody) && (IsIsoDateTime($requestBody['tracked_time']) || IsIsoDate($requestBody['tracked_time'])))
|
||||
{
|
||||
$trackedTime = $requestBody['tracked_time'];
|
||||
}
|
||||
|
@ -1,43 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use \Grocy\Services\RecipesService;
|
||||
|
||||
class RecipesApiController extends BaseApiController
|
||||
{
|
||||
public function __construct(\Slim\Container $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->RecipesService = new RecipesService();
|
||||
}
|
||||
|
||||
protected $RecipesService;
|
||||
|
||||
public function AddNotFulfilledProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
$excludedProductIds = null;
|
||||
|
||||
if ($requestBody !== null && array_key_exists('excludedProductIds', $requestBody))
|
||||
{
|
||||
$excludedProductIds = $requestBody['excludedProductIds'];
|
||||
}
|
||||
|
||||
$this->RecipesService->AddNotFulfilledProductsToShoppingList($args['recipeId'], $excludedProductIds);
|
||||
return $this->EmptyApiResponse($response);
|
||||
}
|
||||
|
||||
public function ConsumeRecipe(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->RecipesService->ConsumeRecipe($args['recipeId']);
|
||||
return $this->EmptyApiResponse($response);
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
return $this->GenericErrorResponse($response, $ex->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use \Grocy\Services\RecipesService;
|
||||
|
||||
class RecipesApiController extends BaseApiController
|
||||
{
|
||||
public function __construct(\Slim\Container $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->RecipesService = new RecipesService();
|
||||
}
|
||||
|
||||
protected $RecipesService;
|
||||
|
||||
public function AddNotFulfilledProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
$excludedProductIds = null;
|
||||
|
||||
if ($requestBody !== null && array_key_exists('excludedProductIds', $requestBody))
|
||||
{
|
||||
$excludedProductIds = $requestBody['excludedProductIds'];
|
||||
}
|
||||
|
||||
$this->RecipesService->AddNotFulfilledProductsToShoppingList($args['recipeId'], $excludedProductIds);
|
||||
return $this->EmptyApiResponse($response);
|
||||
}
|
||||
|
||||
public function ConsumeRecipe(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->RecipesService->ConsumeRecipe($args['recipeId']);
|
||||
return $this->EmptyApiResponse($response);
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
return $this->GenericErrorResponse($response, $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function GetRecipeFulfillment(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(!isset($args['recipeId']))
|
||||
{
|
||||
return $this->ApiResponse($this->RecipesService->GetRecipesResolved());
|
||||
}
|
||||
|
||||
$recipeResolved = FindObjectInArrayByPropertyValue($this->RecipesService->GetRecipesResolved(), 'recipe_id', $args['recipeId']);
|
||||
if(!$recipeResolved)
|
||||
{
|
||||
throw new \Exception('Recipe does not exist');
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->ApiResponse($recipeResolved);
|
||||
}
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
return $this->GenericErrorResponse($response, $ex->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -222,6 +222,7 @@ class StockController extends BaseController
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'shoppinglistitemform', [
|
||||
'products' => $this->Database->products()->orderBy('name'),
|
||||
'shoppingLists' => $this->Database->shopping_lists()->orderBy('name'),
|
||||
'mode' => 'create'
|
||||
]);
|
||||
}
|
||||
@ -230,6 +231,7 @@ class StockController extends BaseController
|
||||
return $this->AppContainer->view->render($response, 'shoppinglistitemform', [
|
||||
'listItem' => $this->Database->shopping_list($args['itemId']),
|
||||
'products' => $this->Database->products()->orderBy('name'),
|
||||
'shoppingLists' => $this->Database->shopping_lists()->orderBy('name'),
|
||||
'mode' => 'edit'
|
||||
]);
|
||||
}
|
||||
|
@ -39,4 +39,17 @@ class TasksApiController extends BaseApiController
|
||||
return $this->GenericErrorResponse($response, $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function UndoTask(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->TasksService->UndoTask($args['taskId']);
|
||||
return $this->EmptyApiResponse($response);
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
return $this->GenericErrorResponse($response, $ex->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1663,6 +1663,47 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/recipes/{recipeId}/fulfillment": {
|
||||
"get": {
|
||||
"summary": "Get stock fulfillment information for the given recipe",
|
||||
"tags": [
|
||||
"Recipes"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "recipeId",
|
||||
"required": true,
|
||||
"description": "A valid recipe id",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A RecipeFulfillmentResponse object",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RecipeFulfillmentResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "The operation was not successful",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GenericErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/recipes/{recipeId}/consume": {
|
||||
"post": {
|
||||
"summary": "Consumes all products of the given recipe",
|
||||
@ -1687,6 +1728,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/recipes/fulfillment": {
|
||||
"get": {
|
||||
"summary": "Get stock fulfillment information for all recipe",
|
||||
"tags": [
|
||||
"Recipes"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "An array of RecipeFulfillmentResponse objects",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/RecipeFulfillmentResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "The operation was not successful",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GenericErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/chores": {
|
||||
"get": {
|
||||
"summary": "Returns all chores incl. the next estimated execution time per chore",
|
||||
@ -2077,6 +2151,40 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/tasks/{taskId}/undo": {
|
||||
"post": {
|
||||
"summary": "Marks the given task as not completed",
|
||||
"tags": [
|
||||
"Tasks"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "taskId",
|
||||
"required": true,
|
||||
"description": "A valid task id",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "The operation was successful"
|
||||
},
|
||||
"400": {
|
||||
"description": "The operation was not successful (possible errors are: Not existing task)",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GenericErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/calendar/ical": {
|
||||
"get": {
|
||||
"summary": "Returns the calendar in iCal format",
|
||||
@ -2375,6 +2483,34 @@
|
||||
"location_id": "4"
|
||||
}
|
||||
},
|
||||
"RecipeFulfillmentResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"recipe_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"need_fulfilled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"need_fulfilled_with_shopping_list": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"missing_products_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"costs": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"recipe_id": "1",
|
||||
"need_fulfilled": "0",
|
||||
"need_fulfilled_with_shopping_list": "0",
|
||||
"missing_products_count": "2",
|
||||
"costs": "17.74"
|
||||
}
|
||||
},
|
||||
"ProductDetailsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -670,7 +670,7 @@ msgstr ""
|
||||
|
||||
msgid "Removed all ingredients of recipe \"%s\" from stock"
|
||||
msgstr ""
|
||||
"Alle Zutaten, die vom Rezept \"%s\" benötigt werden, wurdem aus dem Bestand "
|
||||
"Alle Zutaten, die vom Rezept \"%s\" benötigt werden, wurden aus dem Bestand "
|
||||
"entfernt"
|
||||
|
||||
msgid "Consume all ingredients needed by this recipe"
|
||||
@ -1374,3 +1374,23 @@ msgid "Booking has subsequent dependent bookings, undo not possible"
|
||||
msgstr ""
|
||||
"Die Buchung hat nachfolgende abhängige Buchungen, rückgängig machen nicht "
|
||||
"möglich"
|
||||
|
||||
msgid "per serving"
|
||||
msgstr "pro Portion"
|
||||
|
||||
msgid "Never"
|
||||
msgstr "Nie"
|
||||
|
||||
msgid "Today"
|
||||
msgstr "Heute"
|
||||
|
||||
msgid "Consume %1$s of %2$s as spoiled"
|
||||
msgstr "Verbrauche %1$s %2$s als verdorben"
|
||||
|
||||
msgid "Not all ingredients of recipe \"%s\" are in stock, nothing removed"
|
||||
msgstr ""
|
||||
"Nicht alle Zutaten, die vom Rezept \"%s\" benötigt werden, sind vorrätig, es"
|
||||
" wurde nichts aus dem Bestand entfernt"
|
||||
|
||||
msgid "Undo task \"%s\""
|
||||
msgstr "Aufgabe \"%s\" rückgängig machen"
|
||||
|
@ -1308,3 +1308,21 @@ msgstr ""
|
||||
|
||||
msgid "Booking has subsequent dependent bookings, undo not possible"
|
||||
msgstr ""
|
||||
|
||||
msgid "per serving"
|
||||
msgstr ""
|
||||
|
||||
msgid "Never"
|
||||
msgstr ""
|
||||
|
||||
msgid "Today"
|
||||
msgstr ""
|
||||
|
||||
msgid "Consume %1$s of %2$s as spoiled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Not all ingredients of recipe \"%s\" are in stock, nothing removed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Undo task \"%s\""
|
||||
msgstr ""
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Translators:
|
||||
# Bernd Bestel <bernd@berrnd.de>, 2019
|
||||
# Fernando Sánchez <fernando.l.sanchez@gmail.com>, 2019
|
||||
# Ankue <ankue.spam@gmail.com>, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@ -8,7 +9,7 @@ msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
|
||||
"PO-Revision-Date: 2019-05-01 17:42+0000\n"
|
||||
"Last-Translator: Fernando Sánchez <fernando.l.sanchez@gmail.com>, 2019\n"
|
||||
"Last-Translator: Ankue <ankue.spam@gmail.com>, 2019\n"
|
||||
"Language-Team: Spanish (https://www.transifex.com/grocy/teams/93189/es/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -37,8 +38,8 @@ msgstr "Nevera"
|
||||
|
||||
msgid "Piece"
|
||||
msgid_plural "Pieces"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "Pieza"
|
||||
msgstr[1] "Piezas"
|
||||
|
||||
msgid "Pack"
|
||||
msgid_plural "Packs"
|
||||
@ -47,8 +48,8 @@ msgstr[1] ""
|
||||
|
||||
msgid "Glass"
|
||||
msgid_plural "Glasses"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "Vaso"
|
||||
msgstr[1] "Vasos"
|
||||
|
||||
msgid "Tin"
|
||||
msgid_plural "Tins"
|
||||
@ -57,8 +58,8 @@ msgstr[1] ""
|
||||
|
||||
msgid "Can"
|
||||
msgid_plural "Cans"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "Lata"
|
||||
msgstr[1] "Latas"
|
||||
|
||||
msgid "Bunch"
|
||||
msgid_plural "Bunches"
|
||||
@ -172,8 +173,8 @@ msgstr "Usuario de demostración"
|
||||
|
||||
msgid "Gram"
|
||||
msgid_plural "Grams"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "Gramo"
|
||||
msgstr[1] "Gramos"
|
||||
|
||||
msgid "Flour"
|
||||
msgstr "Harina"
|
||||
@ -281,4 +282,4 @@ msgid "Swedish"
|
||||
msgstr "Sueco"
|
||||
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
msgstr "Polaco"
|
||||
|
@ -5,6 +5,7 @@
|
||||
# Cedric Octave <transifex@octvcdrc.fr>, 2019
|
||||
# Hydreliox Hydreliox <hydreliox@gmail.com>, 2019
|
||||
# Matthieu K, 2019
|
||||
# Mathieu Fortin <mathieugfortin@gmail.com>, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@ -12,7 +13,7 @@ msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
|
||||
"PO-Revision-Date: 2019-05-01 17:42+0000\n"
|
||||
"Last-Translator: Matthieu K, 2019\n"
|
||||
"Last-Translator: Mathieu Fortin <mathieugfortin@gmail.com>, 2019\n"
|
||||
"Language-Team: French (https://www.transifex.com/grocy/teams/93189/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -1369,4 +1370,22 @@ msgid "Marked task %s as completed on %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Booking has subsequent dependent bookings, undo not possible"
|
||||
msgstr "La réservation a des dépendances, impossible de revenir en arrière"
|
||||
|
||||
msgid "per serving"
|
||||
msgstr ""
|
||||
|
||||
msgid "Never"
|
||||
msgstr ""
|
||||
|
||||
msgid "Today"
|
||||
msgstr ""
|
||||
|
||||
msgid "Consume %1$s of %2$s as spoiled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Not all ingredients of recipe \"%s\" are in stock, nothing removed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Undo task \"%s\""
|
||||
msgstr ""
|
||||
|
@ -1,5 +1,6 @@
|
||||
# Translators:
|
||||
# Bernd Bestel <bernd@berrnd.de>, 2019
|
||||
# Matteo Piotto <matteo.piotto@welaika.com>, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@ -7,7 +8,7 @@ msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
|
||||
"PO-Revision-Date: 2019-05-01 17:42+0000\n"
|
||||
"Last-Translator: Bernd Bestel <bernd@berrnd.de>, 2019\n"
|
||||
"Last-Translator: Matteo Piotto <matteo.piotto@welaika.com>, 2019\n"
|
||||
"Language-Team: Italian (https://www.transifex.com/grocy/teams/93189/it/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -23,10 +24,10 @@ msgid "dynamic-regular"
|
||||
msgstr "Regolatore dinamico"
|
||||
|
||||
msgid "daily"
|
||||
msgstr ""
|
||||
msgstr "Giornalmente"
|
||||
|
||||
msgid "weekly"
|
||||
msgstr ""
|
||||
msgstr "Settimanalmente"
|
||||
|
||||
msgid "monthly"
|
||||
msgstr ""
|
||||
msgstr "Mensilmente"
|
||||
|
@ -1,5 +1,6 @@
|
||||
# Translators:
|
||||
# Bernd Bestel <bernd@berrnd.de>, 2019
|
||||
# Giel Janssens <gieljnssns@me.com>, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@ -7,7 +8,7 @@ msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
|
||||
"PO-Revision-Date: 2019-05-01 17:42+0000\n"
|
||||
"Last-Translator: Bernd Bestel <bernd@berrnd.de>, 2019\n"
|
||||
"Last-Translator: Giel Janssens <gieljnssns@me.com>, 2019\n"
|
||||
"Language-Team: Dutch (https://www.transifex.com/grocy/teams/93189/nl/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -26,7 +27,7 @@ msgid "moment_locale"
|
||||
msgstr "nl"
|
||||
|
||||
msgid "datatables_localization"
|
||||
msgstr ""
|
||||
msgstr "nl-NL"
|
||||
|
||||
msgid "summernote_locale"
|
||||
msgstr "nl-NL"
|
||||
|
@ -1357,3 +1357,21 @@ msgstr ""
|
||||
|
||||
msgid "Booking has subsequent dependent bookings, undo not possible"
|
||||
msgstr ""
|
||||
|
||||
msgid "per serving"
|
||||
msgstr ""
|
||||
|
||||
msgid "Never"
|
||||
msgstr ""
|
||||
|
||||
msgid "Today"
|
||||
msgstr ""
|
||||
|
||||
msgid "Consume %1$s of %2$s as spoiled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Not all ingredients of recipe \"%s\" are in stock, nothing removed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Undo task \"%s\""
|
||||
msgstr ""
|
||||
|
@ -1387,3 +1387,27 @@ msgstr "Dni \"blisko terminu wykonania zadania\""
|
||||
|
||||
msgid "Products"
|
||||
msgstr "Produkty"
|
||||
|
||||
msgid "Marked task %s as completed on %s"
|
||||
msgstr "Oznaczono zadanie %s jako wykonane dnia %s "
|
||||
|
||||
msgid "Booking has subsequent dependent bookings, undo not possible"
|
||||
msgstr "Rezerwacja ma kolejne zależne rezerwacje, nie można jej cofnąć"
|
||||
|
||||
msgid "per serving"
|
||||
msgstr ""
|
||||
|
||||
msgid "Never"
|
||||
msgstr ""
|
||||
|
||||
msgid "Today"
|
||||
msgstr ""
|
||||
|
||||
msgid "Consume %1$s of %2$s as spoiled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Not all ingredients of recipe \"%s\" are in stock, nothing removed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Undo task \"%s\""
|
||||
msgstr ""
|
||||
|
@ -1391,3 +1391,27 @@ msgstr "Критерий для \"подходит срок задач\" в дн
|
||||
|
||||
msgid "Products"
|
||||
msgstr "Продукты"
|
||||
|
||||
msgid "Marked task %s as completed on %s"
|
||||
msgstr "Пометить задачу \"%s\" как выполненную %s"
|
||||
|
||||
msgid "Booking has subsequent dependent bookings, undo not possible"
|
||||
msgstr "У данного действия есть зависимые действия, отмена невозможна"
|
||||
|
||||
msgid "per serving"
|
||||
msgstr "на порцию"
|
||||
|
||||
msgid "Never"
|
||||
msgstr "Никогда"
|
||||
|
||||
msgid "Today"
|
||||
msgstr "Сегодня"
|
||||
|
||||
msgid "Consume %1$s of %2$s as spoiled"
|
||||
msgstr "Употребить %1$s %2$s как испорченное"
|
||||
|
||||
msgid "Not all ingredients of recipe \"%s\" are in stock, nothing removed"
|
||||
msgstr "Не все ингредиенты рецепта \"%s\" есть в запасе, ничего не изъято"
|
||||
|
||||
msgid "Undo task \"%s\""
|
||||
msgstr ""
|
||||
|
@ -1268,3 +1268,21 @@ msgstr ""
|
||||
|
||||
msgid "Booking has subsequent dependent bookings, undo not possible"
|
||||
msgstr ""
|
||||
|
||||
msgid "per serving"
|
||||
msgstr ""
|
||||
|
||||
msgid "Never"
|
||||
msgstr ""
|
||||
|
||||
msgid "Today"
|
||||
msgstr ""
|
||||
|
||||
msgid "Consume %1$s of %2$s as spoiled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Not all ingredients of recipe \"%s\" are in stock, nothing removed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Undo task \"%s\""
|
||||
msgstr ""
|
||||
|
@ -22,7 +22,7 @@ class ApiKeyAuthMiddleware extends BaseMiddleware
|
||||
$route = $request->getAttribute('route');
|
||||
$routeName = $route->getName();
|
||||
|
||||
if (GROCY_IS_DEMO_INSTALL || GROCY_IS_EMBEDDED_INSTALL)
|
||||
if (GROCY_IS_DEMO_INSTALL || GROCY_IS_EMBEDDED_INSTALL || GROCY_DISABLE_AUTH)
|
||||
{
|
||||
define('GROCY_AUTHENTICATED', true);
|
||||
$response = $next($request, $response);
|
||||
|
@ -25,7 +25,7 @@ class SessionAuthMiddleware extends BaseMiddleware
|
||||
{
|
||||
$response = $next($request, $response);
|
||||
}
|
||||
elseif (GROCY_IS_DEMO_INSTALL || GROCY_IS_EMBEDDED_INSTALL)
|
||||
elseif (GROCY_IS_DEMO_INSTALL || GROCY_IS_EMBEDDED_INSTALL || GROCY_DISABLE_AUTH)
|
||||
{
|
||||
$user = $sessionService->GetDefaultUser();
|
||||
define('GROCY_AUTHENTICATED', true);
|
||||
|
92
migrations/0073.sql
Normal file
92
migrations/0073.sql
Normal file
@ -0,0 +1,92 @@
|
||||
DROP TRIGGER create_internal_recipe;
|
||||
CREATE TRIGGER create_internal_recipe AFTER INSERT ON meal_plan
|
||||
BEGIN
|
||||
/* This contains practically the same logic as the trigger remove_internal_recipe */
|
||||
|
||||
-- Create a recipe per day
|
||||
DELETE FROM recipes
|
||||
WHERE name = NEW.day
|
||||
AND type = 'mealplan-day';
|
||||
|
||||
INSERT OR REPLACE INTO recipes
|
||||
(id, name, type)
|
||||
VALUES
|
||||
((SELECT MIN(id) - 1 FROM recipes), NEW.day, 'mealplan-day');
|
||||
|
||||
-- Create a recipe per week
|
||||
DELETE FROM recipes
|
||||
WHERE name = LTRIM(STRFTIME('%Y-%W', NEW.day), '0')
|
||||
AND type = 'mealplan-week';
|
||||
|
||||
INSERT INTO recipes
|
||||
(id, name, type)
|
||||
VALUES
|
||||
((SELECT MIN(id) - 1 FROM recipes), LTRIM(STRFTIME('%Y-%W', NEW.day), '0'), 'mealplan-week');
|
||||
|
||||
-- Delete all current nestings entries for the day and week recipe
|
||||
DELETE FROM recipes_nestings
|
||||
WHERE recipe_id IN (SELECT id FROM recipes WHERE name = NEW.day AND type = 'mealplan-day')
|
||||
OR recipe_id IN (SELECT id FROM recipes WHERE name = NEW.day AND type = 'mealplan-week');
|
||||
|
||||
-- Add all recipes for this day as included recipes in the day-recipe
|
||||
INSERT INTO recipes_nestings
|
||||
(recipe_id, includes_recipe_id, servings)
|
||||
SELECT (SELECT id FROM recipes WHERE name = NEW.day AND type = 'mealplan-day'), recipe_id, SUM(servings)
|
||||
FROM meal_plan
|
||||
WHERE day = NEW.day
|
||||
GROUP BY recipe_id;
|
||||
|
||||
-- Add all recipes for this week as included recipes in the week-recipe
|
||||
INSERT INTO recipes_nestings
|
||||
(recipe_id, includes_recipe_id, servings)
|
||||
SELECT (SELECT id FROM recipes WHERE name = LTRIM(STRFTIME('%Y-%W', NEW.day), '0') AND type = 'mealplan-week'), recipe_id, SUM(servings)
|
||||
FROM meal_plan
|
||||
WHERE STRFTIME('%Y-%W', day) = STRFTIME('%Y-%W', NEW.day)
|
||||
GROUP BY recipe_id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER remove_internal_recipe AFTER DELETE ON meal_plan
|
||||
BEGIN
|
||||
/* This contains practically the same logic as the trigger create_internal_recipe */
|
||||
|
||||
-- Create a recipe per day
|
||||
DELETE FROM recipes
|
||||
WHERE name = OLD.day
|
||||
AND type = 'mealplan-day';
|
||||
|
||||
INSERT OR REPLACE INTO recipes
|
||||
(id, name, type)
|
||||
VALUES
|
||||
((SELECT MIN(id) - 1 FROM recipes), OLD.day, 'mealplan-day');
|
||||
|
||||
-- Create a recipe per week
|
||||
DELETE FROM recipes
|
||||
WHERE name = LTRIM(STRFTIME('%Y-%W', OLD.day), '0')
|
||||
AND type = 'mealplan-week';
|
||||
|
||||
INSERT INTO recipes
|
||||
(id, name, type)
|
||||
VALUES
|
||||
((SELECT MIN(id) - 1 FROM recipes), LTRIM(STRFTIME('%Y-%W', OLD.day), '0'), 'mealplan-week');
|
||||
|
||||
-- Delete all current nestings entries for the day and week recipe
|
||||
DELETE FROM recipes_nestings
|
||||
WHERE recipe_id IN (SELECT id FROM recipes WHERE name = OLD.day AND type = 'mealplan-day')
|
||||
OR recipe_id IN (SELECT id FROM recipes WHERE name = OLD.day AND type = 'mealplan-week');
|
||||
|
||||
-- Add all recipes for this day as included recipes in the day-recipe
|
||||
INSERT INTO recipes_nestings
|
||||
(recipe_id, includes_recipe_id, servings)
|
||||
SELECT (SELECT id FROM recipes WHERE name = OLD.day AND type = 'mealplan-day'), recipe_id, SUM(servings)
|
||||
FROM meal_plan
|
||||
WHERE day = OLD.day
|
||||
GROUP BY recipe_id;
|
||||
|
||||
-- Add all recipes for this week as included recipes in the week-recipe
|
||||
INSERT INTO recipes_nestings
|
||||
(recipe_id, includes_recipe_id, servings)
|
||||
SELECT (SELECT id FROM recipes WHERE name = LTRIM(STRFTIME('%Y-%W', OLD.day), '0') AND type = 'mealplan-week'), recipe_id, SUM(servings)
|
||||
FROM meal_plan
|
||||
WHERE STRFTIME('%Y-%W', day) = STRFTIME('%Y-%W', OLD.day)
|
||||
GROUP BY recipe_id;
|
||||
END;
|
85
migrations/0074.sql
Normal file
85
migrations/0074.sql
Normal file
@ -0,0 +1,85 @@
|
||||
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 rnrr.base_servings*1.0 ELSE 1 END) AS recipe_amount,
|
||||
IFNULL(sc.amount, 0) AS stock_amount,
|
||||
CASE WHEN IFNULL(sc.amount, 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 rnrr.base_servings*1.0 ELSE 1 END) END THEN 1 ELSE 0 END AS need_fulfilled,
|
||||
CASE WHEN IFNULL(sc.amount, 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 rnrr.base_servings*1.0 ELSE 1 END) END < 0 THEN ABS(IFNULL(sc.amount, 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 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, 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 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 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 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 / p.qu_factor_purchase_to_stock) * pcp.last_price AS costs,
|
||||
CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN 0 ELSE 1 END AS is_nested_recipe_pos,
|
||||
rp.ingredient_group,
|
||||
rp.id, -- Just a dummy id column
|
||||
rnr.includes_recipe_id as child_recipe_id,
|
||||
rp.note,
|
||||
rp.variable_amount AS recipe_variable_amount
|
||||
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 (
|
||||
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_current_price pcp
|
||||
ON rp.product_id = pcp.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 rnrr.base_servings*1.0 ELSE 1 END) AS recipe_amount,
|
||||
IFNULL(sc.amount, 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 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 / p.qu_factor_purchase_to_stock) * pcp.last_price AS costs,
|
||||
CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN 0 ELSE 1 END AS is_nested_recipe_pos,
|
||||
rp.ingredient_group,
|
||||
rp.id, -- Just a dummy id column
|
||||
rnr.includes_recipe_id as child_recipe_id,
|
||||
rp.note,
|
||||
rp.variable_amount AS recipe_variable_amount
|
||||
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 (
|
||||
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_current_price pcp
|
||||
ON rp.product_id = pcp.product_id
|
||||
WHERE rp.not_check_stock_fulfillment = 1;
|
2
migrations/0075.sql
Normal file
2
migrations/0075.sql
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE shopping_list
|
||||
ADD done INT DEFAULT 0;
|
87
migrations/0076.sql
Normal file
87
migrations/0076.sql
Normal file
@ -0,0 +1,87 @@
|
||||
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 rnrr.base_servings*1.0 ELSE 1 END) AS recipe_amount,
|
||||
IFNULL(sc.amount, 0) AS stock_amount,
|
||||
CASE WHEN IFNULL(sc.amount, 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 rnrr.base_servings*1.0 ELSE 1 END) END THEN 1 ELSE 0 END AS need_fulfilled,
|
||||
CASE WHEN IFNULL(sc.amount, 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 rnrr.base_servings*1.0 ELSE 1 END) END < 0 THEN ABS(IFNULL(sc.amount, 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 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, 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 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 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 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 / p.qu_factor_purchase_to_stock) * pcp.last_price AS costs,
|
||||
CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN 0 ELSE 1 END AS is_nested_recipe_pos,
|
||||
rp.ingredient_group,
|
||||
rp.id, -- Just a dummy id column
|
||||
rnr.includes_recipe_id as child_recipe_id,
|
||||
rp.note,
|
||||
rp.variable_amount AS recipe_variable_amount,
|
||||
rp.only_check_single_unit_in_stock
|
||||
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 (
|
||||
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_current_price pcp
|
||||
ON rp.product_id = pcp.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 rnrr.base_servings*1.0 ELSE 1 END) AS recipe_amount,
|
||||
IFNULL(sc.amount, 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 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 / p.qu_factor_purchase_to_stock) * pcp.last_price AS costs,
|
||||
CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN 0 ELSE 1 END AS is_nested_recipe_pos,
|
||||
rp.ingredient_group,
|
||||
rp.id, -- Just a dummy id column
|
||||
rnr.includes_recipe_id as child_recipe_id,
|
||||
rp.note,
|
||||
rp.variable_amount AS recipe_variable_amount,
|
||||
rp.only_check_single_unit_in_stock
|
||||
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 (
|
||||
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_current_price pcp
|
||||
ON rp.product_id = pcp.product_id
|
||||
WHERE rp.not_check_stock_fulfillment = 1;
|
28
migrations/0077.sql
Normal file
28
migrations/0077.sql
Normal file
@ -0,0 +1,28 @@
|
||||
DROP VIEW chores_current;
|
||||
CREATE VIEW chores_current
|
||||
AS
|
||||
SELECT
|
||||
h.id AS chore_id,
|
||||
MAX(l.tracked_time) AS last_tracked_time,
|
||||
CASE h.period_type
|
||||
WHEN 'manually' THEN '2999-12-31 23:59:59'
|
||||
WHEN 'dynamic-regular' THEN DATETIME(MAX(l.tracked_time), '+' || CAST(h.period_days AS TEXT) || ' day')
|
||||
WHEN 'daily' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '+1 day')
|
||||
WHEN 'weekly' THEN
|
||||
CASE
|
||||
WHEN period_config LIKE '%sunday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '1 days', 'weekday 0')
|
||||
WHEN period_config LIKE '%monday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '1 days', 'weekday 1')
|
||||
WHEN period_config LIKE '%tuesday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '1 days', 'weekday 2')
|
||||
WHEN period_config LIKE '%wednesday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '1 days', 'weekday 3')
|
||||
WHEN period_config LIKE '%thursday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '1 days', 'weekday 4')
|
||||
WHEN period_config LIKE '%friday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '1 days', 'weekday 5')
|
||||
WHEN period_config LIKE '%saturday%' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '1 days', 'weekday 6')
|
||||
END
|
||||
WHEN 'monthly' THEN DATETIME(IFNULL(MAX(l.tracked_time), DATETIME('now', 'localtime')), '+1 month', 'start of month', '+' || CAST(h.period_days - 1 AS TEXT) || ' day')
|
||||
END AS next_estimated_execution_time,
|
||||
h.track_date_only
|
||||
FROM chores h
|
||||
LEFT JOIN chores_log l
|
||||
ON h.id = l.chore_id
|
||||
AND l.undone = 0
|
||||
GROUP BY h.id, h.period_days;
|
@ -270,7 +270,7 @@ html {
|
||||
}
|
||||
|
||||
/* Third party component customizations - Tempus Dominus */
|
||||
.date-only-datetimepicker .bootstrap-datetimepicker-widget.dropdown-menu {
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu {
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,13 @@ GetUriParam = function(key)
|
||||
}
|
||||
};
|
||||
|
||||
UpdateUriParam = function(key, value)
|
||||
{
|
||||
var queryParameters = new URLSearchParams(location.search);
|
||||
queryParameters.set(key, value);
|
||||
window.history.replaceState({ }, "", decodeURIComponent(`${location.pathname}?${queryParameters}`));
|
||||
};
|
||||
|
||||
IsTouchInputDevice = function()
|
||||
{
|
||||
if (("ontouchstart" in window) || window.DocumentTouch && document instanceof DocumentTouch)
|
||||
|
@ -1,101 +1,4 @@
|
||||
Grocy.Translator = new Translator(Grocy.GettextPo);
|
||||
__t = function(text, ...placeholderValues)
|
||||
{
|
||||
if (Grocy.Mode === "dev")
|
||||
{
|
||||
var text2 = text;
|
||||
Grocy.Api.Post('system/log-missing-localization', { "text": text2 });
|
||||
}
|
||||
|
||||
return Grocy.Translator.__(text, ...placeholderValues)
|
||||
}
|
||||
__n = function(number, singularForm, pluralForm)
|
||||
{
|
||||
if (Grocy.Mode === "dev")
|
||||
{
|
||||
var singularForm2 = singularForm;
|
||||
Grocy.Api.Post('system/log-missing-localization', { "text": singularForm2 });
|
||||
}
|
||||
|
||||
return Grocy.Translator.n__(singularForm, pluralForm, number, number)
|
||||
}
|
||||
|
||||
U = function(relativePath)
|
||||
{
|
||||
return Grocy.BaseUrl.replace(/\/$/, '') + relativePath;
|
||||
}
|
||||
|
||||
if (!Grocy.ActiveNav.isEmpty())
|
||||
{
|
||||
var menuItem = $('#sidebarResponsive').find("[data-nav-for-page='" + Grocy.ActiveNav + "']");
|
||||
menuItem.addClass('active-page');
|
||||
|
||||
var parentMenuSelector = menuItem.data("sub-menu-of");
|
||||
if (typeof parentMenuSelector !== "undefined")
|
||||
{
|
||||
$(parentMenuSelector).collapse("show");
|
||||
$(parentMenuSelector).prev(".nav-link-collapse").addClass("active-page");
|
||||
}
|
||||
}
|
||||
|
||||
var observer = new MutationObserver(function(mutations)
|
||||
{
|
||||
mutations.forEach(function(mutation)
|
||||
{
|
||||
if (mutation.attributeName === "class")
|
||||
{
|
||||
var attributeValue = $(mutation.target).prop(mutation.attributeName);
|
||||
if (attributeValue.contains("sidenav-toggled"))
|
||||
{
|
||||
window.localStorage.setItem("sidebar_state", "collapsed");
|
||||
}
|
||||
else
|
||||
{
|
||||
window.localStorage.setItem("sidebar_state", "expanded");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
observer.observe(document.body, {
|
||||
attributes: true
|
||||
});
|
||||
if (window.localStorage.getItem("sidebar_state") === "collapsed")
|
||||
{
|
||||
$("#sidenavToggler").click();
|
||||
}
|
||||
|
||||
$.timeago.settings.allowFuture = true;
|
||||
RefreshContextualTimeago = function()
|
||||
{
|
||||
$("time.timeago").each(function()
|
||||
{
|
||||
var element = $(this);
|
||||
var timestamp = element.attr("datetime");
|
||||
element.timeago("update", timestamp);
|
||||
});
|
||||
}
|
||||
RefreshContextualTimeago();
|
||||
|
||||
toastr.options = {
|
||||
toastClass: 'alert',
|
||||
closeButton: true,
|
||||
timeOut: 20000,
|
||||
extendedTimeOut: 5000
|
||||
};
|
||||
|
||||
window.FontAwesomeConfig = {
|
||||
searchPseudoElements: true
|
||||
}
|
||||
|
||||
// Don't show tooltips on touch input devices
|
||||
if (IsTouchInputDevice())
|
||||
{
|
||||
var css = document.createElement("style");
|
||||
css.innerHTML = ".tooltip { display: none; }";
|
||||
document.body.appendChild(css);
|
||||
}
|
||||
|
||||
Grocy.Api = { };
|
||||
Grocy.Api = { };
|
||||
Grocy.Api.Get = function(apiFunction, success, error)
|
||||
{
|
||||
var xhr = new XMLHttpRequest();
|
||||
@ -323,6 +226,124 @@ Grocy.Api.DeleteFile = function(fileName, group, success, error)
|
||||
xhr.send();
|
||||
};
|
||||
|
||||
Grocy.Translator = new Translator(Grocy.GettextPo);
|
||||
__t = function(text, ...placeholderValues)
|
||||
{
|
||||
if (Grocy.Mode === "dev")
|
||||
{
|
||||
var text2 = text;
|
||||
Grocy.Api.Post('system/log-missing-localization', { "text": text2 });
|
||||
}
|
||||
|
||||
return Grocy.Translator.__(text, ...placeholderValues)
|
||||
}
|
||||
__n = function(number, singularForm, pluralForm)
|
||||
{
|
||||
if (Grocy.Mode === "dev")
|
||||
{
|
||||
var singularForm2 = singularForm;
|
||||
Grocy.Api.Post('system/log-missing-localization', { "text": singularForm2 });
|
||||
}
|
||||
|
||||
return Grocy.Translator.n__(singularForm, pluralForm, number, number)
|
||||
}
|
||||
|
||||
U = function(relativePath)
|
||||
{
|
||||
return Grocy.BaseUrl.replace(/\/$/, '') + relativePath;
|
||||
}
|
||||
|
||||
if (!Grocy.ActiveNav.isEmpty())
|
||||
{
|
||||
var menuItem = $('#sidebarResponsive').find("[data-nav-for-page='" + Grocy.ActiveNav + "']");
|
||||
menuItem.addClass('active-page');
|
||||
|
||||
var parentMenuSelector = menuItem.data("sub-menu-of");
|
||||
if (typeof parentMenuSelector !== "undefined")
|
||||
{
|
||||
$(parentMenuSelector).collapse("show");
|
||||
$(parentMenuSelector).prev(".nav-link-collapse").addClass("active-page");
|
||||
}
|
||||
}
|
||||
|
||||
var observer = new MutationObserver(function(mutations)
|
||||
{
|
||||
mutations.forEach(function(mutation)
|
||||
{
|
||||
if (mutation.attributeName === "class")
|
||||
{
|
||||
var attributeValue = $(mutation.target).prop(mutation.attributeName);
|
||||
if (attributeValue.contains("sidenav-toggled"))
|
||||
{
|
||||
window.localStorage.setItem("sidebar_state", "collapsed");
|
||||
}
|
||||
else
|
||||
{
|
||||
window.localStorage.setItem("sidebar_state", "expanded");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
observer.observe(document.body, {
|
||||
attributes: true
|
||||
});
|
||||
if (window.localStorage.getItem("sidebar_state") === "collapsed")
|
||||
{
|
||||
$("#sidenavToggler").click();
|
||||
}
|
||||
|
||||
$.timeago.settings.allowFuture = true;
|
||||
RefreshContextualTimeago = function()
|
||||
{
|
||||
$("time.timeago").each(function()
|
||||
{
|
||||
var element = $(this);
|
||||
var timestamp = element.attr("datetime");
|
||||
|
||||
var isNever = timestamp && timestamp.substring(0, 10) == "2999-12-31";
|
||||
var isToday = timestamp && timestamp.length == 10 && timestamp.substring(0, 10) == moment().format("YYYY-MM-DD");
|
||||
var isDateWithoutTime = element.hasClass("timeago-date-only");
|
||||
|
||||
if (isNever)
|
||||
{
|
||||
element.prev().text(__t("Never"));
|
||||
}
|
||||
else if (isToday)
|
||||
{
|
||||
element.text(__t("Today"));
|
||||
}
|
||||
else
|
||||
{
|
||||
element.timeago("update", timestamp);
|
||||
}
|
||||
|
||||
if (isDateWithoutTime)
|
||||
{
|
||||
element.prev().text(element.prev().text().substring(0, 10));
|
||||
}
|
||||
});
|
||||
}
|
||||
RefreshContextualTimeago();
|
||||
|
||||
toastr.options = {
|
||||
toastClass: 'alert',
|
||||
closeButton: true,
|
||||
timeOut: 20000,
|
||||
extendedTimeOut: 5000
|
||||
};
|
||||
|
||||
window.FontAwesomeConfig = {
|
||||
searchPseudoElements: true
|
||||
}
|
||||
|
||||
// Don't show tooltips on touch input devices
|
||||
if (IsTouchInputDevice())
|
||||
{
|
||||
var css = document.createElement("style");
|
||||
css.innerHTML = ".tooltip { display: none; }";
|
||||
document.body.appendChild(css);
|
||||
}
|
||||
|
||||
Grocy.FrontendHelpers = { };
|
||||
Grocy.FrontendHelpers.ValidateForm = function(formId)
|
||||
{
|
||||
|
@ -1,4 +1,10 @@
|
||||
$("#calendar").fullCalendar({
|
||||
var firstDay = null;
|
||||
if (!Grocy.CalendarFirstDayOfWeek.isEmpty())
|
||||
{
|
||||
firstDay = parseInt(Grocy.CalendarFirstDayOfWeek);
|
||||
}
|
||||
|
||||
$("#calendar").fullCalendar({
|
||||
"themeSystem": "bootstrap4",
|
||||
"header": {
|
||||
"left": "month,basicWeek,listWeek",
|
||||
@ -6,6 +12,7 @@
|
||||
"right": "prev,next"
|
||||
},
|
||||
"weekNumbers": true,
|
||||
"firstDay": firstDay,
|
||||
"eventLimit": true,
|
||||
"eventSources": fullcalendarEventSources
|
||||
});
|
||||
|
@ -8,13 +8,14 @@ Grocy.Components.BatteryCard.Refresh = function(batteryId)
|
||||
$('#batterycard-battery-name').text(batteryDetails.battery.name);
|
||||
$('#batterycard-battery-used_in').text(batteryDetails.battery.used_in);
|
||||
$('#batterycard-battery-last-charged').text((batteryDetails.last_charged || __t('never')));
|
||||
$('#batterycard-battery-last-charged-timeago').text($.timeago(batteryDetails.last_charged || ''));
|
||||
$('#batterycard-battery-last-charged-timeago').attr("datetime", batteryDetails.last_charged || '');
|
||||
$('#batterycard-battery-charge-cycles-count').text((batteryDetails.charge_cycles_count || '0'));
|
||||
|
||||
$('#batterycard-battery-edit-button').attr("href", U("/battery/" + batteryDetails.battery.id.toString()));
|
||||
$('#batterycard-battery-edit-button').removeClass("disabled");
|
||||
|
||||
EmptyElementWhenMatches('#batterycard-battery-last-charged-timeago', __t('timeago_nan'));
|
||||
RefreshContextualTimeago();
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
|
@ -7,7 +7,7 @@ Grocy.Components.ChoreCard.Refresh = function(choreId)
|
||||
{
|
||||
$('#chorecard-chore-name').text(choreDetails.chore.name);
|
||||
$('#chorecard-chore-last-tracked').text((choreDetails.last_tracked || __t('never')));
|
||||
$('#chorecard-chore-last-tracked-timeago').text($.timeago(choreDetails.last_tracked || ''));
|
||||
$('#chorecard-chore-last-tracked-timeago').attr("datetime", choreDetails.last_tracked || '');
|
||||
$('#chorecard-chore-tracked-count').text((choreDetails.tracked_count || '0'));
|
||||
$('#chorecard-chore-last-done-by').text((choreDetails.last_done_by.display_name || __t('Unknown')));
|
||||
|
||||
@ -15,6 +15,7 @@ Grocy.Components.ChoreCard.Refresh = function(choreId)
|
||||
$('#chorecard-chore-edit-button').removeClass("disabled");
|
||||
|
||||
EmptyElementWhenMatches('#chorecard-chore-last-tracked-timeago', __t('timeago_nan'));
|
||||
RefreshContextualTimeago();
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
|
@ -48,6 +48,15 @@ Grocy.Components.DateTimePicker.ChangeFormat = function(format)
|
||||
$(".datetimepicker").datetimepicker("destroy");
|
||||
Grocy.Components.DateTimePicker.GetInputElement().data("format", format);
|
||||
Grocy.Components.DateTimePicker.Init();
|
||||
|
||||
if (format == "YYYY-MM-DD")
|
||||
{
|
||||
Grocy.Components.DateTimePicker.GetInputElement().addClass("date-only-datetimepicker");
|
||||
}
|
||||
else
|
||||
{
|
||||
Grocy.Components.DateTimePicker.GetInputElement().removeClass("date-only-datetimepicker");
|
||||
}
|
||||
}
|
||||
|
||||
var startDate = null;
|
||||
@ -226,8 +235,9 @@ Grocy.Components.DateTimePicker.GetInputElement().on('keyup', function(e)
|
||||
|
||||
Grocy.Components.DateTimePicker.GetInputElement().on('input', function(e)
|
||||
{
|
||||
$('#datetimepicker-timeago').text($.timeago(Grocy.Components.DateTimePicker.GetValue()));
|
||||
$('#datetimepicker-timeago').attr("datetime", Grocy.Components.DateTimePicker.GetValue());
|
||||
EmptyElementWhenMatches('#datetimepicker-timeago', __t('timeago_nan'));
|
||||
RefreshContextualTimeago();
|
||||
});
|
||||
|
||||
$('.datetimepicker').on('update.datetimepicker', function(e)
|
||||
|
@ -12,9 +12,9 @@ Grocy.Components.ProductCard.Refresh = function(productId)
|
||||
$('#productcard-product-stock-amount').text(stockAmount);
|
||||
$('#productcard-product-stock-qu-name').text(__n(stockAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural));
|
||||
$('#productcard-product-last-purchased').text((productDetails.last_purchased || __t('never')).substring(0, 10));
|
||||
$('#productcard-product-last-purchased-timeago').text($.timeago(productDetails.last_purchased || ''));
|
||||
$('#productcard-product-last-purchased-timeago').attr("datetime", productDetails.last_purchased || '');
|
||||
$('#productcard-product-last-used').text((productDetails.last_used || __t('never')).substring(0, 10));
|
||||
$('#productcard-product-last-used-timeago').text($.timeago(productDetails.last_used || ''));
|
||||
$('#productcard-product-last-used-timeago').attr("datetime", productDetails.last_used || '');
|
||||
$('#productcard-product-location').text(productDetails.location.name);
|
||||
$('#productcard-product-spoil-rate').text(parseFloat(productDetails.spoil_rate_percent).toLocaleString(undefined, { style: "percent" }));
|
||||
|
||||
@ -71,6 +71,7 @@ Grocy.Components.ProductCard.Refresh = function(productId)
|
||||
|
||||
EmptyElementWhenMatches('#productcard-product-last-purchased-timeago', __t('timeago_nan'));
|
||||
EmptyElementWhenMatches('#productcard-product-last-used-timeago', __t('timeago_nan'));
|
||||
RefreshContextualTimeago();
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
|
@ -1,4 +1,12 @@
|
||||
var calendar = $("#calendar").fullCalendar({
|
||||
var firstRender = true;
|
||||
|
||||
var firstDay = null;
|
||||
if (!Grocy.CalendarFirstDayOfWeek.isEmpty())
|
||||
{
|
||||
firstDay = parseInt(Grocy.CalendarFirstDayOfWeek);
|
||||
}
|
||||
|
||||
var calendar = $("#calendar").fullCalendar({
|
||||
"themeSystem": "bootstrap4",
|
||||
"header": {
|
||||
"left": "title",
|
||||
@ -9,8 +17,18 @@
|
||||
"eventLimit": true,
|
||||
"eventSources": fullcalendarEventSources,
|
||||
"defaultView": "basicWeek",
|
||||
"firstDay": firstDay,
|
||||
"viewRender": function(view)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
firstRender = false
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateUriParam("week", view.start.format("YYYY-MM-DD"));
|
||||
}
|
||||
|
||||
$(".fc-day-header").append('<a class="ml-1 btn btn-outline-dark btn-xs my-1 add-recipe-button" href="#"><i class="fas fa-plus"></i></a>');
|
||||
|
||||
var weekRecipeName = view.start.year().toString() + "-" + (view.start.week() - 1).toString();
|
||||
@ -18,6 +36,7 @@
|
||||
|
||||
var weekCosts = 0;
|
||||
var weekRecipeOrderMissingButtonHtml = "";
|
||||
var weekRecipeConsumeButtonHtml = "";
|
||||
if (weekRecipe !== null)
|
||||
{
|
||||
weekCosts = FindObjectInArrayByPropertyValue(recipesResolved, "recipe_id", weekRecipe.id).costs;
|
||||
@ -27,9 +46,15 @@
|
||||
{
|
||||
weekRecipeOrderMissingButtonDisabledClasses = "disabled";
|
||||
}
|
||||
var weekRecipeConsumeButtonDisabledClasses = "";
|
||||
if (FindObjectInArrayByPropertyValue(recipesResolved, "recipe_id", weekRecipe.id).need_fulfilled == 0)
|
||||
{
|
||||
weekRecipeConsumeButtonDisabledClasses = "disabled";
|
||||
}
|
||||
weekRecipeOrderMissingButtonHtml = '<a class="ml-1 btn btn-outline-primary btn-xs recipe-order-missing-button ' + weekRecipeOrderMissingButtonDisabledClasses + '" href="#" data-toggle="tooltip" title="' + __t("Put missing products on shopping list") + '" data-recipe-id="' + weekRecipe.id.toString() + '" data-recipe-name="' + weekRecipe.name + '" data-recipe-type="' + weekRecipe.type + '"><i class="fas fa-cart-plus"></i></a>'
|
||||
weekRecipeConsumeButtonHtml = '<a class="ml-1 btn btn-outline-success btn-xs recipe-consume-button ' + weekRecipeConsumeButtonDisabledClasses + '" href="#" data-toggle="tooltip" title="' + __t("Consume all ingredients needed by this recipe") + '" data-recipe-id="' + weekRecipe.id.toString() + '" data-recipe-name="' + weekRecipe.name + '" data-recipe-type="' + weekRecipe.type + '"><i class="fas fa-utensils"></i></a>'
|
||||
}
|
||||
$(".fc-header-toolbar .fc-center").html("<h4>" + __t("Week costs") + ': <span class="locale-number-format" data-format="currency">' + weekCosts.toString() + "</span> " + weekRecipeOrderMissingButtonHtml + "</h4>");
|
||||
$(".fc-header-toolbar .fc-center").html("<h4>" + __t("Week costs") + ': <span class="locale-number-format" data-format="currency">' + weekCosts.toString() + "</span> " + weekRecipeOrderMissingButtonHtml + weekRecipeConsumeButtonHtml + "</h4>");
|
||||
},
|
||||
"eventRender": function(event, element)
|
||||
{
|
||||
@ -49,6 +74,12 @@
|
||||
recipeOrderMissingButtonDisabledClasses = "disabled";
|
||||
}
|
||||
|
||||
var recipeConsumeButtonDisabledClasses = "";
|
||||
if (resolvedRecipe.need_fulfilled == 0)
|
||||
{
|
||||
recipeConsumeButtonDisabledClasses = "disabled";
|
||||
}
|
||||
|
||||
var fulfillmentInfoHtml = __t('Enough in stock');
|
||||
var fulfillmentIconHtml = '<i class="fas fa-check text-success"></i>';
|
||||
if (resolvedRecipe.need_fulfilled != 1)
|
||||
@ -62,10 +93,11 @@
|
||||
<h5>' + recipe.name + '<h5> \
|
||||
<h5 class="small">' + __n(mealPlanEntry.servings, "%s serving", "%s servings") + '</h5> \
|
||||
<h5 class="small timeago-contextual">' + fulfillmentIconHtml + " " + fulfillmentInfoHtml + '</h5> \
|
||||
<h5 class="small locale-number-format" data-format="currency">' + resolvedRecipe.costs + '<h5> \
|
||||
<h5 class="small"><span class="locale-number-format" data-format="currency">' + resolvedRecipe.costs + '</span> ' + __t('per serving') + '<h5> \
|
||||
<h5> \
|
||||
<a class="ml-1 btn btn-outline-danger btn-xs remove-recipe-button" href="#"><i class="fas fa-trash"></i></a> \
|
||||
<a class="ml-1 btn btn-outline-primary btn-xs recipe-order-missing-button ' + recipeOrderMissingButtonDisabledClasses + '" href="#" data-toggle="tooltip" title="' + __t("Put missing products on shopping list") + '" data-recipe-id="' + recipe.id.toString() + '" data-recipe-name="' + recipe.name + '" data-recipe-type="' + recipe.type + '"><i class="fas fa-cart-plus"></i></a> \
|
||||
<a class="ml-1 btn btn-outline-success btn-xs recipe-consume-button ' + recipeConsumeButtonDisabledClasses + '" href="#" data-toggle="tooltip" title="' + __t("Consume all ingredients needed by this recipe") + '" data-recipe-id="' + recipe.id.toString() + '" data-recipe-name="' + recipe.name + '" data-recipe-type="' + recipe.type + '"><i class="fas fa-utensils"></i></a> \
|
||||
</h5> \
|
||||
</div>');
|
||||
|
||||
@ -77,6 +109,11 @@
|
||||
"eventAfterAllRender": function(view)
|
||||
{
|
||||
RefreshLocaleNumberDisplay();
|
||||
|
||||
if (GetUriParam("week") !== undefined)
|
||||
{
|
||||
$("#calendar").fullCalendar("gotoDate", GetUriParam("week"));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -103,7 +140,7 @@ $(document).on("click", ".remove-recipe-button", function(e)
|
||||
Grocy.Api.Delete('objects/meal_plan/' + mealPlanEntry.id.toString(), { },
|
||||
function(result)
|
||||
{
|
||||
calendar.fullCalendar('removeEvents', [mealPlanEntry.id]);
|
||||
window.location.reload();
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
@ -214,3 +251,44 @@ $(document).on('click', '.recipe-order-missing-button', function(e)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.recipe-consume-button', function(e)
|
||||
{
|
||||
var objectName = $(e.currentTarget).attr('data-recipe-name');
|
||||
var objectId = $(e.currentTarget).attr('data-recipe-id');
|
||||
|
||||
bootbox.confirm({
|
||||
message: __t('Are you sure to consume all ingredients needed by recipe "%s" (ingredients marked with "check only if a single unit is in stock" will be ignored)?', objectName),
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: __t('Yes'),
|
||||
className: 'btn-success'
|
||||
},
|
||||
cancel: {
|
||||
label: __t('No'),
|
||||
className: 'btn-danger'
|
||||
}
|
||||
},
|
||||
callback: function(result)
|
||||
{
|
||||
if (result === true)
|
||||
{
|
||||
Grocy.FrontendHelpers.BeginUiBusy();
|
||||
|
||||
Grocy.Api.Post('recipes/' + objectId + '/consume', { },
|
||||
function(result)
|
||||
{
|
||||
Grocy.FrontendHelpers.EndUiBusy();
|
||||
toastr.success(__t('Removed all ingredients of recipe "%s" from stock', objectName));
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
toastr.warning(__t('Not all ingredients of recipe "%s" are in stock, nothing removed', objectName));
|
||||
Grocy.FrontendHelpers.EndUiBusy();
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -187,6 +187,7 @@ $("#selectedRecipeConsumeButton").on('click', function(e)
|
||||
function(xhr)
|
||||
{
|
||||
Grocy.FrontendHelpers.EndUiBusy();
|
||||
toastr.warning(__t('Not all ingredients of recipe "%s" are in stock, nothing removed', objectName));
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
|
@ -259,6 +259,46 @@ $(document).on('click', '#shopping-list-stock-add-workflow-skip-button', functio
|
||||
window.postMessage(WindowMessageBag("Ready"), Grocy.BaseUrl);
|
||||
});
|
||||
|
||||
$(document).on('click', '.order-listitem-button', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
Grocy.FrontendHelpers.BeginUiBusy();
|
||||
|
||||
var listItemId = $(e.currentTarget).attr('data-item-id');
|
||||
|
||||
var done = 1;
|
||||
if ($(e.currentTarget).attr('data-item-done') == 1)
|
||||
{
|
||||
done = 0;
|
||||
}
|
||||
|
||||
$(e.currentTarget).attr('data-item-done', done);
|
||||
|
||||
Grocy.Api.Put('objects/shopping_list/' + listItemId, { 'done': done },
|
||||
function()
|
||||
{
|
||||
if (done == 1)
|
||||
{
|
||||
$('#shoppinglistitem-' + listItemId + '-row').addClass("text-muted");
|
||||
$('#shoppinglistitem-' + listItemId + '-row').addClass("text-strike-through");
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#shoppinglistitem-' + listItemId + '-row').removeClass("text-muted");
|
||||
$('#shoppinglistitem-' + listItemId + '-row').removeClass("text-strike-through");
|
||||
}
|
||||
|
||||
Grocy.FrontendHelpers.EndUiBusy();
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
Grocy.FrontendHelpers.EndUiBusy();
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
function OnListItemRemoved()
|
||||
{
|
||||
if ($(".shopping-list-stock-add-workflow-list-item-button").length === 0)
|
||||
|
@ -3,7 +3,6 @@
|
||||
e.preventDefault();
|
||||
|
||||
var jsonData = $('#shoppinglist-form').serializeJSON();
|
||||
jsonData.shopping_list_id = GetUriParam("list");
|
||||
Grocy.FrontendHelpers.BeginUiBusy("shoppinglist-form");
|
||||
|
||||
if (Grocy.EditMode === 'create')
|
||||
@ -11,7 +10,7 @@
|
||||
Grocy.Api.Post('objects/shopping_list', jsonData,
|
||||
function(result)
|
||||
{
|
||||
window.location.href = U('/shoppinglist?list=' + GetUriParam("list"));
|
||||
window.location.href = U('/shoppinglist?list=' + $("#shopping_list_id").val().toString());
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
@ -25,7 +24,7 @@
|
||||
Grocy.Api.Put('objects/shopping_list/' + Grocy.EditObjectId, jsonData,
|
||||
function(result)
|
||||
{
|
||||
window.location.href = U('/shoppinglist?list=' + GetUriParam("list"));
|
||||
window.location.href = U('/shoppinglist?list=' + $("#shopping_list_id").val().toString());
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
@ -93,12 +92,12 @@ $('#amount').on('focus', function(e)
|
||||
}
|
||||
});
|
||||
|
||||
$('#shoppinglist-form input').keyup(function (event)
|
||||
$('#shoppinglist-form input').keyup(function(event)
|
||||
{
|
||||
Grocy.FrontendHelpers.ValidateForm('shoppinglist-form');
|
||||
});
|
||||
|
||||
$('#shoppinglist-form input').keydown(function (event)
|
||||
$('#shoppinglist-form input').keydown(function(event)
|
||||
{
|
||||
if (event.keyCode === 13) //Enter
|
||||
{
|
||||
@ -114,3 +113,8 @@ $('#shoppinglist-form input').keydown(function (event)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (GetUriParam("list") !== undefined)
|
||||
{
|
||||
$("#shopping_list_id").val(GetUriParam("list"));
|
||||
}
|
||||
|
@ -89,11 +89,10 @@ $(document).on('click', '.product-consume-button', function(e)
|
||||
Grocy.FrontendHelpers.BeginUiBusy();
|
||||
|
||||
var productId = $(e.currentTarget).attr('data-product-id');
|
||||
var productName = $(e.currentTarget).attr('data-product-name');
|
||||
var productQuName = $(e.currentTarget).attr('data-product-qu-name');
|
||||
var consumeAmount = $(e.currentTarget).attr('data-consume-amount');
|
||||
var wasSpoiled = $(e.currentTarget).hasClass("product-consume-button-spoiled");
|
||||
|
||||
Grocy.Api.Post('stock/products/' + productId + '/consume', { 'amount': consumeAmount },
|
||||
Grocy.Api.Post('stock/products/' + productId + '/consume', { 'amount': consumeAmount, 'spoiled': wasSpoiled },
|
||||
function()
|
||||
{
|
||||
Grocy.Api.Get('stock/products/' + productId,
|
||||
@ -127,8 +126,9 @@ $(document).on('click', '.product-consume-button', function(e)
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#product-' + productId + '-qu-name').text(__n(newAmount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural));
|
||||
$('#product-' + productId + '-amount').parent().effect('highlight', { }, 500);
|
||||
$('#product-' + productId + '-amount').fadeOut(500, function()
|
||||
$('#product-' + productId + '-amount').fadeOut(500, function ()
|
||||
{
|
||||
$(this).text(newAmount).fadeIn(500);
|
||||
});
|
||||
@ -156,10 +156,21 @@ $(document).on('click', '.product-consume-button', function(e)
|
||||
});
|
||||
}
|
||||
|
||||
var toastMessage = __t('Removed %1$s of %2$s from stock', consumeAmount.toString() + " " + __n(consumeAmount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural), result.product.name);
|
||||
if (wasSpoiled)
|
||||
{
|
||||
toastMessage += " (" + __t("Spoiled") + ")";
|
||||
}
|
||||
|
||||
Grocy.FrontendHelpers.EndUiBusy();
|
||||
toastr.success(__t('Removed %1$s of %2$s from stock', consumeAmount, productQuName, productName));
|
||||
RefreshContextualTimeago();
|
||||
toastr.success(toastMessage);
|
||||
RefreshStatistics();
|
||||
|
||||
// Needs to be delayed because of the animation above the date-text would be wrong if fired immediately...
|
||||
setTimeout(function ()
|
||||
{
|
||||
RefreshContextualTimeago();
|
||||
}, 520);
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
@ -214,14 +225,14 @@ $(document).on('click', '.product-open-button', function(e)
|
||||
}
|
||||
|
||||
$('#product-' + productId + '-next-best-before-date').parent().effect('highlight', {}, 500);
|
||||
$('#product-' + productId + '-next-best-before-date').fadeOut(500, function ()
|
||||
$('#product-' + productId + '-next-best-before-date').fadeOut(500, function()
|
||||
{
|
||||
$(this).text(result.next_best_before_date).fadeIn(500);
|
||||
});
|
||||
$('#product-' + productId + '-next-best-before-date-timeago').attr('datetime', result.next_best_before_date);
|
||||
|
||||
$('#product-' + productId + '-opened-amount').parent().effect('highlight', {}, 500);
|
||||
$('#product-' + productId + '-opened-amount').fadeOut(500, function ()
|
||||
$('#product-' + productId + '-opened-amount').fadeOut(500, function()
|
||||
{
|
||||
$(this).text(__t('%s opened', result.stock_amount_opened)).fadeIn(500);
|
||||
});
|
||||
@ -233,8 +244,13 @@ $(document).on('click', '.product-open-button', function(e)
|
||||
|
||||
Grocy.FrontendHelpers.EndUiBusy();
|
||||
toastr.success(__t('Marked %1$s of %2$s as opened', 1 + " " + productQuName, productName));
|
||||
RefreshContextualTimeago();
|
||||
RefreshStatistics();
|
||||
|
||||
// Needs to be delayed because of the animation above the date-text would be wrong if fired immediately...
|
||||
setTimeout(function()
|
||||
{
|
||||
RefreshContextualTimeago();
|
||||
}, 600);
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
|
@ -102,6 +102,32 @@ $(document).on('click', '.do-task-button', function(e)
|
||||
);
|
||||
});
|
||||
|
||||
$(document).on('click', '.undo-task-button', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
// Remove the focus from the current button
|
||||
// to prevent that the tooltip stays until clicked anywhere else
|
||||
document.activeElement.blur();
|
||||
|
||||
Grocy.FrontendHelpers.BeginUiBusy();
|
||||
|
||||
var taskId = $(e.currentTarget).attr('data-task-id');
|
||||
var taskName = $(e.currentTarget).attr('data-task-name');
|
||||
|
||||
Grocy.Api.Post('tasks/' + taskId + '/undo', { },
|
||||
function()
|
||||
{
|
||||
window.location.reload();
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
Grocy.FrontendHelpers.EndUiBusy();
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$(document).on('click', '.delete-task-button', function (e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
@ -173,7 +173,9 @@ $app->group('/api', function()
|
||||
if (GROCY_FEATURE_FLAG_RECIPES)
|
||||
{
|
||||
$this->post('/recipes/{recipeId}/add-not-fulfilled-products-to-shoppinglist', '\Grocy\Controllers\RecipesApiController:AddNotFulfilledProductsToShoppingList');
|
||||
$this->get('/recipes/{recipeId}/fulfillment', '\Grocy\Controllers\RecipesApiController:GetRecipeFulfillment');
|
||||
$this->post('/recipes/{recipeId}/consume', '\Grocy\Controllers\RecipesApiController:ConsumeRecipe');
|
||||
$this->get('/recipes/fulfillment', '\Grocy\Controllers\RecipesApiController:GetRecipeFulfillment');
|
||||
}
|
||||
|
||||
// Chores
|
||||
@ -199,6 +201,7 @@ $app->group('/api', function()
|
||||
{
|
||||
$this->get('/tasks', '\Grocy\Controllers\TasksApiController:Current');
|
||||
$this->post('/tasks/{taskId}/complete', '\Grocy\Controllers\TasksApiController:MarkTaskAsCompleted');
|
||||
$this->post('/tasks/{taskId}/undo', '\Grocy\Controllers\TasksApiController:UndoTask');
|
||||
}
|
||||
|
||||
// Calendar
|
||||
|
@ -67,12 +67,12 @@ class RecipesService extends BaseService
|
||||
throw new \Exception('Recipe does not exist');
|
||||
}
|
||||
|
||||
$recipePositions = $this->Database->recipes_pos()->where('recipe_id', $recipeId)->fetchAll();
|
||||
$recipePositions = $this->Database->recipes_pos_resolved()->where('recipe_id', $recipeId)->fetchAll();
|
||||
foreach ($recipePositions as $recipePosition)
|
||||
{
|
||||
if ($recipePosition->only_check_single_unit_in_stock == 0)
|
||||
{
|
||||
$this->StockService->ConsumeProduct($recipePosition->product_id, $recipePosition->amount, false, StockService::TRANSACTION_TYPE_CONSUME, 'default', $recipeId);
|
||||
$this->StockService->ConsumeProduct($recipePosition->product_id, $recipePosition->recipe_amount, false, StockService::TRANSACTION_TYPE_CONSUME, 'default', $recipeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ class StockService extends BaseService
|
||||
$averageShelfLifeDays = intval($this->Database->stock_average_product_shelf_life()->where('id', $productId)->fetch()->average_shelf_life_days);
|
||||
|
||||
$lastPrice = null;
|
||||
$lastLogRow = $this->Database->stock_log()->where('product_id = :1 AND transaction_type = :2 AND undone = 0', $productId, self::TRANSACTION_TYPE_PURCHASE)->orderBy('row_created_timestamp', 'DESC')->limit(1)->fetch();
|
||||
$lastLogRow = $this->Database->stock_log()->where('product_id = :1 AND transaction_type IN (:2, :3) AND undone = 0', $productId, self::TRANSACTION_TYPE_PURCHASE, self::TRANSACTION_TYPE_INVENTORY_CORRECTION)->orderBy('row_created_timestamp', 'DESC')->limit(1)->fetch();
|
||||
if ($lastLogRow !== null && !empty($lastLogRow))
|
||||
{
|
||||
$lastPrice = $lastLogRow->price;
|
||||
@ -120,7 +120,7 @@ class StockService extends BaseService
|
||||
}
|
||||
|
||||
$returnData = array();
|
||||
$rows = $this->Database->stock_log()->where('product_id = :1 AND transaction_type = :2 AND undone = 0', $productId, self::TRANSACTION_TYPE_PURCHASE)->whereNOT('price', null)->orderBy('purchased_date', 'DESC');
|
||||
$rows = $this->Database->stock_log()->where('product_id = :1 AND transaction_type IN (:2, :3) AND undone = 0', $productId, self::TRANSACTION_TYPE_PURCHASE, self::TRANSACTION_TYPE_INVENTORY_CORRECTION)->whereNOT('price', null)->orderBy('purchased_date', 'DESC');
|
||||
foreach ($rows as $row)
|
||||
{
|
||||
$returnData[] = array(
|
||||
|
@ -26,6 +26,22 @@ class TasksService extends BaseService
|
||||
return true;
|
||||
}
|
||||
|
||||
public function UndoTask($taskId)
|
||||
{
|
||||
if (!$this->TaskExists($taskId))
|
||||
{
|
||||
throw new \Exception('Task does not exist');
|
||||
}
|
||||
|
||||
$taskRow = $this->Database->tasks()->where('id = :1', $taskId)->fetch();
|
||||
$taskRow->update(array(
|
||||
'done' => 0,
|
||||
'done_timestamp' => null
|
||||
));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function TaskExists($taskId)
|
||||
{
|
||||
$taskRow = $this->Database->tasks()->where('id = :1', $taskId)->fetch();
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"Version": "2.4.2",
|
||||
"ReleaseDate": "2019-06-09"
|
||||
"Version": "2.4.4",
|
||||
"ReleaseDate": "2019-07-07"
|
||||
}
|
||||
|
@ -55,8 +55,8 @@
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
{{ $choreLogEntry->tracked_time }}
|
||||
<time class="timeago timeago-contextual" datetime="{{ $choreLogEntry->tracked_time }}"></time>
|
||||
<span>{{ $choreLogEntry->tracked_time }}</span>
|
||||
<time class="timeago timeago-contextual @if(FindObjectInArrayByPropertyValue($chores, 'id', $choreLogEntry->chore_id)->track_date_only == 1) timeago-date-only @endif" datetime="{{ $choreLogEntry->tracked_time }}"></time>
|
||||
</td>
|
||||
<td>
|
||||
@if ($choreLogEntry->done_by_user_id !== null && !empty($choreLogEntry->done_by_user_id))
|
||||
|
@ -85,14 +85,14 @@
|
||||
<td>
|
||||
@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_TYPE_MANUALLY)
|
||||
<span id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-time">{{ $curentChoreEntry->next_estimated_execution_time }}</span>
|
||||
<time id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-time-timeago" class="timeago timeago-contextual" datetime="{{ $curentChoreEntry->next_estimated_execution_time }}"></time>
|
||||
<time id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-time-timeago" class="timeago timeago-contextual @if($curentChoreEntry->track_date_only == 1) timeago-date-only @endif" datetime="{{ $curentChoreEntry->next_estimated_execution_time }}"></time>
|
||||
@else
|
||||
...
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<span id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time">{{ $curentChoreEntry->last_tracked_time }}</span>
|
||||
<time id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time-timeago" class="timeago timeago-contextual" datetime="{{ $curentChoreEntry->last_tracked_time }}"></time>
|
||||
<time id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time-timeago" class="timeago timeago-contextual @if($curentChoreEntry->track_date_only == 1) timeago-date-only @endif" datetime="{{ $curentChoreEntry->last_tracked_time }}"></time>
|
||||
</td>
|
||||
<td class="d-none">
|
||||
@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s')) overdue @elseif(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) duesoon @endif
|
||||
|
@ -59,7 +59,7 @@
|
||||
|
||||
@include('components.locationpicker', array(
|
||||
'locations' => $locations,
|
||||
'hint' => 'This will apply to added products'
|
||||
'hint' => $__t('This will apply to added products')
|
||||
))
|
||||
|
||||
<button id="save-inventory-button" class="btn btn-success">{{ $__t('OK') }}</button>
|
||||
|
@ -52,6 +52,7 @@
|
||||
Grocy.ActiveNav = '@yield('activeNav', '')';
|
||||
Grocy.Culture = '{{ GROCY_CULTURE }}';
|
||||
Grocy.Currency = '{{ GROCY_CURRENCY }}';
|
||||
Grocy.CalendarFirstDayOfWeek = '{{ GROCY_CALENDAR_FIRST_DAY_OF_WEEK }}';
|
||||
Grocy.GettextPo = {!! $GettextPo !!};
|
||||
Grocy.UserSettings = {!! json_encode($userSettings) !!};
|
||||
Grocy.FeatureFlags = {!! json_encode($featureFlags) !!};
|
||||
|
@ -121,10 +121,10 @@
|
||||
<div id="selectedRecipeCard" class="card">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-cocktail"></i> {{ $selectedRecipe->name }}
|
||||
<a id="selectedRecipeConsumeButton" class="btn btn-sm btn-outline-success py-0" href="#" data-toggle="tooltip" title="{{ $__t('Consume all ingredients needed by this recipe') }}" data-recipe-id="{{ $selectedRecipe->id }}" data-recipe-name="{{ $selectedRecipe->name }}">
|
||||
<a id="selectedRecipeConsumeButton" class="btn btn-sm btn-outline-success py-0 @if(FindObjectInArrayByPropertyValue($recipesResolved, 'recipe_id', $selectedRecipe->id)->need_fulfilled == 0) disabled @endif" href="#" data-toggle="tooltip" title="{{ $__t('Consume all ingredients needed by this recipe') }}" data-recipe-id="{{ $selectedRecipe->id }}" data-recipe-name="{{ $selectedRecipe->name }}">
|
||||
<i class="fas fa-utensils"></i>
|
||||
</a>
|
||||
<a class="btn btn-sm btn-outline-primary py-0 recipe-order-missing-button @if(FindObjectInArrayByPropertyValue($recipesResolved, 'recipe_id', $selectedRecipe->id)->need_fulfilled_with_shopping_list == 1){{ disabled }}@endif" href="#" data-toggle="tooltip" title="{{ $__t('Put missing products on shopping list') }}" data-recipe-id="{{ $selectedRecipe->id }}" data-recipe-name="{{ $selectedRecipe->name }}">
|
||||
<a class="btn btn-sm btn-outline-primary py-0 recipe-order-missing-button @if(FindObjectInArrayByPropertyValue($recipesResolved, 'recipe_id', $selectedRecipe->id)->need_fulfilled_with_shopping_list == 1) disabled @endif" href="#" data-toggle="tooltip" title="{{ $__t('Put missing products on shopping list') }}" data-recipe-id="{{ $selectedRecipe->id }}" data-recipe-name="{{ $selectedRecipe->name }}">
|
||||
<i class="fas fa-cart-plus"></i>
|
||||
</a>
|
||||
<a id="selectedRecipeEditButton" class="btn btn-sm btn-outline-info py-0" href="{{ $U('/recipe/') }}{{ $selectedRecipe->id }}">
|
||||
|
@ -85,8 +85,13 @@
|
||||
</thead>
|
||||
<tbody class="d-none">
|
||||
@foreach($listItems as $listItem)
|
||||
<tr id="shoppinglistitem-{{ $listItem->id }}-row" class="@if(FindObjectInArrayByPropertyValue($missingProducts, 'id', $listItem->product_id) !== null) table-info @endif">
|
||||
<tr id="shoppinglistitem-{{ $listItem->id }}-row" class="@if(FindObjectInArrayByPropertyValue($missingProducts, 'id', $listItem->product_id) !== null) table-info @endif @if($listItem->done == 1) text-muted text-strike-through @endif">
|
||||
<td class="fit-content border-right">
|
||||
<a class="btn btn-success btn-sm order-listitem-button" href="#"
|
||||
data-item-id="{{ $listItem->id }}"
|
||||
data-item-done="{{ $listItem->done }}">
|
||||
<i class="fas fa-check"></i>
|
||||
</a>
|
||||
<a class="btn btn-sm btn-info" href="{{ $U('/shoppinglistitem/') . $listItem->id . '?list=' . $selectedShoppingListId }}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
|
@ -21,6 +21,15 @@
|
||||
|
||||
<form id="shoppinglist-form" novalidate>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="product_group_id">{{ $__t('Shopping list') }}</label>
|
||||
<select class="form-control" id="shopping_list_id" name="shopping_list_id">
|
||||
@foreach($shoppingLists as $shoppingList)
|
||||
<option @if($mode == 'edit' && $shoppingList->id == $listItem->shopping_list_id) selected="selected" @endif value="{{ $shoppingList->id }}">{{ $shoppingList->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@php if($mode == 'edit') { $productId = $listItem->product_id; } else { $productId = ''; } @endphp
|
||||
@include('components.productpicker', array(
|
||||
'products' => $products,
|
||||
|
@ -122,6 +122,14 @@
|
||||
<a class="dropdown-item" type="button" href="{{ $U('/product/') }}{{ $currentStockEntry->product_id }}">
|
||||
<i class="fas fa-edit"></i> {{ $__t('Edit product') }}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item product-consume-button product-consume-button-spoiled @if($currentStockEntry->amount < 1) disabled @endif" type="button" href="#"
|
||||
data-product-id="{{ $currentStockEntry->product_id }}"
|
||||
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}"
|
||||
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name }}"
|
||||
data-consume-amount="1">
|
||||
<i class="fas fa-utensils"></i> {{ $__t('Consume %1$s of %2$s as spoiled', '1 ' . FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name) }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@ -129,7 +137,7 @@
|
||||
{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}
|
||||
</td>
|
||||
<td>
|
||||
<span id="product-{{ $currentStockEntry->product_id }}-amount">{{ $currentStockEntry->amount }}</span> {{ $__n($currentStockEntry->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name_plural) }}
|
||||
<span id="product-{{ $currentStockEntry->product_id }}-amount">{{ $currentStockEntry->amount }}</span> <span id="product-{{ $currentStockEntry->product_id }}-qu-name">{{ $__n($currentStockEntry->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name_plural) }}</span>
|
||||
<span id="product-{{ $currentStockEntry->product_id }}-opened-amount" class="small font-italic">@if($currentStockEntry->amount_opened > 0){{ $__t('%s opened', $currentStockEntry->amount_opened) }}@endif</span>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -73,11 +73,19 @@
|
||||
@foreach($tasks as $task)
|
||||
<tr id="task-{{ $task->id }}-row" class="@if($task->done == 1) text-muted @endif @if(!empty($task->due_date) && $task->due_date < date('Y-m-d')) table-danger @elseif(!empty($task->due_date) && $task->due_date < date('Y-m-d', strtotime("+$nextXDays days"))) table-warning @endif">
|
||||
<td class="fit-content border-right">
|
||||
<a class="btn btn-success btn-sm do-task-button @if($task->done == 1) disabled @endif" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Mark task "%s" as completed', $task->name) }}"
|
||||
@if($task->done == 0)
|
||||
<a class="btn btn-success btn-sm do-task-button" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Mark task "%s" as completed', $task->name) }}"
|
||||
data-task-id="{{ $task->id }}"
|
||||
data-task-name="{{ $task->name }}">
|
||||
<i class="fas fa-check"></i>
|
||||
</a>
|
||||
@else
|
||||
<a class="btn btn-secondary btn-sm undo-task-button" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Undo task "%s"', $task->name) }}"
|
||||
data-task-id="{{ $task->id }}"
|
||||
data-task-name="{{ $task->name }}">
|
||||
<i class="fas fa-undo"></i>
|
||||
</a>
|
||||
@endif
|
||||
<a class="btn btn-sm btn-danger delete-task-button" href="#"
|
||||
data-task-id="{{ $task->id }}"
|
||||
data-task-name="{{ $task->name }}">
|
||||
|
Loading…
x
Reference in New Issue
Block a user