Compare commits

..

35 Commits

Author SHA1 Message Date
Bernd Bestel
001d5c5d1d Prepared next release 2019-07-06 20:43:30 +02:00
Bernd Bestel
b4d2e2a20a Added the possibility to undo a task (closes #252) 2019-07-06 20:34:01 +02:00
Bernd Bestel
914dde4609 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 (closes #256) 2019-07-06 20:19:21 +02:00
Bernd Bestel
1eb1aa8b11 Added a "consume this recipe"-button to the meal plan (and also a button to consume all recipes for a whole week) (closes #283) 2019-07-06 20:02:40 +02:00
Bernd Bestel
09b23847b5 Added a new config.php setting DISABLE_AUTH to be able to disable authentication / the login screen (closes #246) 2019-07-06 18:29:18 +02:00
Bernd Bestel
8c205941c7 Added that products can now also be consumed as spoiled from the stock overview page (option in the more/context menu per line) (closes #251) 2019-07-06 18:15:53 +02:00
Bernd Bestel
b24683f954 Added the possibility to mark a shopping list item as "done" (closes #257) 2019-07-06 17:56:59 +02:00
Bernd Bestel
e4d26bb8fd Make it possible to switch shopping list items between shopping lists (closes #284) 2019-07-06 17:31:17 +02:00
Bernd Bestel
c6c10c87e4 Improved date display for dates of today and no time
Instead of the hours since midnight now just "Today" will be shown
2019-07-06 17:19:28 +02:00
Bernd Bestel
df529c3c0b Show 2999-12-31 as "Never" everywhere (closes #296) 2019-07-06 15:43:54 +02:00
Bernd Bestel
482a520062 Slightly modified new recipe stock fulfillment API endpoints (references #289) 2019-07-06 15:28:49 +02:00
Bernd Bestel
ddef58e2a9 Merge pull request #289 from Aerex/add-resolved-recipes-endpoint
Add requirement fulfillment recipes endpoint
2019-07-06 15:02:20 +02:00
Bernd Bestel
d34c7b0a87 Created the changelog for the next version 2019-07-06 14:56:56 +02:00
Bernd Bestel
b76e51ba41 Fixed nested recipes costs calculation (fixes #299) 2019-07-06 14:48:46 +02:00
Bernd Bestel
67cfd0ba5f Remove the internal meal plan recipe when removing a meal plan entry (fixes #298) 2019-07-06 13:57:49 +02:00
Bernd Bestel
3fcede0b7c Fix that "Track date only" cannot be tracked <> today (fixes #300) 2019-07-06 13:32:40 +02:00
Bernd Bestel
0c0e8c6957 Fixed the consume success message on stock overview page (fixes #302) 2019-07-06 13:13:38 +02:00
Aerex
a01a80578c Merge branch 'master' into add-resolved-recipes-endpoint 2019-06-20 23:40:55 -05:00
grocy
7a51fb77b0 feat: Added recipes/requirements route
- feat: Added requirements route to allow clients to access the requirements fulfilments of recipes
2019-06-20 23:11:57 -05:00
Bernd Bestel
511c95070e Minor typos... 2019-06-09 09:34:21 +02:00
Bernd Bestel
eec6270cb7 Prepared next release 2019-06-09 09:30:18 +02:00
Bernd Bestel
7c7c5db28c Fixed that a entered barcode on the product edit page was only saved when "adding" it to the barcodes list by pressing TAB (closes #269) 2019-06-09 09:21:56 +02:00
Bernd Bestel
6b98fa85d3 Improved meal plan entry reference when deleting an entry (references #255) 2019-06-09 09:10:29 +02:00
Bernd Bestel
825be63b93 Fixed single quotes problem in all pickers (fixes #264) 2019-06-09 09:06:44 +02:00
Bernd Bestel
a56f8be19e Fixed page reloads with unsaved form data and "Auto reload on external changes" enabled (fixes #265) 2019-06-09 08:58:46 +02:00
Bernd Bestel
f736c4de44 Added changelog with current changes 2019-06-08 17:03:17 +02:00
Bernd Bestel
56217804b7 Always include en_GB localizations regardless of completion (because there aren't real translations needed, fixes #276) 2019-06-08 16:56:37 +02:00
Bernd Bestel
43710b5062 Properly show the API error when it failed to undo a stock booking (fixes #267) 2019-06-08 16:53:31 +02:00
Bernd Bestel
b1adaa24cf Fix session problem on 32 bit systems when using "stay logged in permanently" (fixes #278) 2019-06-08 16:47:45 +02:00
Bernd Bestel
9e7d62b62d Make sure to hide all tooltips before removing an element (fixes #260) 2019-06-08 16:39:35 +02:00
Bernd Bestel
9f2481a6a8 Fixed an issue when a plural translation is empty/null (can be the case for quantity units, fixes #259) 2019-06-08 16:32:23 +02:00
Bernd Bestel
2b77bc6ae6 Fixed deleting meal plan entries did not work (fixes #255) 2019-06-08 16:30:45 +02:00
Bernd Bestel
91116ee768 Make sure user settings variable is populated always (fixes #277) 2019-06-08 15:54:56 +02:00
Bernd Bestel
167e57ef3f Merge pull request #272 from BlizzWave/patch-23
Update grocy_night_mode.css
2019-05-30 19:42:05 +02:00
Marius Boro
3321bcd683 Update grocy_night_mode.css
Fixed unreadable text
2019-05-30 14:32:27 +02:00
69 changed files with 3295 additions and 485 deletions

View File

@@ -1,3 +1,4 @@
pushd ..
tx pull --all --minimum-perc=90
tx pull --language en_GB
popd

View File

@@ -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' => [

View File

@@ -0,0 +1,11 @@
- Fixed that deleting meal plan entries did not work
- Fixed a problem that the user settings were not properly initialized for the frontend JS part when not logged only (so potentially affected only the login page)
- Fixed an issue that the shopping list did not load when a plural translation for a quantity unit was missing
- Fixed that tooltips were visible forever when consuming all products on the stock overview page
- Fixed that login did not work when "Stay logged in permanently" was set and grocy runs on a 32-bit system (thanks @matejdro)
- Fixed page reloads when "Auto reload on external changes" is enabled and there is unsaved form data (the detection did not work for forms in modal dialogs, e. g. when adding a entry to the meal plan)
- Fixed (again) that the product picker did not work properly when the product name contains single quotes
- Fixed that a entered barcode on the product edit page was only saved when "adding" it to the barcodes list by pressing `TAB` (is now automatically added to the list also when just leaving the field)
- Improved that errors/messages from the API are shown properly when undoing a stock booking is not possible (stock journal page)
- Improved night mode CSS (done by @BlizzWave, thanks!)
- A new localization for `en_GB` is now always included - nothing is really translated there, it's only about component "translations" that e. g. the first day of the week is correct for calendars

View 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

View File

@@ -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
@@ -41,6 +46,10 @@ Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
# set this to true
Setting('DISABLE_URL_REWRITING', false);
# 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);

View File

@@ -71,6 +71,10 @@ class BaseController
{
$container->view->set('userSettings', $usersService->GetUserSettings(GROCY_USER_ID));
}
else
{
$container->view->set('userSettings', null);
}
}
catch (\Exception $ex)
{

View File

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

View File

@@ -30,7 +30,7 @@ class LoginController extends BaseController
if ($user !== null && password_verify($inputPassword, $user->password))
{
$sessionKey = $this->SessionService->CreateSession($user->id, $stayLoggedInPermanently);
setcookie($this->SessionCookieName, $sessionKey, intval(time() + 31220640000)); // Cookie expires in 999 years, but session validity is up to SessionService
setcookie($this->SessionCookieName, $sessionKey, PHP_INT_SIZE == 4 ? PHP_INT_MAX : PHP_INT_MAX>>32); // Cookie expires never, but session validity is up to SessionService
if (password_needs_rehash($user->password, PASSWORD_DEFAULT))
{

View File

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

View File

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

View File

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

View File

@@ -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": {

View File

@@ -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"
@@ -1366,3 +1366,31 @@ msgstr "Aufgaben \"bald fällig\" Tage"
msgid "Products"
msgstr "Produkte"
msgid "Marked task %s as completed on %s"
msgstr "Aufgabe %s als erledigt markiert am %s"
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"

View File

@@ -0,0 +1,29 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"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"
"Language-Team: English (United Kingdom) (https://www.transifex.com/grocy/teams/93189/en_GB/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_GB\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/chore_types\n"
msgid "manually"
msgstr ""
msgid "dynamic-regular"
msgstr ""
msgid "daily"
msgstr ""
msgid "weekly"
msgstr ""
msgid "monthly"
msgstr ""

View File

@@ -0,0 +1,35 @@
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2019
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"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"
"Language-Team: English (United Kingdom) (https://www.transifex.com/grocy/teams/93189/en_GB/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_GB\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/component_translations\n"
msgid "timeago_locale"
msgstr ""
msgid "timeago_nan"
msgstr ""
msgid "moment_locale"
msgstr "en-gb"
msgid "datatables_localization"
msgstr ""
msgid "summernote_locale"
msgstr "en-gb"
msgid "fullcalendar_locale"
msgstr "en-gb"

View File

@@ -0,0 +1,280 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"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"
"Language-Team: English (United Kingdom) (https://www.transifex.com/grocy/teams/93189/en_GB/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_GB\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/demo_data\n"
msgid "Cookies"
msgstr ""
msgid "Chocolate"
msgstr ""
msgid "Pantry"
msgstr ""
msgid "Candy cupboard"
msgstr ""
msgid "Tinned food cupboard"
msgstr ""
msgid "Fridge"
msgstr ""
msgid "Piece"
msgid_plural "Pieces"
msgstr[0] ""
msgstr[1] ""
msgid "Pack"
msgid_plural "Packs"
msgstr[0] ""
msgstr[1] ""
msgid "Glass"
msgid_plural "Glasses"
msgstr[0] ""
msgstr[1] ""
msgid "Tin"
msgid_plural "Tins"
msgstr[0] ""
msgstr[1] ""
msgid "Can"
msgid_plural "Cans"
msgstr[0] ""
msgstr[1] ""
msgid "Bunch"
msgid_plural "Bunches"
msgstr[0] ""
msgstr[1] ""
msgid "Gummy bears"
msgstr ""
msgid "Crisps"
msgstr ""
msgid "Eggs"
msgstr ""
msgid "Noodles"
msgstr ""
msgid "Pickles"
msgstr ""
msgid "Gulash soup"
msgstr ""
msgid "Yogurt"
msgstr ""
msgid "Cheese"
msgstr ""
msgid "Cold cuts"
msgstr ""
msgid "Paprika"
msgstr ""
msgid "Cucumber"
msgstr ""
msgid "Radish"
msgstr ""
msgid "Tomato"
msgstr ""
msgid "Changed towels in the bathroom"
msgstr ""
msgid "Cleaned the kitchen floor"
msgstr ""
msgid "Warranty ends"
msgstr ""
msgid "TV remote control"
msgstr ""
msgid "Alarm clock"
msgstr ""
msgid "Heat remote control"
msgstr ""
msgid "Lawn mowed in the garden"
msgstr ""
msgid "Some good snacks"
msgstr ""
msgid "Pizza dough"
msgstr ""
msgid "Sieved tomatoes"
msgstr ""
msgid "Salami"
msgstr ""
msgid "Toast"
msgstr ""
msgid "Minced meat"
msgstr ""
msgid "Pizza"
msgstr ""
msgid "Spaghetti bolognese"
msgstr ""
msgid "Sandwiches"
msgstr ""
msgid "English"
msgstr ""
msgid "German"
msgstr ""
msgid "Italian"
msgstr ""
msgid "Demo in different language"
msgstr ""
msgid "This is the note content of the recipe ingredient"
msgstr ""
msgid "Demo User"
msgstr ""
msgid "Gram"
msgid_plural "Grams"
msgstr[0] ""
msgstr[1] ""
msgid "Flour"
msgstr ""
msgid "Pancakes"
msgstr ""
msgid "Sugar"
msgstr ""
msgid "Home"
msgstr ""
msgid "Life"
msgstr ""
msgid "Projects"
msgstr ""
msgid "Repair the garage door"
msgstr ""
msgid "Fork and improve grocy"
msgstr ""
msgid "Find a solution for what to do when I forget the door keys"
msgstr ""
msgid "Sweets"
msgstr ""
msgid "Bakery products"
msgstr ""
msgid "Tinned food"
msgstr ""
msgid "Butchery products"
msgstr ""
msgid "Vegetables/Fruits"
msgstr ""
msgid "Refrigerated products"
msgstr ""
msgid "Coffee machine"
msgstr ""
msgid "Dishwasher"
msgstr ""
msgid "Liter"
msgstr ""
msgid "Liters"
msgstr ""
msgid "Bottle"
msgstr ""
msgid "Bottles"
msgstr ""
msgid "Milk"
msgstr ""
msgid "Chocolate sauce"
msgstr ""
msgid "Milliliters"
msgstr ""
msgid "Milliliter"
msgstr ""
msgid "Bottom"
msgstr ""
msgid "Topping"
msgstr ""
msgid "French"
msgstr ""
msgid "Turkish"
msgstr ""
msgid "Spanish"
msgstr ""
msgid "Russian"
msgstr ""
msgid "The thing which happens on the 5th of every month"
msgstr ""
msgid "The thing which happens daily"
msgstr ""
msgid "The thing which happens on Mondays and Wednesdays"
msgstr ""
msgid "Swedish"
msgstr ""
msgid "Polish"
msgstr ""

View File

@@ -0,0 +1,26 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"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"
"Language-Team: English (United Kingdom) (https://www.transifex.com/grocy/teams/93189/en_GB/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_GB\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/stock_transaction_types\n"
msgid "purchase"
msgstr ""
msgid "consume"
msgstr ""
msgid "inventory-correction"
msgstr ""
msgid "product-opened"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-05-01 17:43+0000\n"
"Language-Team: English (United Kingdom) (https://www.transifex.com/grocy/teams/93189/en_GB/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_GB\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Domain: grocy/userfield_types\n"
msgid "text-single-line"
msgstr ""
msgid "text-multi-line"
msgstr ""
msgid "number-integral"
msgstr ""
msgid "number-decimal"
msgstr ""
msgid "date"
msgstr ""
msgid "datetime"
msgstr ""
msgid "checkbox"
msgstr ""
msgid "preset-list"
msgstr ""

View File

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

View File

@@ -1,6 +1,7 @@
# Translators:
# Cedric Octave <transifex@octvcdrc.fr>, 2019
# bigoudo, 2019
# Matthieu K, 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: bigoudo, 2019\n"
"Last-Translator: Matthieu K, 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"
@@ -37,36 +38,36 @@ msgstr "Réfrigérateur"
msgid "Piece"
msgid_plural "Pieces"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Unité"
msgstr[1] "Unités"
msgid "Pack"
msgid_plural "Packs"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Pack"
msgstr[1] "Packs"
msgid "Glass"
msgid_plural "Glasses"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Verre"
msgstr[1] "Verres"
msgid "Tin"
msgid_plural "Tins"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Boîte de conserve"
msgstr[1] "Boîtes de conserve"
msgid "Can"
msgid_plural "Cans"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Canette"
msgstr[1] "Canettes"
msgid "Bunch"
msgid_plural "Bunches"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Lot"
msgstr[1] "Lots"
msgid "Gummy bears"
msgstr "Oursons en gélatine"
msgstr "Bonbons Oursons"
msgid "Crisps"
msgstr "Chips"
@@ -81,7 +82,7 @@ msgid "Pickles"
msgstr "Cornichons"
msgid "Gulash soup"
msgstr "Soupe de goulache"
msgstr "Minestrone"
msgid "Yogurt"
msgstr "Yaourt"
@@ -93,7 +94,7 @@ msgid "Cold cuts"
msgstr "Charcuterie"
msgid "Paprika"
msgstr "Paprika"
msgstr "Piment d'Espelette"
msgid "Cucumber"
msgstr "Concombre"
@@ -105,7 +106,7 @@ msgid "Tomato"
msgstr "Tomate"
msgid "Changed towels in the bathroom"
msgstr "Changement des serviettes dans la salle de bain"
msgstr "Lessive des serviettes de la salle de bain"
msgid "Cleaned the kitchen floor"
msgstr "Nettoyage du sol de la cuisine"
@@ -172,8 +173,8 @@ msgstr "Utilisateur de démonstration"
msgid "Gram"
msgid_plural "Grams"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Gramme"
msgstr[1] "Grammes"
msgid "Flour"
msgstr "Farine"
@@ -262,25 +263,25 @@ msgid "French"
msgstr "Français"
msgid "Turkish"
msgstr ""
msgstr "Turque"
msgid "Spanish"
msgstr ""
msgstr "Espagnol"
msgid "Russian"
msgstr ""
msgstr "Russe"
msgid "The thing which happens on the 5th of every month"
msgstr ""
msgstr "La chose qui se répète le 5 de chaque mois"
msgid "The thing which happens daily"
msgstr ""
msgstr "La chose qui se répète chaque jour"
msgid "The thing which happens on Mondays and Wednesdays"
msgstr ""
msgstr "La chose qui se répète les Lundis et Mercredis"
msgid "Swedish"
msgstr ""
msgstr "Suèdois"
msgid "Polish"
msgstr ""
msgstr "Polonais"

View File

@@ -1,7 +1,11 @@
# Translators:
# Cedric Octave <transifex@octvcdrc.fr>, 2019
# bigoudo, 2019
# Bernd Bestel <bernd@berrnd.de>, 2019
# Alex Deneuvillers <alex.deneuvillers@gmail.com>, 2019
# Cedric Octave <transifex@octvcdrc.fr>, 2019
# Hydreliox Hydreliox <hydreliox@gmail.com>, 2019
# Matthieu K, 2019
# Mathieu Fortin <mathieugfortin@gmail.com>, 2019
#
msgid ""
msgstr ""
@@ -9,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: Bernd Bestel <bernd@berrnd.de>, 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"
@@ -23,52 +27,52 @@ msgstr "Aperçu du stock"
msgid "%s product expires"
msgid_plural "%s products expiring"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s produit périmé"
msgstr[1] "%s produits périmés"
msgid "within the next day"
msgid_plural "within the next %s days"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "d'ici demain"
msgstr[1] "d'ici %s jours"
msgid "%s product is already expired"
msgid_plural "%s products are already expired"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s produit est déjà périmé"
msgstr[1] "%s produits sont déjà périmés"
msgid "%s product is below defined min. stock amount"
msgid_plural "%s products are below defined min. stock amount"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s produit est en quantité inférieure à la limite définie"
msgstr[1] "%s produits sont en quantité inférieure à la limite définie"
msgid "Product"
msgstr "Produit"
msgid "%s Product"
msgid_plural "%s Products"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s Produit"
msgstr[1] "%s Produits"
msgid "Amount"
msgstr "Quantité"
msgid "Next best before date"
msgstr "Prochaine date de péremption"
msgstr "Prochaine DLUO/DLC"
msgid "Logout"
msgstr "Se déconnecter"
msgstr "Déconnexion"
msgid "Chores overview"
msgstr "Aperçu des corvées"
msgid "Batteries overview"
msgstr "Batteries"
msgstr "Piles"
msgid "Purchase"
msgstr "Achat"
msgid "Consume"
msgstr "Consommation"
msgstr "Consommer"
msgid "Inventory"
msgstr "Inventaire"
@@ -80,7 +84,7 @@ msgid "Chore tracking"
msgstr "Suivi des corvées"
msgid "Battery tracking"
msgstr "Suivi des batteries"
msgstr "Suivi des piles"
msgid "Locations"
msgstr "Emplacements"
@@ -113,7 +117,7 @@ msgid "Next planned charge cycle"
msgstr "Prochaine charge planifiée"
msgid "Best before"
msgstr "Date d'expiration"
msgstr "DLUO / DLC"
msgid "OK"
msgstr "Ok"
@@ -142,13 +146,14 @@ msgstr "La recherche par code barres est désactivée"
msgid ""
"will be added to the list of barcodes for the selected product on submit"
msgstr ""
"sera ajouté à la liste des codes barres du produit sélectionné à l'envoi"
"sera ajouté à la liste des codes-barres pour le produit sélectionné après "
"avoir cliqué sur le bouton"
msgid "New amount"
msgstr "Nouvelle quantité"
msgid "Note"
msgstr "Note"
msgstr "Remarques"
msgid "Tracked time"
msgstr "Réalisé le"
@@ -276,8 +281,8 @@ msgid ""
msgstr ""
"A l'achat, ce nombre de jours sera ajouté à la date de péremption suggérée"
msgid "This means 1 %s purchased will be converted into %s %s in stock"
msgstr "1 %s acheté sera converti en %s %s dans le stock"
msgid "This means 1 %1$s purchased will be converted into %2$s %3$s in stock"
msgstr "Signifie qu'après l'achat d'1 %1$s vous aurez %2$s %3$s dans le stock"
msgid "Login"
msgstr "Se connecter"
@@ -369,8 +374,8 @@ msgstr ""
"La prochaine exécution de cette corvée sera programmée %s jours après sa "
"dernière exécution"
msgid "Removed %s %s of %s from stock"
msgstr "%s %s de %s supprimés du stock"
msgid "Removed %1$s of %2$s from stock"
msgstr "%1$senlevé du stock de%2$s "
msgid "About grocy"
msgstr "À propos de grocy"
@@ -381,20 +386,17 @@ msgstr "Fermer"
msgid "Released on"
msgstr "Date de sortie"
msgid "Consume %s %s of %s"
msgstr "Consommer %s %s de %s"
msgid "Added %1$s of %2$s to stock"
msgstr "Ajouté %1$s de %2$s au stock"
msgid "Added %s %s of %s to stock"
msgstr "%s %s de %s ajoutés au stock"
msgid "Stock amount of %1$s is now %2$s"
msgstr "La quantité en stock de %1$s est maintenant de %2$s"
msgid "Stock amount of %s is now %s %s"
msgstr "La quantité en stock de %s est maintenant de %s %s"
msgid "Tracked execution of chore %1$s on %2$s"
msgstr "Suivi de la corvée%1$s sur %2$s"
msgid "Tracked execution of chore %s on %s"
msgstr "La corvée \"%s\" a été réalisée le %s"
msgid "Tracked charge cycle of battery %s on %s"
msgstr "La batterie \"%s\" a été rechargée le %s"
msgid "Tracked charge cycle of battery %1$s on %2$s"
msgstr "Suivi de la recharge des piles%1$s sur %2$s"
msgid "Consume all %s which are currently in stock"
msgstr "Consommer tous les %s actuellement en stock"
@@ -445,7 +447,7 @@ msgid "A period type is required"
msgstr "Un type de période est requis"
msgid "A best before date is required"
msgstr ""
msgstr "Une date \"À consommer de préférence\" est requise"
msgid "Settings"
msgstr "Paramètres"
@@ -481,7 +483,7 @@ msgid "Are you sure to delete recipe ingredient \"%s\"?"
msgstr "Voulez-vous vraiment supprimer l'ingrédient \"%s\" de la recette ?"
msgid "Are you sure to empty shopping list \"%s\"?"
msgstr ""
msgstr "Voulez-vous vraiment vider la liste de courses ?"
msgid "Clear list"
msgstr "Vider la liste"
@@ -500,7 +502,11 @@ msgid ""
msgid_plural ""
"Not enough in stock, %s ingredients missing but already on the shopping list"
msgstr[0] ""
"Pas assez en stock, %s ingrédient manquant mais déjà ajouté à la liste de "
"courses"
msgstr[1] ""
"Pas assez en stock, %s ingrédients manquants mais déjà ajoutés à la liste de"
" courses"
msgid "Expand to fullscreen"
msgstr "Mettre en plein écran"
@@ -514,8 +520,8 @@ msgstr "Préparation"
msgid "Recipe"
msgstr "Recette"
msgid "Not enough in stock, %s missing, %s already on shopping list"
msgstr "Pas assez en stock, %s manquant et %s déjà dans la liste de courses"
msgid "Not enough in stock, %1$s missing, %2$s already on shopping list"
msgstr "Pas assez en stock, %1$s manque, %2$s déjà dans la liste de courses"
msgid "Show notes"
msgstr "Afficher les notes"
@@ -615,28 +621,28 @@ msgstr "Unité"
msgid "%s Unit"
msgid_plural "%s Units"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s unité"
msgstr[1] "%s unités"
msgid "%s chore is due to be done"
msgid_plural "%s chores are due to be done"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s corvée reste à faire"
msgstr[1] "%s corvées restent à faire"
msgid "%s chore is overdue to be done"
msgid_plural "%s chores are overdue to be done"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s corvée n'a pas été réalisée à temps"
msgstr[1] "%s corvées n'ont pas été réalisées à temps"
msgid "%s battery is due to be charged"
msgid_plural "%s batteries are due to be charged"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s pile doit être rechargée"
msgstr[1] "%s piles doivent être rechargées"
msgid "%s battery is overdue to be charged"
msgid_plural "%s batteries are overdue to be charged"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s pile aurait dû être chargée"
msgstr[1] "%s piles auraient dû être rechargées"
msgid "in singular form"
msgstr "Au singulier"
@@ -724,13 +730,13 @@ msgstr "Voulez-vous vraiment supprimer la tâche \"%s\" ?"
msgid "%s task is due to be done"
msgid_plural "%s tasks are due to be done"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s tâche est due"
msgstr[1] "%s tâches sont dues"
msgid "%s task is overdue to be done"
msgid_plural "%s tasks are overdue to be done"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s tâche en retard"
msgstr[1] "%s tâches en retard"
msgid "Edit task category"
msgstr "Modifier la catégorie de tâche"
@@ -949,11 +955,11 @@ msgstr "Désactiver la vérification du stock pour cet ingrédient"
msgid "Add all list items to stock"
msgstr "Ajouter toute la liste dans le stock"
msgid "Add %s %s of %s to stock"
msgstr "Ajouter %s %s de %s au stock"
msgid "Add %1$s of %2$s to stock"
msgstr "Ajouter %1$s de %2$s au stock"
msgid "Adding shopping list item %s of %s"
msgstr "Ajout du produit %s sur %s de la liste de courses"
msgid "Adding shopping list item %1$s of %2$s"
msgstr "Ajout :%1$s de %2$s à la liste de courses"
msgid "Use a specific stock item"
msgstr "Utiliser un objet spécifique du stock"
@@ -966,8 +972,8 @@ msgstr ""
"qui est \"Le premier arrivant à péremption en premier, puis premier entré "
"premier sorti\""
msgid "Mark %s of %s as open"
msgstr ""
msgid "Mark %1$s of %2$s as open"
msgstr "Indiquer%1$s de %2$s comme ouvert"
msgid ""
"When a product was marked as opened, the best before date will be replaced "
@@ -980,14 +986,14 @@ msgstr ""
msgid "Default best before days after opened"
msgstr "Date de péremption en jours par défaut après ouverture"
msgid "Marked %s %s of %s as opened"
msgstr "%s %s de %s indiqués comme ouverts"
msgid "Marked %1$s of %2$s as opened"
msgstr "Indiqué%1$s de %2$s comme ouvert"
msgid "Mark as opened"
msgstr "Indiquer comme ouvert"
msgid "Expires on %s; Bought on %s"
msgstr "Périme le %s; Acheté le %s"
msgid "Expires on %1$s; Bought on %2$s"
msgstr "Se périmera le%1$s; Acheté le %2$s"
msgid "Not opened"
msgstr "Non ouvert"
@@ -1031,19 +1037,19 @@ msgid "Skip"
msgstr "Passer"
msgid "Servings"
msgstr ""
msgstr "Parts / Portions"
msgid "Costs"
msgstr ""
msgstr "Coûts"
msgid "Based on the prices of the last purchase per product"
msgstr ""
msgstr "Basé sur les prix du dernier achat du produit"
msgid "The ingredients listed here result in this amount of servings"
msgstr ""
msgstr "Les ingrédients mentionnés ici correspondent à ce nombre de portions"
msgid "Do not check against the shopping list when adding missing items to it"
msgstr ""
msgstr "Désactiver la fonction d'ajout automatique à la liste de courses"
msgid ""
"By default the amount to be added to the shopping list is \"needed amount - "
@@ -1051,227 +1057,335 @@ msgid ""
"checked against the stock amount, not against what is already on the "
"shopping list"
msgstr ""
"Par défaut, la quantité ajoutée à la liste d'achats est \"qté nécessaire - "
"qté en stock - qté sur la liste de courses\", lorsque cette fonction est "
"activée, la comparaison s'effectuera par rapport au stock et non à la "
"quantité déjà présente sur la liste de course."
msgid "Picture"
msgstr ""
msgstr "Photo"
msgid "Uncheck ingredients to not put them on the shopping list"
msgstr ""
"Pas assez en stock, %s ingrédients manquants mais déjà dans la liste de "
"courses"
msgid "This is for statistical purposes only"
msgstr ""
msgstr "À des fins statistiques uniquement"
msgid "You have to select a recipe"
msgstr ""
msgstr "Vous devez sélectionner une recette"
msgid "Key type"
msgstr ""
msgstr "Type d'attribut"
msgid "Share/Integrate calendar (iCal)"
msgstr ""
msgstr "Partage du calendrier (iCal)"
msgid ""
"Use the following (public) URL to share or integrate the calendar in iCal "
"format"
msgstr ""
"Utilisez cette URL (publique) pour partager ou intégrer le calendrier au "
"format iCal."
msgid "Allow partial units in stock"
msgstr ""
msgstr "Unités avec décimale"
msgid "Enable tare weight handling"
msgstr ""
msgstr "Activer la pesée"
msgid ""
"This is useful e.g. for flour in jars - on purchase/consume/inventory you "
"always weigh the whole jar, the amount to be posted is then automatically "
"calculated based on what is in stock and the tare weight defined below"
msgstr ""
"Lors de l'utilisation d'un récipient hermétique, vous pouvez définir ici la "
"\"tare\" ou \"poids à vide\". Le calcul du \"poids net\" s'effectuera "
"automatiquement à partir du \"poids brut\" que vous aurez constaté."
msgid "Tare weight"
msgstr ""
msgstr "Tare"
msgid ""
"Tare weight handling enabled - please weigh the whole container, the amount "
"to be posted will be automatically calculcated"
msgstr ""
"Mode pesée activé, veuillez saisir le poids total sans tenir compte du "
"récipient, appelé aussi \"poids brut\"."
msgid "You have to select a location"
msgstr ""
msgstr "Vous devez sélectionner un endroit"
msgid "List"
msgstr ""
msgstr "Liste"
msgid "Gallery"
msgstr ""
msgstr "Gallerie"
msgid "The current picture will be deleted when you save the recipe"
msgstr ""
msgstr "La photo actuelle sera supprimée lors de la sauvegarde de la recette."
msgid "Show product details"
msgstr ""
msgstr "Voir les détails du produit"
msgid "Stock journal for this product"
msgstr ""
msgstr "Journal du stock"
msgid "Show chore details"
msgstr ""
msgstr "Voir les détails de la corvée"
msgid "Journal for this chore"
msgstr ""
msgstr "Journal de la corvée"
msgid "Show battery details"
msgstr ""
msgstr "Voir les détails"
msgid "Journal for this battery"
msgstr ""
msgstr "Journal individuel"
msgid "System info"
msgstr ""
msgstr "Informations système"
msgid "Changelog"
msgstr ""
msgstr "Journalisation"
msgid "will be multiplied a factor of %s to get %s"
msgstr ""
msgid "will be multiplied a factor of %1$s to get %2$s"
msgstr "sera multiplié par %1$s pour obtenir %2$s"
msgid "The given date is earlier than today, are you sure?"
msgstr ""
msgstr "La date saisie est antérieure à aujourd'hui, êtes-vous sûr?"
msgid "Product count"
msgstr ""
msgstr "Nombre de produits"
msgid "Type a new product name or barcode and hit TAB to start a workflow"
msgstr ""
"Tapez un nouveau nom de produit ou un nouveau code-barres et appuyez sur la "
"touche de tabulation pour lancer un flux de travail."
msgid ""
"This will be used as the default setting when adding this product as a "
"recipe ingredient"
msgstr ""
"Ce paramétrage sera utilisé par défaut quand ce produit sera ajouté en tant "
"qu'ingrédient dans une recette"
msgid "Add item"
msgstr ""
msgstr "Ajouter item"
msgid "Selected shopping list"
msgstr ""
msgstr "Liste de courses sélectionnée"
msgid "New shopping list"
msgstr ""
msgstr "Nouvelle liste de courses"
msgid "Delete shopping list"
msgstr ""
msgstr "Supprimer liste de courses"
msgid "Chores settings"
msgstr ""
msgstr "Paramètres des corvées"
msgid "Batteries settings"
msgstr ""
msgstr "Paramètres des batteries"
msgid "Tasks settings"
msgstr ""
msgstr "Paramètres des tâches"
msgid "Create shopping list"
msgstr ""
msgstr "Créer liste de courses"
msgid "Are you sure to delete shopping list \"%s\"?"
msgstr ""
msgstr "Voulez-vous vraiment vider la liste de courses \"%s\" ?"
msgid "Average shelf life"
msgstr ""
msgstr "Durée moyenne de conservation "
msgid "Spoil rate"
msgstr ""
msgstr "Taux de gâte"
msgid "Show more"
msgstr ""
msgstr "Afficher plus"
msgid "Show less"
msgstr ""
msgstr "Afficher moins"
msgid "The amount must be between %s and %s"
msgstr ""
msgid "The amount must be between %1$s and %2$s"
msgstr "La quantité doit se situer entre %1$s et %2$s"
msgid "Day of month"
msgstr ""
msgstr "Jour du mois"
msgid "Monday"
msgstr ""
msgstr "Lundi"
msgid "Tuesday"
msgstr ""
msgstr "Mardi"
msgid "Wednesday"
msgstr ""
msgstr "Mercredi"
msgid "Thursday"
msgstr ""
msgstr "Jeudi"
msgid "Friday"
msgstr ""
msgstr "Vendredi"
msgid "Saturday"
msgstr ""
msgstr "Samedi"
msgid "Sunday"
msgstr ""
msgstr "Dimanche"
msgid "Configure userfields"
msgstr ""
msgstr "Configurer les attributs personalisés"
msgid "Userfields"
msgstr ""
msgstr "Attributs personalisés"
msgid "Filter by entity"
msgstr ""
msgstr "Filtrer par entité"
msgid "Entity"
msgstr ""
msgstr "Élément"
msgid "Caption"
msgstr ""
msgstr "Titre"
msgid "Type"
msgstr ""
msgstr "Type"
msgid "Create userfield"
msgstr ""
msgstr "Ajouter un attribut personalisé"
msgid "A entity is required"
msgstr ""
msgstr "Vous n'avez pas défini d'élément"
msgid "A caption is required"
msgstr ""
msgstr "Vous n'avez pas défini de titre"
msgid "A type is required"
msgstr ""
msgstr "Vous n'avez pas défini de type"
msgid "Show as column in tables"
msgstr ""
msgstr "Affichage des valeurs dans une colonne"
msgid "This is required and can only contain letters and numbers"
msgstr ""
msgstr "Obligatoire, ne peut contenir que des lettres et des chiffres"
msgid "Edit userfield"
msgstr ""
msgstr "Configurer l'attribut personalisé"
msgid "Plural forms"
msgstr ""
msgstr "Forme plurielle"
msgid "One plural form per line, the current language requires"
msgstr ""
msgstr "Une forme plurielle par ligne, la langue actuelle nécessite"
msgid "Plural count"
msgstr ""
msgstr "Nombre de pluriel"
msgid "Plural rule"
msgstr ""
msgstr "Règle pour la forme plurielle"
msgid "in plural form"
msgstr "Au pluriel"
msgid "Not enough in stock, %s ingredient missing"
msgid_plural "Not enough in stock, %s ingredients missing"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Pas assez en stock, il vous manque %s ingrédient"
msgstr[1] "Pas assez en stock, il vous manque %s ingrédients"
msgid "Consume %s of %s"
msgid "The amount cannot be lower than %1$s or equal %2$s"
msgstr "La quantité ne peut être inférieure à%1$s ou égale à %2$s"
msgid "Not enough in stock, but already on the shopping list"
msgstr "Pas assez en stock, mais déjà sur la liste de courses."
msgid "Not enough in stock"
msgstr "Pas assez en stock"
msgid "Expiring soon days"
msgstr "Jours avant péremption"
msgid "Default amount for purchase"
msgstr "Quantité ajoutée par défaut lors d'un achat"
msgid "Default amount for consume"
msgstr "Quantité retirée par défaut lors de la consommation"
msgid "Variable amount"
msgstr "Quantité variable"
msgid ""
"When this is not empty, it will be shown instead of the amount entered above"
" while the amount there will still be used for stock fulfillment checking"
msgstr ""
"Lorsque ce champs n'est pas vide, la valeur sera affichée à la place de "
"celle saisie ci-dessus mais la valeur initiale restera utilsée pour la "
"vérification des quantité minimales du stock."
msgid "Track date only"
msgstr "Suivre uniquement les dates"
msgid "When enabled only the day of an execution is tracked, not the time"
msgstr "Lorsque activé, seul le jour d'une exécution est suivi, pas l'heure"
msgid "Consume %1$s of %2$s"
msgstr "Consommer %1$s de %2$s"
msgid "Meal plan"
msgstr "Prévisions des menus"
msgid "Add recipe to %s"
msgstr "Ajouter la recette à %s"
msgid "%s serving"
msgid_plural "%s servings"
msgstr[0] "%s part / portion"
msgstr[1] "%s parts / portions"
msgid "Week costs"
msgstr "Budget hebo"
msgid "Configuration"
msgstr "Configuration"
msgid "A predefined list of values, one per line"
msgstr "Liste prédéfinie, une valeur par ligne"
msgid "Chores due soon days"
msgstr "Corvée à réaliser jours"
msgid "Batteries due to be charged soon days"
msgstr "piles doivent être rechargées jours"
msgid "Tasks due soon days"
msgstr "Tâches à réaliser bientôt jours"
msgid "Products"
msgstr "Produits"
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 ""

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
# Translators:
# Bernd Bestel <bernd@berrnd.de>, 2019
# Mitchel Nijkamp <mitchelnijkamp1@msn.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:43+0000\n"
"Last-Translator: Bernd Bestel <bernd@berrnd.de>, 2019\n"
"Last-Translator: Mitchel Nijkamp <mitchelnijkamp1@msn.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"
@@ -36,3 +37,6 @@ msgstr "Datum en tijd"
msgid "checkbox"
msgstr "Checkbox"
msgid "preset-list"
msgstr "vooringestelde lijst"

View File

@@ -37,33 +37,33 @@ msgstr "Kjøleskapet"
msgid "Piece"
msgid_plural "Pieces"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Del"
msgstr[1] "Deler"
msgid "Pack"
msgid_plural "Packs"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Pakke"
msgstr[1] "Pakker"
msgid "Glass"
msgid_plural "Glasses"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Glass"
msgstr[1] "Glass"
msgid "Tin"
msgid_plural "Tins"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Boks"
msgstr[1] "Bokser"
msgid "Can"
msgid_plural "Cans"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Boks"
msgstr[1] "Bokser"
msgid "Bunch"
msgid_plural "Bunches"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Bunt"
msgstr[1] "Bunter"
msgid "Gummy bears"
msgstr "Vingummibjørner"
@@ -172,8 +172,8 @@ msgstr "Demo Bruker"
msgid "Gram"
msgid_plural "Grams"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Gram"
msgstr[1] "Gram"
msgid "Flour"
msgstr "Mel"
@@ -278,7 +278,7 @@ msgid "The thing which happens on Mondays and Wednesdays"
msgstr "Husarbeid som skjer på mandager og onsdager"
msgid "Swedish"
msgstr ""
msgstr "Svensk"
msgid "Polish"
msgstr ""
msgstr "Polsk"

View File

@@ -1,6 +1,6 @@
# Translators:
# Marius Borø <blizzwave@gmail.com>, 2019
# Bernd Bestel <bernd@berrnd.de>, 2019
# Marius Borø <blizzwave@gmail.com>, 2019
#
msgid ""
msgstr ""
@@ -8,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: Marius Borø <blizzwave@gmail.com>, 2019\n"
"Language-Team: Norwegian (https://www.transifex.com/grocy/teams/93189/no/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -22,31 +22,31 @@ msgstr "Husholdning"
msgid "%s product expires"
msgid_plural "%s products expiring"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s går ut på dato"
msgstr[1] "%s produkter går ut på dato"
msgid "within the next day"
msgid_plural "within the next %s days"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "innen neste dag"
msgstr[1] "innen de %s dagene"
msgid "%s product is already expired"
msgid_plural "%s products are already expired"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s produkt har gått ut på dato"
msgstr[1] "%s produkter har gått ut på dato"
msgid "%s product is below defined min. stock amount"
msgid_plural "%s products are below defined min. stock amount"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s produkt er under definert min. for husholdningen"
msgstr[1] "%s produkter er under definert min. for husholdningen"
msgid "Product"
msgstr "Produkt"
msgid "%s Product"
msgid_plural "%s Products"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s Produkt"
msgstr[1] "%s Produkter"
msgid "Amount"
msgstr "Antall"
@@ -273,8 +273,9 @@ msgid ""
" date suggestion"
msgstr "Når innkjøp gjøres vil dette bli brukt som antall dager \"best før\""
msgid "This means 1 %s purchased will be converted into %s %s in stock"
msgstr "Dette betyr at 1 %s innkjøp vil bli omgjort til %s %s husholdning"
msgid "This means 1 %1$s purchased will be converted into %2$s %3$s in stock"
msgstr ""
"Dette betyr at %1$s innkjøp vil bli gjort om til %2$s %3$s i husholdningen"
msgid "Login"
msgstr "Logg inn"
@@ -364,8 +365,8 @@ msgstr ""
"Dette betyr at det er estimert at den nye utførelsen av denne husarbeid "
"oppgaven er logget %s dag etter den sist var logget"
msgid "Removed %s %s of %s from stock"
msgstr "Fjernet %s %s %s fra husholdningen"
msgid "Removed %1$s of %2$s from stock"
msgstr "Fjernet %1$s %2$s fra husholdnigen"
msgid "About grocy"
msgstr "Om Grocy"
@@ -376,20 +377,17 @@ msgstr "Lukk"
msgid "Released on"
msgstr "Utgitt"
msgid "Consume %s %s of %s"
msgstr "Forbruk %s %s av %s"
msgid "Added %1$s of %2$s to stock"
msgstr "Lagt til %1$s %2$s i husholdningen"
msgid "Added %s %s of %s to stock"
msgstr "%s %s %s lagt til i husholdningen"
msgid "Stock amount of %1$s is now %2$s"
msgstr "Husholdning av %1$s er nå %2$s"
msgid "Stock amount of %s is now %s %s"
msgstr "Husholdning antall %s er nå %s %s"
msgid "Tracked execution of chore %1$s on %2$s"
msgstr "Utførte husarbeid oppgave \"%1$s\" den %2$s"
msgid "Tracked execution of chore %s on %s"
msgstr "Utførte husarbeid oppgave \"%s\" den %s"
msgid "Tracked charge cycle of battery %s on %s"
msgstr "Ladet %s den %s"
msgid "Tracked charge cycle of battery %1$s on %2$s"
msgstr "Logget lading av batteri %1$s den %2$s"
msgid "Consume all %s which are currently in stock"
msgstr "Forbruk alle %s som er i husholdningen"
@@ -476,7 +474,7 @@ msgid "Are you sure to delete recipe ingredient \"%s\"?"
msgstr "Er du sikker du ønsker å slette ingrediens \"%s\" fra oppskriften?"
msgid "Are you sure to empty shopping list \"%s\"?"
msgstr ""
msgstr "Er du sikker du ønsker å slette handleliste \"%s\"?"
msgid "Clear list"
msgstr "Slett handleliste"
@@ -495,7 +493,10 @@ msgid ""
msgid_plural ""
"Not enough in stock, %s ingredients missing but already on the shopping list"
msgstr[0] ""
"Ikke nok i husholdningen, %s ingrediens mangler, men den er på handlelisten."
msgstr[1] ""
"Ikke nok i husholdningen, %s ingredienser mangler, men de er på "
"handlelisten."
msgid "Expand to fullscreen"
msgstr "Full skjerm"
@@ -509,8 +510,8 @@ msgstr "Forberedelse / Slik gjør du"
msgid "Recipe"
msgstr "Oppskrift"
msgid "Not enough in stock, %s missing, %s already on shopping list"
msgstr "Ikke nok i husholdningen, mangler %s, er %s på handlelisten"
msgid "Not enough in stock, %1$s missing, %2$s already on shopping list"
msgstr "Ikke nok i husholdningen, mangler %1$s, er %2$s på handlelisten"
msgid "Show notes"
msgstr "Vis notater"
@@ -608,28 +609,28 @@ msgstr "Enhet"
msgid "%s Unit"
msgid_plural "%s Units"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s Enhet"
msgstr[1] "%s Enheter"
msgid "%s chore is due to be done"
msgid_plural "%s chores are due to be done"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s husarbeidsoppgave som må gjøre nå"
msgstr[1] "%s husarbeidsoppgaver som må gjøre nå"
msgid "%s chore is overdue to be done"
msgid_plural "%s chores are overdue to be done"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s husarbeidsoppgave har gått over fristen"
msgstr[1] "%s husarbeidsoppgaver har gått over fristen"
msgid "%s battery is due to be charged"
msgid_plural "%s batteries are due to be charged"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s batteri må lades nå"
msgstr[1] "%s batterier må lades nå"
msgid "%s battery is overdue to be charged"
msgid_plural "%s batteries are overdue to be charged"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s batteri er over fristen for å bli ladet"
msgstr[1] "%s batterier er over fristen for å bli ladet"
msgid "in singular form"
msgstr "I entall"
@@ -715,13 +716,13 @@ msgstr "Er du sikker du ønsker slette oppgave \"%s\"?"
msgid "%s task is due to be done"
msgid_plural "%s tasks are due to be done"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s oppgave må gjøres nå"
msgstr[1] "%s oppgaver må gjøres nå"
msgid "%s task is overdue to be done"
msgid_plural "%s tasks are overdue to be done"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%s oppgave har gått over fristen"
msgstr[1] "%s oppgaver har gått over fristen"
msgid "Edit task category"
msgstr "Endre oppgave kategori"
@@ -941,11 +942,11 @@ msgstr "Ikke bruk husholdningsjekk for denne ingrediensen "
msgid "Add all list items to stock"
msgstr "Legg alle produktene i listen til husholdningen"
msgid "Add %s %s of %s to stock"
msgstr "Legg til %s %s av %s til husholdningen"
msgid "Add %1$s of %2$s to stock"
msgstr "Legg til %1$s %2$s i husholdningen"
msgid "Adding shopping list item %s of %s"
msgstr "Legger til produkt %s av %s fra handlelisten"
msgid "Adding shopping list item %1$s of %2$s"
msgstr "Legger til handlelisteenhet %1$s av %2$s"
msgid "Use a specific stock item"
msgstr "Velg et bestemt produkt"
@@ -957,8 +958,8 @@ msgstr ""
"Første produkt på listen vil bli konsumert først i henhold til standard "
"regelen. \"Går ut på dato først. Deretter først inn, først ut\"."
msgid "Mark %s of %s as open"
msgstr ""
msgid "Mark %1$s of %2$s as open"
msgstr "Merk %1$s av %2$s som åpen"
msgid ""
"When a product was marked as opened, the best before date will be replaced "
@@ -970,14 +971,14 @@ msgstr ""
msgid "Default best before days after opened"
msgstr "Standard best før dager etter åpnet"
msgid "Marked %s %s of %s as opened"
msgstr "Merket %s %s av %s som åpnet"
msgid "Marked %1$s of %2$s as opened"
msgstr "Merket %1$s av %2$s som åpnet"
msgid "Mark as opened"
msgstr "Merk som åpnet"
msgid "Expires on %s; Bought on %s"
msgstr "Går ut på dato %s; Kjøpt %s"
msgid "Expires on %1$s; Bought on %2$s"
msgstr "Går ut på dato %1$s, Kjøpt %2$s"
msgid "Not opened"
msgstr "Ikke åpnet"
@@ -1021,7 +1022,7 @@ msgid "Skip"
msgstr "Hopp over"
msgid "Servings"
msgstr "Prosjoner"
msgstr "Porsjoner"
msgid "Costs"
msgstr "Kostnad"
@@ -1134,8 +1135,8 @@ msgstr "System info"
msgid "Changelog"
msgstr "Change Log"
msgid "will be multiplied a factor of %s to get %s"
msgstr "Vil bli ganget med %s for å få %s"
msgid "will be multiplied a factor of %1$s to get %2$s"
msgstr "Vil bli ganget med %1$s for å få %2$s"
msgid "The given date is earlier than today, are you sure?"
msgstr ""
@@ -1143,48 +1144,52 @@ msgstr ""
"denne?"
msgid "Product count"
msgstr ""
msgstr "Produkt telling"
msgid "Type a new product name or barcode and hit TAB to start a workflow"
msgstr ""
"Skriv inn et nytt produkt eller strekkode, og så trykk TAB for å starte "
"prosessen"
msgid ""
"This will be used as the default setting when adding this product as a "
"recipe ingredient"
msgstr ""
"Dette vil bli brukt som standard innstilling når dette produktet blir brukt "
"som ingrediens i en oppskrift"
msgid "Add item"
msgstr ""
msgstr "Legg til produkt"
msgid "Selected shopping list"
msgstr ""
msgstr "Valgt handleliste:"
msgid "New shopping list"
msgstr ""
msgstr "Ny handleliste"
msgid "Delete shopping list"
msgstr ""
msgstr "Slett handleliste"
msgid "Chores settings"
msgstr ""
msgstr "Husarbeid - innstillinger "
msgid "Batteries settings"
msgstr ""
msgstr "Batteri - innstillinger"
msgid "Tasks settings"
msgstr ""
msgstr "Oppgaver - innstillinger"
msgid "Create shopping list"
msgstr ""
msgstr "Lag handleliste"
msgid "Are you sure to delete shopping list \"%s\"?"
msgstr ""
msgstr "Er du sikker du ønsker å slette handleliste \"%s\"?"
msgid "Average shelf life"
msgstr ""
msgstr "Gjennomsnittlig holdbarhetstid"
msgid "Spoil rate"
msgstr ""
msgstr "\"Utgått på dato\" rate"
msgid "Show more"
msgstr "Vis mer"
@@ -1192,11 +1197,11 @@ msgstr "Vis mer"
msgid "Show less"
msgstr "Vis mindre"
msgid "The amount must be between %s and %s"
msgstr ""
msgid "The amount must be between %1$s and %2$s"
msgstr "Antallet må være mellom %1$s og %2$s"
msgid "Day of month"
msgstr ""
msgstr "Dag i måned"
msgid "Monday"
msgstr "Mandag"
@@ -1220,63 +1225,153 @@ msgid "Sunday"
msgstr "Søndag"
msgid "Configure userfields"
msgstr ""
msgstr "Sett opp egendefinerte kolonner"
msgid "Userfields"
msgstr ""
msgstr "Kolonner"
msgid "Filter by entity"
msgstr ""
msgstr "Filtrér etter type"
msgid "Entity"
msgstr ""
msgstr "Enhet"
msgid "Caption"
msgstr ""
msgstr "Kolonne navn"
msgid "Type"
msgstr ""
msgstr "Type"
msgid "Create userfield"
msgstr ""
msgstr "Lag egendefinert kolonne"
msgid "A entity is required"
msgstr ""
msgstr "En enhet kreves"
msgid "A caption is required"
msgstr ""
msgstr "Et kolonne navn kreves"
msgid "A type is required"
msgstr ""
msgstr "En type kreves"
msgid "Show as column in tables"
msgstr ""
msgstr "Vis som kolonner på alle sider"
msgid "This is required and can only contain letters and numbers"
msgstr ""
msgstr "Dette kreves og kan kun inneholde bokstaver og tall"
msgid "Edit userfield"
msgstr ""
msgstr "Endre egendefinert kolonne"
msgid "Plural forms"
msgstr ""
msgstr "I flertall form"
msgid "One plural form per line, the current language requires"
msgstr ""
msgstr "Valgt språk må ha en flertallsform per linje"
msgid "Plural count"
msgstr ""
msgstr "Flertallstelling"
msgid "Plural rule"
msgstr ""
msgstr "Flertallsregel"
msgid "in plural form"
msgstr "I flertall"
msgid "Not enough in stock, %s ingredient missing"
msgid_plural "Not enough in stock, %s ingredients missing"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Ikke nok i husholdningen, %s ingrediens mangler"
msgstr[1] "Ikke nok i husholdningen, %s ingredienser mangler"
msgid "Consume %s of %s"
msgid "The amount cannot be lower than %1$s or equal %2$s"
msgstr "Antallet kan ikke være lavere enn %1$s eller lik %2$s"
msgid "Not enough in stock, but already on the shopping list"
msgstr "Ikke nok i husholdningen, men er på handelisten"
msgid "Not enough in stock"
msgstr "Ikke nok i husholdningen"
msgid "Expiring soon days"
msgstr "Går ut på dato snart dager"
msgid "Default amount for purchase"
msgstr "Standard antall for innkjøp"
msgid "Default amount for consume"
msgstr "Standard antall for forbruk"
msgid "Variable amount"
msgstr "Variabelt antall"
msgid ""
"When this is not empty, it will be shown instead of the amount entered above"
" while the amount there will still be used for stock fulfillment checking"
msgstr ""
"Når denne ikke er tom, vil denne bli vist i stedet for antallet skrevet inn "
"over, mens antallet der vil bli brukt for å sjekke husholdnigsantall."
msgid "Track date only"
msgstr "Logg kun dato"
msgid "When enabled only the day of an execution is tracked, not the time"
msgstr "Når dette er aktivert så vil kun dagen og ikke tiden bli logget"
msgid "Consume %1$s of %2$s"
msgstr "Forbruk %1$s av %2$s"
msgid "Meal plan"
msgstr "Middagsplanlegger"
msgid "Add recipe to %s"
msgstr "Legg oppskrift til %s"
msgid "%s serving"
msgid_plural "%s servings"
msgstr[0] "%s porsjon"
msgstr[1] "%s porsjoner"
msgid "Week costs"
msgstr "Utgift for uke"
msgid "Configuration"
msgstr "Oppsett"
msgid "A predefined list of values, one per line"
msgstr "En forhåndsdefinert liste av verdier, en per linje"
msgid "Chores due soon days"
msgstr "Husarbeid må gjøres snart dager"
msgid "Batteries due to be charged soon days"
msgstr "Batteri må lades snart dager"
msgid "Tasks due soon days"
msgstr "Oppgaver som må gjøres snart dager"
msgid "Products"
msgstr "Produkter"
msgid "Marked task %s as completed on %s"
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 ""

View File

@@ -1,3 +1,5 @@
# Translators:
# Marius Borø <blizzwave@gmail.com>, 2019
#
msgid ""
msgstr ""
@@ -5,6 +7,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:43+0000\n"
"Last-Translator: Marius Borø <blizzwave@gmail.com>, 2019\n"
"Language-Team: Norwegian (https://www.transifex.com/grocy/teams/93189/no/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -14,22 +17,25 @@ msgstr ""
"X-Domain: grocy/userfield_types\n"
msgid "text-single-line"
msgstr ""
msgstr "text-single-line"
msgid "text-multi-line"
msgstr ""
msgstr "text-multi-line"
msgid "number-integral"
msgstr ""
msgstr "number-integral"
msgid "number-decimal"
msgstr ""
msgstr "number-decimal"
msgid "date"
msgstr ""
msgstr "date"
msgid "datetime"
msgstr ""
msgstr "datetime"
msgid "checkbox"
msgstr ""
msgstr "checkbox"
msgid "preset-list"
msgstr "preset-list"

View File

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

View File

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

View File

@@ -1262,3 +1262,27 @@ msgstr ""
msgid "Products"
msgstr ""
msgid "Marked task %s as completed on %s"
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 ""

View File

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

View File

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

@@ -0,0 +1,2 @@
ALTER TABLE shopping_list
ADD done INT DEFAULT 0;

87
migrations/0076.sql Normal file
View 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;

View File

@@ -5,7 +5,12 @@
.night-mode .table-info,
.night-mode .table-info > td,
.night-mode .table-info > th {
background-color: #07373f;
background-color: #07373f;
color: #6c757d;
}
.night-mode .table {
color: #6c757d;
}
.night-mode .btn,

View File

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

View File

@@ -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,116 @@ 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");
if (isNever)
{
element.prev().text(__t("Never"));
}
if (isToday)
{
element.text(__t("Today"));
}
else
{
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.FrontendHelpers = { };
Grocy.FrontendHelpers.ValidateForm = function(formId)
{
@@ -374,11 +387,11 @@ Grocy.FrontendHelpers.ShowGenericError = function(message, exception)
console.error(exception);
}
$("form").on("keyup paste", "input, textarea", function()
$(document).on("keyup paste change", "input, textarea", function()
{
$(this).closest("form").addClass("is-dirty");
});
$("form").on("click", "select", function()
$(document).on("click", "select", function()
{
$(this).closest("form").addClass("is-dirty");
});

View File

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

View File

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

View File

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

View File

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

View File

@@ -43,7 +43,7 @@ $('.location-combobox').combobox({
var prefillByName = Grocy.Components.LocationPicker.GetPicker().parent().data('prefill-by-name').toString();
if (typeof prefillByName !== "undefined")
{
possibleOptionElement = $("#location_id option:contains('" + prefillByName + "')").first();
possibleOptionElement = $("#location_id option:contains(\"" + prefillByName + "\")").first();
if (possibleOptionElement.length > 0)
{

View File

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

View File

@@ -70,10 +70,10 @@ if (!prefillProduct2.isEmpty())
}
if (typeof prefillProduct !== "undefined")
{
var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + prefillProduct.replace("'", "\\'") + "']").first();
var possibleOptionElement = $("#product_id option[data-additional-searchdata*=\"" + prefillProduct + "\"]").first();
if (possibleOptionElement.length === 0)
{
possibleOptionElement = $("#product_id option:contains('" + prefillProduct + "')").first();
possibleOptionElement = $("#product_id option:contains(\"" + prefillProduct + "\")").first();
}
if (possibleOptionElement.length > 0)
@@ -120,7 +120,7 @@ $('#product_id_text_input').on('blur', function(e)
}
var input = $('#product_id_text_input').val().toString();
var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + input.replace("'", "\\'") + "']").first();
var possibleOptionElement = $("#product_id option[data-additional-searchdata*=\"" + input + "\"]").first();
if (GetUriParam('addbarcodetoselection') === undefined && possibleOptionElement.length > 0)
{
@@ -135,7 +135,7 @@ $('#product_id_text_input').on('blur', function(e)
return;
}
var optionElement = $("#product_id option:contains('" + input + "')").first();
var optionElement = $("#product_id option:contains(\"" + input + "\")").first();
if (input.length > 0 && optionElement.length === 0 && typeof GetUriParam('addbarcodetoselection') === "undefined")
{
var addProductWorkflowsAdditionalCssClasses = "";

View File

@@ -43,7 +43,7 @@ $('.recipe-combobox').combobox({
var prefillByName = Grocy.Components.RecipePicker.GetPicker().parent().data('prefill-by-name').toString();
if (typeof prefillByName !== "undefined")
{
possibleOptionElement = $("#recipe_id option:contains('" + prefillByName + "')").first();
possibleOptionElement = $("#recipe_id option:contains(\"" + prefillByName + "\")").first();
if (possibleOptionElement.length > 0)
{

View File

@@ -42,10 +42,10 @@ $('.user-combobox').combobox({
var prefillUser = Grocy.Components.UserPicker.GetPicker().parent().data('prefill-by-username').toString();
if (typeof prefillUser !== "undefined")
{
var possibleOptionElement = $("#user_id option[data-additional-searchdata*='" + prefillUser.replace("'", "\\'") + "']").first();
var possibleOptionElement = $("#user_id option[data-additional-searchdata*=\"" + prefillUser + "\"]").first();
if (possibleOptionElement.length === 0)
{
possibleOptionElement = $("#user_id option:contains('" + prefillUser + "')").first();
possibleOptionElement = $("#user_id option:contains(\"" + prefillUser + "\")").first();
}
if (possibleOptionElement.length > 0)

View File

@@ -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"));
}
},
});
@@ -98,12 +135,12 @@ $("#add-recipe-modal").on("shown.bs.modal", function(e)
$(document).on("click", ".remove-recipe-button", function(e)
{
var mealPlanEntry = JSON.parse($(this).parent().parent().attr("data-meal-plan-entry"));
var mealPlanEntry = JSON.parse($(this).parents(".fc-h-event:first").attr("data-meal-plan-entry"));
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);
}
);
}
}
});
});

View File

@@ -149,6 +149,11 @@ if (prefillBarcode !== undefined)
$('#name').focus();
}
$("#barcode-taginput").on("blur", function(e)
{
$("#barcode-taginput").tagsManager("pushTag", $("#barcode-taginput").val());
});
$('.input-group-qu').on('change', function(e)
{
var quIdPurchase = $("#qu_id_purchase").val();

View File

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

View File

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

View File

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

View File

@@ -69,6 +69,7 @@ $(document).on('click', '.undo-stock-booking-button', function(e)
function(xhr)
{
console.error(xhr);
toastr.error(__t(JSON.parse(xhr.response).error_message));
}
);
});

View File

@@ -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,
@@ -121,13 +120,15 @@ $(document).on('click', '.product-consume-button', function(e)
{
$('#product-' + productId + '-row').fadeOut(500, function()
{
$(this).tooltip("hide");
$(this).remove();
});
}
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);
});
@@ -155,8 +156,14 @@ $(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));
toastr.success(toastMessage);
RefreshContextualTimeago();
RefreshStatistics();
},

View File

@@ -78,6 +78,7 @@ $(document).on('click', '.do-task-button', function(e)
{
$('#task-' + taskId + '-row').fadeOut(500, function ()
{
$(this).tooltip("hide");
$(this).remove();
});
}
@@ -101,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();
@@ -129,6 +156,7 @@ $(document).on('click', '.delete-task-button', function (e)
{
$('#task-' + objectId + '-row').fadeOut(500, function ()
{
$(this).tooltip("hide");
$(this).remove();
});
},

View File

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

View File

@@ -109,7 +109,7 @@ class LocalizationService
}
}
public function __t(string $text, ...$placeholderValues)
public function __t($text, ...$placeholderValues)
{
$this->CheckAndAddMissingTranslationToPot($text);
@@ -123,14 +123,14 @@ class LocalizationService
}
}
public function __n($number, string $singularForm, ?string $pluralForm)
public function __n($number, $singularForm, $pluralForm)
{
$this->CheckAndAddMissingTranslationToPot($singularForm);
return sprintf($this->Translator->ngettext($singularForm, $pluralForm, $number), $number);
}
public function CheckAndAddMissingTranslationToPot(string $text)
public function CheckAndAddMissingTranslationToPot($text)
{
if (GROCY_MODE === 'dev')
{

View File

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

View File

@@ -40,7 +40,7 @@ class SessionService extends BaseService
$expires = date('Y-m-d H:i:s', intval(time() + 2592000)); // Default is that sessions expire in 30 days
if ($stayLoggedInPermanently === true)
{
$expires = date('Y-m-d H:i:s', intval(time() + 31220640000)); // 999 years aka forever
$expires = date('Y-m-d H:i:s', PHP_INT_SIZE == 4 ? PHP_INT_MAX : PHP_INT_MAX>>32); // Never
}
$sessionRow = $this->Database->sessions()->createRow(array(

View File

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

View File

@@ -1,4 +1,4 @@
{
"Version": "2.4.1",
"ReleaseDate": "2019-05-16"
"Version": "2.4.3",
"ReleaseDate": "2019-07-06"
}

View File

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

View File

@@ -121,10 +121,10 @@
<div id="selectedRecipeCard" class="card">
<div class="card-header">
<i class="fas fa-cocktail"></i> {{ $selectedRecipe->name }}&nbsp;&nbsp;
<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>&nbsp;&nbsp;
<a id="selectedRecipeEditButton" class="btn btn-sm btn-outline-info py-0" href="{{ $U('/recipe/') }}{{ $selectedRecipe->id }}">

View File

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

View File

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

View File

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

View File

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