Compare commits

..

No commits in common. "master" and "v4.7.0" have entirely different histories.

86 changed files with 649 additions and 753 deletions

View File

@ -46,7 +46,6 @@ try
}
catch (\Grocy\Helpers\EInvalidConfig $ex)
{
http_response_code(500);
exit('Invalid setting in config.php: ' . $ex->getMessage());
}
@ -61,11 +60,6 @@ if (!file_exists($viewcachePath))
// The version changed (so when an update was done)
// GROCY_BASE_URL OR GROCY_BASE_PATH changed
$hash = hash('sha256', file_get_contents(__DIR__ . '/version.json') . GROCY_BASE_URL . GROCY_BASE_PATH);
if (GROCY_MODE === 'dev')
{
// For dev mode use routes.php to track changes
$hash = hash('sha256', file_get_contents(__DIR__ . '/routes.php'));
}
$hashCacheFile = $viewcachePath . "/$hash.txt";
if (!file_exists($hashCacheFile))
{

View File

@ -1,17 +0,0 @@
### Stock
- Fixed that certain fields were not copied when copying a product
### Calendar
- Fixed that the iCal export was broken (any shared `https://<Grocy>/api/calendar/ical?secret=xxx`-link always returned `401 Unauthorized`)
### General
- Optimized that table dropdown filters now search accent insensitive
- Fixed that it wasn't possible to log in using passwords containing non-latin characters
### API
- New API endpoint `/stock/products/{productId}/copy` (to copy a product)
- Fixed that API key authentication did not work when running Grocy in a subdirectory

View File

@ -1,57 +0,0 @@
> ⚠️ xxxBREAKING CHANGESxxx
> ❗ xxxImportant upgrade informationXXX
> 💡 xxxMinor upgrade informationXXX
### New Feature: xxxx
- xxx
### Stock
- xxx
### Shopping list
- xxx
### Recipes
- xxx
### Meal plan
- xxx
### Chores
- xxx
### Calendar
- xxx
### Tasks
- xxx
### Batteries
- xxx
### Equipment
- xxx
### Userfields
- xxx
### General
- xxx
### API
- The API endpoints `/stock/products/{productId}/open` and `/stock/products/by-barcode/{barcode}/open` now support a new (optional) `location_id` request body parameter (to only open stock entries at that location)

34
composer.lock generated
View File

@ -1001,7 +1001,7 @@
},
{
"name": "illuminate/bus",
"version": "v12.69.1",
"version": "v12.68.0",
"source": {
"type": "git",
"url": "https://github.com/illuminate/bus.git",
@ -1054,7 +1054,7 @@
},
{
"name": "illuminate/collections",
"version": "v12.69.1",
"version": "v12.68.0",
"source": {
"type": "git",
"url": "https://github.com/illuminate/collections.git",
@ -1114,7 +1114,7 @@
},
{
"name": "illuminate/conditionable",
"version": "v12.69.1",
"version": "v12.68.0",
"source": {
"type": "git",
"url": "https://github.com/illuminate/conditionable.git",
@ -1160,7 +1160,7 @@
},
{
"name": "illuminate/container",
"version": "v12.69.1",
"version": "v12.68.0",
"source": {
"type": "git",
"url": "https://github.com/illuminate/container.git",
@ -1222,7 +1222,7 @@
},
{
"name": "illuminate/contracts",
"version": "v12.69.1",
"version": "v12.68.0",
"source": {
"type": "git",
"url": "https://github.com/illuminate/contracts.git",
@ -1270,7 +1270,7 @@
},
{
"name": "illuminate/events",
"version": "v12.69.1",
"version": "v12.68.0",
"source": {
"type": "git",
"url": "https://github.com/illuminate/events.git",
@ -1325,7 +1325,7 @@
},
{
"name": "illuminate/filesystem",
"version": "v12.69.1",
"version": "v12.68.0",
"source": {
"type": "git",
"url": "https://github.com/illuminate/filesystem.git",
@ -1392,7 +1392,7 @@
},
{
"name": "illuminate/macroable",
"version": "v12.69.1",
"version": "v12.68.0",
"source": {
"type": "git",
"url": "https://github.com/illuminate/macroable.git",
@ -1438,7 +1438,7 @@
},
{
"name": "illuminate/pipeline",
"version": "v12.69.1",
"version": "v12.68.0",
"source": {
"type": "git",
"url": "https://github.com/illuminate/pipeline.git",
@ -1490,7 +1490,7 @@
},
{
"name": "illuminate/reflection",
"version": "v12.69.1",
"version": "v12.68.0",
"source": {
"type": "git",
"url": "https://github.com/illuminate/reflection.git",
@ -1541,7 +1541,7 @@
},
{
"name": "illuminate/support",
"version": "v12.69.1",
"version": "v12.68.0",
"source": {
"type": "git",
"url": "https://github.com/illuminate/support.git",
@ -1621,7 +1621,7 @@
},
{
"name": "illuminate/view",
"version": "v12.69.1",
"version": "v12.68.0",
"source": {
"type": "git",
"url": "https://github.com/illuminate/view.git",
@ -3065,16 +3065,16 @@
},
{
"name": "slim/slim",
"version": "4.15.3",
"version": "4.15.2",
"source": {
"type": "git",
"url": "https://github.com/slimphp/Slim.git",
"reference": "3675bf6baac66b07032575b7bef4200b60b7974b"
"reference": "e12cb05ca2a14e8f459d019e87a31dc915b80470"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/3675bf6baac66b07032575b7bef4200b60b7974b",
"reference": "3675bf6baac66b07032575b7bef4200b60b7974b",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/e12cb05ca2a14e8f459d019e87a31dc915b80470",
"reference": "e12cb05ca2a14e8f459d019e87a31dc915b80470",
"shasum": ""
},
"require": {
@ -3177,7 +3177,7 @@
"type": "tidelift"
}
],
"time": "2026-09-01T17:11:05+00:00"
"time": "2026-05-22T08:00:12+00:00"
},
{
"name": "symfony/clock",

View File

@ -555,12 +555,6 @@ class StockApiController extends BaseApiController
throw new \Exception('An amount is required');
}
$locationId = null;
if (array_key_exists('location_id', $requestBody) && !empty($requestBody['location_id']) && is_numeric($requestBody['location_id']))
{
$locationId = $requestBody['location_id'];
}
$specificStockEntryId = 'default';
if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id']))
{
@ -574,7 +568,7 @@ class StockApiController extends BaseApiController
}
$transactionId = null;
$transactionId = StockService::GetInstance()->OpenProduct($args['productId'], $requestBody['amount'], $locationId, $specificStockEntryId, $transactionId, $allowSubproductSubstitution);
$transactionId = StockService::GetInstance()->OpenProduct($args['productId'], $requestBody['amount'], $specificStockEntryId, $transactionId, $allowSubproductSubstitution);
$args['transactionId'] = $transactionId;
return $this->StockTransactions($request, $response, $args);
}
@ -654,7 +648,7 @@ class StockApiController extends BaseApiController
$allowSubproductSubstitution = true;
}
return $this->FilteredApiResponse($response, StockService::GetInstance()->GetProductStockEntries($args['productId'], null, false, $allowSubproductSubstitution), $request->getQueryParams());
return $this->FilteredApiResponse($response, StockService::GetInstance()->GetProductStockEntries($args['productId'], false, $allowSubproductSubstitution), $request->getQueryParams());
}
public function LocationStockEntries(Request $request, Response $response, array $args)
@ -932,25 +926,4 @@ class StockApiController extends BaseApiController
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function CopyProduct(Request $request, Response $response, array $args)
{
User::CheckPermission($request, User::PERMISSION_STOCK_EDIT);
try
{
if (filter_var($args['productId'], FILTER_VALIDATE_INT) === false)
{
throw new \Exception('Provided {productId} is not a valid integer');
}
return $this->ApiResponse($response, [
'created_object_id' => StockService::GetInstance()->CopyProduct($args['productId'])
]);
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
}

View File

@ -31,7 +31,7 @@ class ExceptionController extends BaseApiController
}
$response = $this->ResponseFactory->createResponse();
$isApiRoute = str_contains($request->getUri()->getPath(), '/api/');
$isApiRoute = string_starts_with($request->getUri()->getPath(), '/api/');
if (!defined('GROCY_AUTHENTICATED'))
{

View File

@ -2255,10 +2255,6 @@
"allow_subproduct_substitution": {
"type": "boolean",
"description": "`true` when any in stock sub product should be used when the given product is a parent product and currently not in stock"
},
"location_id": {
"type": "integer",
"description": "A valid location id (if supplied, only stock at the given location is considered, if ommitted, stock of any location is considered)"
}
},
"example": {
@ -2337,53 +2333,6 @@
}
}
},
"/stock/products/{productId}/copy": {
"post": {
"summary": "Copies a product",
"tags": [
"Stock"
],
"parameters": [
{
"in": "path",
"name": "productId",
"required": true,
"description": "A valid product id of the product to copy",
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "The operation was successful",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"created_object_id": {
"type": "integer",
"description": "The id of the created product"
}
}
}
}
}
},
"400": {
"description": "The operation was not successful (possible errors are: Invalid product id)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error400"
}
}
}
}
}
}
},
"/stock/products/{productIdToKeep}/merge/{productIdToRemove}": {
"post": {
"summary": "Merges two products into one",
@ -2603,7 +2552,7 @@
},
"allow_subproduct_substitution": {
"type": "boolean",
"description": "`true` when any in stock sub product should be used when the given product is a parent product and currently not in stock"
"description": "`rue` when any in stock sub product should be used when the given product is a parent product and currently not in stock"
}
},
"example": {
@ -2826,11 +2775,7 @@
},
"allow_subproduct_substitution": {
"type": "boolean",
"description": "`true` when any in stock sub product should be used when the given product is a parent product and currently not in stock"
},
"location_id": {
"type": "integer",
"description": "A valid location id (if supplied, only stock at the given location is considered, if ommitted, stock of any location is considered)"
"description": "`rue` when any in stock sub product should be used when the given product is a parent product and currently not in stock"
}
},
"example": {

View File

@ -19,15 +19,15 @@ class PrerequisiteChecker
{
public function checkRequirements()
{
self::CheckPhpVersion();
self::CheckConfigFile();
self::CheckConfigDistFile();
self::CheckComposer();
self::CheckPhpExtensions();
self::CheckSqliteVersion();
self::checkForPhpVersion();
self::checkForConfigFile();
self::checkForConfigDistFile();
self::checkForComposer();
self::checkForPhpExtensions();
self::checkForSqliteVersion();
}
private function CheckComposer()
private function checkForComposer()
{
if (!file_exists(__DIR__ . '/../packages/autoload.php'))
{
@ -35,7 +35,7 @@ class PrerequisiteChecker
}
}
private function CheckConfigDistFile()
private function checkForConfigDistFile()
{
if (!file_exists(__DIR__ . '/../config-dist.php'))
{
@ -43,7 +43,7 @@ class PrerequisiteChecker
}
}
private function CheckConfigFile()
private function checkForConfigFile()
{
if (!file_exists(GROCY_DATAPATH . '/config.php'))
{
@ -51,7 +51,7 @@ class PrerequisiteChecker
}
}
private function CheckPhpExtensions()
private function checkForPhpExtensions()
{
$loadedExtensions = get_loaded_extensions();
foreach (REQUIRED_PHP_EXTENSIONS as $extension)
@ -63,16 +63,16 @@ class PrerequisiteChecker
}
}
private function CheckSqliteVersion()
private function checkForSqliteVersion()
{
$sqliteVersion = self::GetSqlVersion();
$sqliteVersion = self::getSqlVersionAsString();
if (version_compare($sqliteVersion, REQUIRED_SQLITE_VERSION, '<'))
{
throw new ERequirementNotMet('SQLite ' . REQUIRED_SQLITE_VERSION . ' is required, however you are running ' . $sqliteVersion);
}
}
private function CheckPhpVersion()
private function checkForPhpVersion()
{
$phpVersion = phpversion();
if (version_compare($phpVersion, REQUIRED_PHP_VERSION, '<'))
@ -81,7 +81,7 @@ class PrerequisiteChecker
}
}
private function GetSqlVersion(): string
private function getSqlVersionAsString()
{
$dbh = new \PDO('sqlite::memory:');
return $dbh->query('select sqlite_version()')->fetch()[0];

View File

@ -2393,6 +2393,9 @@ msgstr[1] "%s години"
msgid "Display product"
msgstr "Показване на продукта"
msgid "Copy recipe"
msgstr "Копирайте рецептата"
msgid "Copy of %s"
msgstr "Копие от %s"

View File

@ -2360,6 +2360,9 @@ msgstr[1] "%s anys"
msgid "Display product"
msgstr "Mostrar producte"
msgid "Copy recipe"
msgstr "Copiar recepta"
msgid "Copy of %s"
msgstr "Copia de %s"

View File

@ -5,8 +5,8 @@
# Radim Kabeláč <radim.ekk@gmail.com>, 2020
# Jaroslav Lichtblau <l10n@lichtblau.cz>, 2020
# Jarda Tesar <intossh@gmail.com>, 2021
# Michal Petříček <michal@petricek.org>, 2026
# Pavel Paseka, 2026
# Michal Petříček <michal@petricek.org>, 2026
#
msgid ""
msgstr ""
@ -14,7 +14,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: Pavel Paseka, 2026\n"
"Last-Translator: Michal Petříček <michal@petricek.org>, 2026\n"
"Language-Team: Czech (https://app.transifex.com/grocy/teams/93189/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -721,7 +721,7 @@ msgid "Are you sure you want to delete product group \"%s\"?"
msgstr "Odstranit skupinu produktů „%s“?"
msgid "Remember Me"
msgstr "Zapamatuj si mne"
msgstr ""
msgid "Status"
msgstr "Stav"
@ -1831,7 +1831,7 @@ msgid "Clear filter"
msgstr "Smazat filtr"
msgid "Permissions"
msgstr "Oprávnění"
msgstr ""
msgid "Are you sure you want to remove full permissions for yourself?"
msgstr "Opravdu chcete odstranit všechna svoje oprávnění?"
@ -2444,6 +2444,9 @@ msgstr[3] "%s let"
msgid "Display product"
msgstr "Zobrazit produkt"
msgid "Copy recipe"
msgstr "Vytvořit kopii receptu"
msgid "Copy of %s"
msgstr "%s (Kopie)"
@ -2908,25 +2911,25 @@ msgstr[2] "Balení"
msgstr[3] "Balení"
msgid "Manage user sessions"
msgstr "Správa přihlášených uživatelů"
msgstr ""
msgid "Sessions"
msgstr "Přihlášení"
msgstr ""
msgid "Token type"
msgstr "Způsob přihlášení"
msgstr ""
msgid "Token hash"
msgstr "Otisk přístupového kódu"
msgstr ""
msgid "Client"
msgstr "Přístup přes"
msgstr ""
msgid "This session"
msgstr "Toto přihlášení"
msgstr ""
msgid "Are you sure you want to delete the selected session?"
msgstr "Opravdu chcete zrušit vybrané přihlášení?"
msgstr ""
msgid "Last access"
msgstr "Poslední přístup"
msgstr ""

View File

@ -2367,6 +2367,9 @@ msgstr[1] "%s år"
msgid "Display product"
msgstr "Vis vare"
msgid "Copy recipe"
msgstr "Kopier opskrift"
msgid "Copy of %s"
msgstr "Kopi af %s"

View File

@ -2410,6 +2410,9 @@ msgstr[1] "%s Jahre"
msgid "Display product"
msgstr "Produkt anzeigen"
msgid "Copy recipe"
msgstr "Rezept kopieren"
msgid "Copy of %s"
msgstr "Kopie von %s"

View File

@ -2315,6 +2315,9 @@ msgstr[1] "%sχρόνια"
msgid "Display product"
msgstr "Προβολή προϊόντος"
msgid "Copy recipe"
msgstr "Αντιγραφή συνταγής"
msgid "Copy of %s"
msgstr ""

View File

@ -2370,6 +2370,9 @@ msgstr[1] "%s years"
msgid "Display product"
msgstr "Display product"
msgid "Copy recipe"
msgstr "Copy recipe"
msgid "Copy of %s"
msgstr "Copy of %s"

View File

@ -2450,6 +2450,9 @@ msgstr[2] "%s años"
msgid "Display product"
msgstr "Mostrar producto"
msgid "Copy recipe"
msgstr "Copiar receta"
msgid "Copy of %s"
msgstr "Copia de %s"

View File

@ -2361,6 +2361,9 @@ msgstr[1] "%s aastat"
msgid "Display product"
msgstr "Näita toodet"
msgid "Copy recipe"
msgstr "Kopeeri retsept"
msgid "Copy of %s"
msgstr "%s Koopia"

View File

@ -2383,6 +2383,9 @@ msgstr[1] "%s vuotta"
msgid "Display product"
msgstr "Näytä tuote"
msgid "Copy recipe"
msgstr "Kopioi resepti"
msgid "Copy of %s"
msgstr "Kopio nimikkeestä %s"

View File

@ -2473,6 +2473,9 @@ msgstr[2] "%s ans"
msgid "Display product"
msgstr "Afficher le produit"
msgid "Copy recipe"
msgstr "Copier la recette"
msgid "Copy of %s"
msgstr "Copie de %s"

View File

@ -700,7 +700,7 @@ msgid "Are you sure you want to delete product group \"%s\"?"
msgstr "למחוק את קבוצת המוצרים „%s”?"
msgid "Remember Me"
msgstr "לזכור אותי"
msgstr ""
msgid "Status"
msgstr "מצב"
@ -1782,7 +1782,7 @@ msgid "Clear filter"
msgstr "לבטל את הסינון"
msgid "Permissions"
msgstr "הרשאה"
msgstr ""
msgid "Are you sure you want to remove full permissions for yourself?"
msgstr "להסיר את מלוא ההרשאות לעצמך?"
@ -2375,6 +2375,9 @@ msgstr[2] "%s שנים"
msgid "Display product"
msgstr "הצגת מוצר"
msgid "Copy recipe"
msgstr "העתקת מתכון"
msgid "Copy of %s"
msgstr "עותק של %s"
@ -2817,25 +2820,25 @@ msgstr[2] "אריזות"
msgstr[3] "אריזות"
msgid "Manage user sessions"
msgstr "ניהול הפעלות משתמשים"
msgstr ""
msgid "Sessions"
msgstr "הפעלות"
msgstr ""
msgid "Token type"
msgstr "סוג אסימון"
msgstr ""
msgid "Token hash"
msgstr "גיבוב אסימון"
msgstr ""
msgid "Client"
msgstr "לקוח"
msgstr ""
msgid "This session"
msgstr "ההפעלה הזאת"
msgstr ""
msgid "Are you sure you want to delete the selected session?"
msgstr "למחוק את ההפעלה הנבחרת?"
msgstr ""
msgid "Last access"
msgstr "גישה אחרונה"
msgstr ""

View File

@ -2384,6 +2384,9 @@ msgstr[1] "%s év"
msgid "Display product"
msgstr "Termék megjelenítése"
msgid "Copy recipe"
msgstr "Recept másolás"
msgid "Copy of %s"
msgstr "%s másolata"

View File

@ -2472,6 +2472,9 @@ msgstr[2] "%s anni"
msgid "Display product"
msgstr "Visualizza prodotto"
msgid "Copy recipe"
msgstr "Copia ricetta"
msgid "Copy of %s"
msgstr "Copia di %s"

View File

@ -3,7 +3,7 @@
# 鳥山 陽平 <kagemomiji@gmail.com>, 2020
# Cisco Benjamin <metallica@vivaldi.net>, 2020
# Takuya Mizuguchi, 2024
# pigmentblue15, 2026
# pigmentblue15, 2025
#
msgid ""
msgstr ""
@ -11,7 +11,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: pigmentblue15, 2026\n"
"Last-Translator: pigmentblue15, 2025\n"
"Language-Team: Japanese (https://app.transifex.com/grocy/teams/93189/ja/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -76,7 +76,7 @@ msgid "Cheese"
msgstr "チーズ"
msgid "Cold cuts"
msgstr "スライスハム"
msgstr "コールドカット"
msgid "Paprika"
msgstr "パプリカ"

View File

@ -2226,6 +2226,9 @@ msgstr[0] "%s年"
msgid "Display product"
msgstr "物品を表示"
msgid "Copy recipe"
msgstr "レシピをコピー"
msgid "Copy of %s"
msgstr "%sのコピー"

View File

@ -2233,6 +2233,9 @@ msgstr[0] "%s년"
msgid "Display product"
msgstr ""
msgid "Copy recipe"
msgstr ""
msgid "Copy of %s"
msgstr ""

View File

@ -2446,6 +2446,9 @@ msgstr[3] "%smetų"
msgid "Display product"
msgstr "Rodyti produktą"
msgid "Copy recipe"
msgstr "Kopijuoti receptą"
msgid "Copy of %s"
msgstr "Kopijuoti iš %s"

View File

@ -2440,6 +2440,9 @@ msgstr[1] "%s jaren"
msgid "Display product"
msgstr "Toon product"
msgid "Copy recipe"
msgstr "Kopieer recept"
msgid "Copy of %s"
msgstr "Kopie van %s"

View File

@ -2376,6 +2376,9 @@ msgstr[1] "%s år"
msgid "Display product"
msgstr "Vis produkt"
msgid "Copy recipe"
msgstr "Kopier oppskrift"
msgid "Copy of %s"
msgstr "Kopi av %s"

View File

@ -2467,6 +2467,9 @@ msgstr[3] "%s lat"
msgid "Display product"
msgstr "Wyświetl produkt"
msgid "Copy recipe"
msgstr "Kopiuj przepis"
msgid "Copy of %s"
msgstr "Kopia %s"

View File

@ -2445,6 +2445,9 @@ msgstr[2] "%s anos"
msgid "Display product"
msgstr "Exibir produto"
msgid "Copy recipe"
msgstr "Copiar receita"
msgid "Copy of %s"
msgstr "Copiar de %s"

View File

@ -2321,6 +2321,9 @@ msgstr[2] ""
msgid "Display product"
msgstr ""
msgid "Copy recipe"
msgstr ""
msgid "Copy of %s"
msgstr ""

View File

@ -2351,6 +2351,9 @@ msgstr[2] "%s ani"
msgid "Display product"
msgstr ""
msgid "Copy recipe"
msgstr ""
msgid "Copy of %s"
msgstr ""

View File

@ -2463,6 +2463,9 @@ msgstr[3] "%s лет"
msgid "Display product"
msgstr "Показать продукт"
msgid "Copy recipe"
msgstr "Копировать рецепт"
msgid "Copy of %s"
msgstr "Копия %s"

View File

@ -2343,6 +2343,9 @@ msgstr[3] ""
msgid "Display product"
msgstr "Zobraziť výrobok"
msgid "Copy recipe"
msgstr ""
msgid "Copy of %s"
msgstr ""

View File

@ -2337,6 +2337,9 @@ msgstr[3] "%s let"
msgid "Display product"
msgstr "Prikaži izdelek"
msgid "Copy recipe"
msgstr "Kopiraj recept"
msgid "Copy of %s"
msgstr "Kopiraj %s"

View File

@ -2113,6 +2113,9 @@ msgstr[1] ""
msgid "Display product"
msgstr ""
msgid "Copy recipe"
msgstr ""
msgid "Copy of %s"
msgstr ""

View File

@ -2395,6 +2395,9 @@ msgstr[1] "%s år"
msgid "Display product"
msgstr "Visa produkt"
msgid "Copy recipe"
msgstr "Kopiera recept"
msgid "Copy of %s"
msgstr "Kopia av %s"

View File

@ -2246,6 +2246,9 @@ msgstr[1] ""
msgid "Display product"
msgstr ""
msgid "Copy recipe"
msgstr ""
msgid "Copy of %s"
msgstr ""

View File

@ -2363,6 +2363,9 @@ msgstr[1] "%syıl"
msgid "Display product"
msgstr "Ürünü görüntüle"
msgid "Copy recipe"
msgstr "Tarifi kopyala"
msgid "Copy of %s"
msgstr "%s'ın kopyası"

View File

@ -2388,6 +2388,9 @@ msgstr[3] "%s років"
msgid "Display product"
msgstr "Показати продукт"
msgid "Copy recipe"
msgstr "Копіювати рецепт"
msgid "Copy of %s"
msgstr "Копія %s"

View File

@ -2222,6 +2222,9 @@ msgstr[0] "%s年"
msgid "Display product"
msgstr "显示产品"
msgid "Copy recipe"
msgstr "复制菜谱"
msgid "Copy of %s"
msgstr "复制%s"

View File

@ -2218,6 +2218,9 @@ msgstr[0] "%s 年"
msgid "Display product"
msgstr ""
msgid "Copy recipe"
msgstr "複製食譜"
msgid "Copy of %s"
msgstr ""

View File

@ -40,7 +40,7 @@ class ApiKeyAuthMiddleware extends BaseAuthMiddleware
// Handling of special purpose API keys
if (!$validApiKey)
{
if ($this->GetRouteName($request) === 'calendar-ical')
if ($this->RouteName === 'calendar-ical')
{
if ($request->getQueryParam('secret') !== null && $apiKeyService->IsValidApiKey($request->getQueryParam('secret'), ApiKeyService::API_KEY_TYPE_SPECIAL_PURPOSE_CALENDAR_ICAL))
{

View File

@ -11,10 +11,17 @@ use Slim\Routing\RouteContext;
abstract class BaseAuthMiddleware extends BaseMiddleware
{
protected ?string $RouteName = null;
protected bool $IsApiRoute = false;
public function __invoke(Request $request, RequestHandler $handler): Response
{
$routeName = $this->GetRouteName($request);
if ($routeName === 'root' || $routeName === 'login')
$routeContext = RouteContext::fromRequest($request);
$route = $routeContext->getRoute();
$this->RouteName = $route->getName();
$this->IsApiRoute = string_starts_with($request->getUri()->getPath(), '/api/');
if ($this->RouteName === 'root' || $this->RouteName === 'login')
{
// Root and Login routes are public/unauthenticated
@ -46,7 +53,7 @@ abstract class BaseAuthMiddleware extends BaseMiddleware
define('GROCY_AUTHENTICATED', false);
$response = $this->ResponseFactory->createResponse();
if ($this->IsApiRoute($request))
if ($this->IsApiRoute)
{
return $response->withStatus(401);
}
@ -62,7 +69,7 @@ abstract class BaseAuthMiddleware extends BaseMiddleware
define('GROCY_USER_USERNAME', $user->username);
define('GROCY_USER_PICTURE_FILE_NAME', $user->picture_file_name);
return $handler->handle($request);
return $response = $handler->handle($request);
}
}
}
@ -115,16 +122,6 @@ abstract class BaseAuthMiddleware extends BaseMiddleware
}
}
protected function GetRouteName(Request $request): ?string
{
return RouteContext::fromRequest($request)->getRoute()?->getName();
}
protected function IsApiRoute(Request $request): bool
{
return string_starts_with(RouteContext::fromRequest($request)->getRoute()->getPattern(), '/api/');
}
/**
* @param array $postParams
* @return bool True/False if the provided credentials were valid

View File

@ -10,7 +10,7 @@ class DefaultAuthMiddleware extends BaseAuthMiddleware
{
protected function AuthenticateRequest(Request $request)
{
if ($this->IsApiRoute($request))
if ($this->IsApiRoute)
{
// Session cookie or API Key is ok
$auth = new SessionAuthMiddleware($this->AppContainer, $this->ResponseFactory);

View File

@ -13,7 +13,7 @@ class ReverseProxyAuthMiddleware extends BaseAuthMiddleware
define('GROCY_EXTERNALLY_MANAGED_AUTHENTICATION', true);
// Try to use regular API Key authentication (applies when the reverse proxy is configured to be bypassed for API routes)
if ($this->IsApiRoute($request))
if ($this->IsApiRoute)
{
$auth = new ApiKeyAuthMiddleware($this->AppContainer, $this->ResponseFactory);
$user = $auth->AuthenticateRequest($request);

View File

@ -39,7 +39,6 @@ try
}
catch (Grocy\Helpers\ERequirementNotMet $ex)
{
http_response_code(500);
exit('Unable to run Grocy: ' . $ex->getMessage());
}

View File

@ -9,7 +9,7 @@
$('#batteries-table tbody').removeClass("d-none");
batteriesTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -17,17 +17,17 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
batteriesTable.search(value.accentNeutralise()).draw();
batteriesTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
batteriesTable.search("").draw();
$("#show-disabled").prop('checked', false);
});
$(document).on('click', '.battery-delete-button', function (e)
$(document).on('click', '.battery-delete-button', function(e)
{
var objectName = $(e.currentTarget).attr('data-battery-name');
var objectId = $(e.currentTarget).attr('data-battery-id');
@ -45,16 +45,16 @@ $(document).on('click', '.battery-delete-button', function (e)
}
},
closeButton: false,
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.Api.Delete('objects/batteries/' + objectId, {},
function (result)
function(result)
{
window.location.href = U('/batteries');
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -64,7 +64,7 @@ $(document).on('click', '.battery-delete-button', function (e)
});
});
$("#show-disabled").change(function ()
$("#show-disabled").change(function()
{
if (this.checked)
{

View File

@ -8,7 +8,7 @@
$('#batteries-journal-table tbody').removeClass("d-none");
batteriesJournalTable.columns.adjust().draw();
$("#battery-filter").on("change", function ()
$("#battery-filter").on("change", function()
{
var value = $(this).val();
if (value === "all")
@ -23,7 +23,7 @@ $("#battery-filter").on("change", function ()
window.location.reload();
});
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -31,10 +31,10 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
batteriesJournalTable.search(value.accentNeutralise()).draw();
batteriesJournalTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
$("#battery-filter").val("all");
@ -45,7 +45,7 @@ $("#clear-filter-button").on("click", function ()
window.location.reload();
});
$("#daterange-filter").on("change", function ()
$("#daterange-filter").on("change", function()
{
UpdateUriParam("months", $(this).val());
window.location.reload();
@ -61,7 +61,7 @@ if (typeof GetUriParam("months") !== "undefined")
$("#daterange-filter").val(GetUriParam("months"));
}
$(document).on('click', '.undo-battery-execution-button', function (e)
$(document).on('click', '.undo-battery-execution-button', function(e)
{
e.preventDefault();
@ -69,7 +69,7 @@ $(document).on('click', '.undo-battery-execution-button', function (e)
var chargeCycleId = $(e.currentTarget).attr('data-charge-cycle-id');
Grocy.Api.Post('batteries/charge-cycles/' + chargeCycleId.toString() + '/undo', {},
function (result)
function(result)
{
element.closest("tr").addClass("text-muted");
element.parent().siblings().find("span.name-anchor").addClass("text-strike-through").after("<br>" + __t("Undone on") + " " + moment().format("YYYY-MM-DD HH:mm:ss") + " <time class='timeago timeago-contextual' datetime='" + moment().format("YYYY-MM-DD HH:mm:ss") + "'></time>");
@ -77,7 +77,7 @@ $(document).on('click', '.undo-battery-execution-button', function (e)
RefreshContextualTimeago("#charge-cycle-" + chargeCycleId + "-row");
toastr.success(__t("Charge cycle successfully undone"));
},
function (xhr)
function(xhr)
{
console.error(xhr);
}

View File

@ -10,7 +10,7 @@
$('#batteries-overview-table tbody').removeClass("d-none");
batteriesOverviewTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -18,10 +18,10 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
batteriesOverviewTable.search(value.accentNeutralise()).draw();
batteriesOverviewTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
$("#status-filter").val("all");
@ -29,7 +29,7 @@ $("#clear-filter-button").on("click", function ()
batteriesOverviewTable.search("").draw();
});
$("#status-filter").on("change", function ()
$("#status-filter").on("change", function()
{
var value = $(this).val();
if (value === "all")
@ -40,17 +40,17 @@ $("#status-filter").on("change", function ()
// Transfer CSS classes of selected element to dropdown element (for background)
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
batteriesOverviewTable.column(batteriesOverviewTable.colReorder.transpose(5)).search(value.accentNeutralise()).draw();
batteriesOverviewTable.column(batteriesOverviewTable.colReorder.transpose(5)).search(value).draw();
});
$(".status-filter-message").on("click", function ()
$(".status-filter-message").on("click", function()
{
var value = $(this).data("status-filter");
$("#status-filter").val(value);
$("#status-filter").trigger("change");
});
$(document).on('click', '.track-charge-cycle-button', function (e)
$(document).on('click', '.track-charge-cycle-button', function(e)
{
e.preventDefault();
@ -61,10 +61,10 @@ $(document).on('click', '.track-charge-cycle-button', function (e)
var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss');
Grocy.Api.Post('batteries/' + batteryId + '/charge', { 'tracked_time': trackedTime },
function ()
function()
{
Grocy.Api.Get('batteries/' + batteryId,
function (result)
function(result)
{
var batteryRow = $('#battery-' + batteryId + '-row');
var nextXDaysThreshold = moment().add($("#info-due-soon-batteries").data("next-x-days"), "days");
@ -97,14 +97,14 @@ $(document).on('click', '.track-charge-cycle-button', function (e)
RefreshContextualTimeago("#battery-" + batteryId + "-row");
RefreshStatistics();
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
@ -112,12 +112,12 @@ $(document).on('click', '.track-charge-cycle-button', function (e)
);
});
$(document).on('click', '.battery-grocycode-label-print', function (e)
$(document).on('click', '.battery-grocycode-label-print', function(e)
{
e.preventDefault();
var batteryId = $(e.currentTarget).attr('data-battery-id');
Grocy.Api.Get('batteries/' + batteryId + '/printlabel', function (labelData)
Grocy.Api.Get('batteries/' + batteryId + '/printlabel', function(labelData)
{
if (Grocy.Webhooks.labelprinter !== undefined)
{
@ -130,7 +130,7 @@ function RefreshStatistics()
{
var nextXDays = $("#info-due-soon-batteries").data("next-x-days");
Grocy.Api.Get('batteries',
function (result)
function(result)
{
var dueTodayCount = 0;
var dueSoonCount = 0;
@ -162,7 +162,7 @@ function RefreshStatistics()
$("#info-due-soon-batteries").html('<span class="d-block d-md-none">' + dueSoonCount + ' <i class="fa-solid fa-clock"></i></span><span class="d-none d-md-block">' + __n(dueSoonCount, '%s battery is due to be charged', '%s batteries are due to be charged') + ' ' + __n(nextXDays, 'within the next day', 'within the next %s days'));
$("#info-overdue-batteries").html('<span class="d-block d-md-none">' + overdueCount + ' <i class="fa-solid fa-times-circle"></i></span><span class="d-none d-md-block">' + __n(overdueCount, '%s battery is overdue to be charged', '%s batteries are overdue to be charged'));
},
function (xhr)
function(xhr)
{
console.error(xhr);
}

View File

@ -8,7 +8,7 @@
$('#chores-table tbody').removeClass("d-none");
choresTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -16,17 +16,17 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
choresTable.search(value.accentNeutralise()).draw();
choresTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
choresTable.search("").draw();
$("#show-disabled").prop('checked', false);
});
$(document).on('click', '.chore-delete-button', function (e)
$(document).on('click', '.chore-delete-button', function(e)
{
var objectName = $(e.currentTarget).attr('data-chore-name');
var objectId = $(e.currentTarget).attr('data-chore-id');
@ -44,16 +44,16 @@ $(document).on('click', '.chore-delete-button', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.Api.Delete('objects/chores/' + objectId, {},
function (result)
function(result)
{
window.location.href = U('/chores');
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -63,7 +63,7 @@ $(document).on('click', '.chore-delete-button', function (e)
});
});
$("#show-disabled").change(function ()
$("#show-disabled").change(function()
{
if (this.checked)
{
@ -80,7 +80,7 @@ if (GetUriParam('include_disabled'))
$("#show-disabled").prop('checked', true);
}
$(".merge-chores-button").on("click", function (e)
$(".merge-chores-button").on("click", function(e)
{
var choreId = $(e.currentTarget).attr("data-chore-id");
$("#merge-chores-keep").val(choreId);
@ -88,7 +88,7 @@ $(".merge-chores-button").on("click", function (e)
$("#merge-chores-modal").modal("show");
});
$("#merge-chores-save-button").on("click", function (e)
$("#merge-chores-save-button").on("click", function(e)
{
e.preventDefault();
@ -101,11 +101,11 @@ $("#merge-chores-save-button").on("click", function (e)
var choreIdToRemove = $("#merge-chores-remove").val();
Grocy.Api.Post("chores/" + choreIdToKeep.toString() + "/merge/" + choreIdToRemove.toString(), {},
function (result)
function(result)
{
window.location.href = U('/chores');
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while merging', xhr.response);
}

View File

@ -9,7 +9,7 @@
$('#chores-journal-table tbody').removeClass("d-none");
choresJournalTable.columns.adjust().draw();
$("#chore-filter").on("change", function ()
$("#chore-filter").on("change", function()
{
var value = $(this).val();
if (value === "all")
@ -24,13 +24,13 @@ $("#chore-filter").on("change", function ()
window.location.reload();
});
$("#daterange-filter").on("change", function ()
$("#daterange-filter").on("change", function()
{
UpdateUriParam("months", $(this).val());
window.location.reload();
});
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -38,10 +38,10 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
choresJournalTable.search(value.accentNeutralise()).draw();
choresJournalTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
$("#daterange-filter").val("24");
@ -66,7 +66,7 @@ if (typeof GetUriParam("months") !== "undefined")
$("#daterange-filter").val(GetUriParam("months"));
}
$(document).on('click', '.undo-chore-execution-button', function (e)
$(document).on('click', '.undo-chore-execution-button', function(e)
{
e.preventDefault();
@ -74,7 +74,7 @@ $(document).on('click', '.undo-chore-execution-button', function (e)
var executionId = $(e.currentTarget).attr('data-execution-id');
Grocy.Api.Post('chores/executions/' + executionId.toString() + '/undo', {},
function (result)
function(result)
{
element.closest("tr").addClass("text-muted");
element.parent().siblings().find("span.name-anchor").addClass("text-strike-through").after("<br>" + __t("Undone on") + " " + moment().format("YYYY-MM-DD HH:mm:ss") + " <time class='timeago timeago-contextual' datetime='" + moment().format("YYYY-MM-DD HH:mm:ss") + "'></time>");
@ -82,7 +82,7 @@ $(document).on('click', '.undo-chore-execution-button', function (e)
RefreshContextualTimeago("#chore-execution-" + executionId + "-row");
toastr.success(__t("Chore execution successfully undone"));
},
function (xhr)
function(xhr)
{
console.error(xhr);
}

View File

@ -11,7 +11,7 @@
$('#chores-overview-table tbody').removeClass("d-none");
choresOverviewTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -19,10 +19,10 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
choresOverviewTable.search(value.accentNeutralise()).draw();
choresOverviewTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#status-filter").on("change", function ()
$("#status-filter").on("change", function()
{
var value = $(this).val();
if (value === "all")
@ -33,10 +33,10 @@ $("#status-filter").on("change", function ()
// Transfer CSS classes of selected element to dropdown element (for background)
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
choresOverviewTable.column(choresOverviewTable.colReorder.transpose(5)).search(value.accentNeutralise()).draw();
choresOverviewTable.column(choresOverviewTable.colReorder.transpose(5)).search(value).draw();
});
$("#user-filter").on("change", function ()
$("#user-filter").on("change", function()
{
var value = $(this).val();
if (value === "all")
@ -47,7 +47,7 @@ $("#user-filter").on("change", function ()
// Transfer CSS classes of selected element to dropdown element (for background)
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
choresOverviewTable.column(choresOverviewTable.colReorder.transpose(6)).search(value.accentNeutralise()).draw();
choresOverviewTable.column(choresOverviewTable.colReorder.transpose(6)).search(value).draw();
if (value)
{
@ -55,7 +55,7 @@ $("#user-filter").on("change", function ()
}
});
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
$("#status-filter").val("all");
@ -66,21 +66,21 @@ $("#clear-filter-button").on("click", function ()
RemoveUriParam("user");
});
$(".status-filter-message").on("click", function ()
$(".status-filter-message").on("click", function()
{
var value = $(this).data("status-filter");
$("#status-filter").val(value);
$("#status-filter").trigger("change");
});
$(".user-filter-message").on("click", function ()
$(".user-filter-message").on("click", function()
{
var value = $(this).data("user-filter");
$("#user-filter").val(value);
$("#user-filter").trigger("change");
});
$(document).on('click', '.track-chore-button', function (e)
$(document).on('click', '.track-chore-button', function(e)
{
e.preventDefault();
@ -92,7 +92,7 @@ $(document).on('click', '.track-chore-button', function (e)
var now = $(e.currentTarget).hasClass("now");
Grocy.Api.Get('chores/' + choreId,
function (choreDetails)
function(choreDetails)
{
var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss');
if ((skipped || !now) && choreDetails.next_estimated_execution_time != null)
@ -113,10 +113,10 @@ $(document).on('click', '.track-chore-button', function (e)
}
Grocy.Api.Post('chores/' + choreId + '/execute', { 'tracked_time': trackedTime, 'skipped': skipped },
function ()
function()
{
Grocy.Api.Get('chores/' + choreId,
function (result)
function(result)
{
var choreRow = $('#chore-' + choreId + '-row');
var nextXDaysThreshold = moment().add($("#info-due-soon-chores").data("next-x-days"), "days");
@ -177,7 +177,7 @@ $(document).on('click', '.track-chore-button', function (e)
RefreshStatistics();
// Delay due to delayed/animated set of new timestamps above
setTimeout(function ()
setTimeout(function()
{
RefreshContextualTimeago("#chore-" + choreId + "-row");
@ -186,21 +186,21 @@ $(document).on('click', '.track-chore-button', function (e)
$(".input-group-filter").trigger("change");
}, Grocy.FormFocusDelay);
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy("choretracking-form");
console.error(xhr);
@ -208,12 +208,12 @@ $(document).on('click', '.track-chore-button', function (e)
);
});
$(document).on('click', '.chore-grocycode-label-print', function (e)
$(document).on('click', '.chore-grocycode-label-print', function(e)
{
e.preventDefault();
var choreId = $(e.currentTarget).attr('data-chore-id');
Grocy.Api.Get('chores/' + choreId + '/printlabel', function (labelData)
Grocy.Api.Get('chores/' + choreId + '/printlabel', function(labelData)
{
if (Grocy.Webhooks.labelprinter !== undefined)
{
@ -226,7 +226,7 @@ function RefreshStatistics()
{
var nextXDays = $("#info-due-soon-chores").data("next-x-days");
Grocy.Api.Get('chores',
function (result)
function(result)
{
var dueTodayCount = 0;
var dueSoonCount = 0;
@ -265,20 +265,20 @@ function RefreshStatistics()
$("#info-overdue-chores").html('<span class="d-block d-md-none">' + overdueCount + ' <i class="fa-solid fa-times-circle"></i></span><span class="d-none d-md-block">' + __n(overdueCount, '%s chore is overdue to be done', '%s chores are overdue to be done'));
$("#info-assigned-to-me-chores").html('<span class="d-block d-md-none">' + assignedToMeCount + ' <i class="fa-solid fa-exclamation-circle"></i></span><span class="d-none d-md-block">' + __n(assignedToMeCount, '%s chore is assigned to me', '%s chores are assigned to me'));
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
);
}
$(document).on("click", ".reschedule-chore-button", function (e)
$(document).on("click", ".reschedule-chore-button", function(e)
{
e.preventDefault();
var choreId = $(e.currentTarget).attr("data-chore-id");
Grocy.EditObjectId = choreId;
Grocy.Api.Get("chores/" + choreId, function (choreDetails)
Grocy.Api.Get("chores/" + choreId, function(choreDetails)
{
var prefillDate = choreDetails.next_estimated_execution_time || moment().format("YYYY-MM-DD HH:mm:ss");
if (choreDetails.chore.rescheduled_date)
@ -316,7 +316,7 @@ $(document).on("click", ".reschedule-chore-button", function (e)
});
});
$("#reschedule-chore-save-button").on("click", function (e)
$("#reschedule-chore-save-button").on("click", function(e)
{
e.preventDefault();
@ -326,45 +326,45 @@ $("#reschedule-chore-save-button").on("click", function (e)
}
Grocy.Api.Put('objects/chores/' + Grocy.EditObjectId, { "rescheduled_date": Grocy.Components.DateTimePicker.GetValue(), "rescheduled_next_execution_assigned_to_user_id": Grocy.Components.UserPicker.GetValue() },
function (result)
function(result)
{
Grocy.Api.Post('chores/executions/calculate-next-assignments', { "chore_id": Grocy.EditObjectId },
function (result)
function(result)
{
window.location.reload();
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
);
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
}
);
});
$("#reschedule-chore-clear-button").on("click", function (e)
$("#reschedule-chore-clear-button").on("click", function(e)
{
e.preventDefault();
Grocy.Api.Put('objects/chores/' + Grocy.EditObjectId, { "rescheduled_date": null, "rescheduled_next_execution_assigned_to_user_id": null },
function (result)
function(result)
{
Grocy.Api.Post('chores/executions/calculate-next-assignments', { "chore_id": Grocy.EditObjectId },
function (result)
function(result)
{
window.location.reload();
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
);
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
}

View File

@ -1,4 +1,4 @@
$('#save-consume-button').on('click', function (e)
$('#save-consume-button').on('click', function(e)
{
e.preventDefault();
@ -40,10 +40,10 @@
var bookingResponse = null;
Grocy.Api.Get('stock/products/' + jsonForm.product_id,
function (productDetails)
function(productDetails)
{
Grocy.Api.Post(apiUrl, jsonData,
function (result)
function(result)
{
if (BoolVal(Grocy.UserSettings.scan_mode_consume_enabled))
{
@ -59,14 +59,14 @@
jsonDataBarcode.product_id = jsonForm.product_id;
Grocy.Api.Post('objects/product_barcodes', jsonDataBarcode,
function (result)
function(result)
{
$("#flow-info-InplaceAddBarcodeToExistingProduct").addClass("d-none");
$('#barcode-lookup-disabled-hint').addClass('d-none');
$('#barcode-lookup-hint').removeClass('d-none');
window.history.replaceState({}, document.title, U("/consume"));
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy("consume-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
@ -130,7 +130,7 @@
$("#consume-exact-amount-group").addClass("d-none");
}
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
Grocy.FrontendHelpers.EndUiBusy("consume-form");
@ -138,7 +138,7 @@
}
);
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy("consume-form");
console.error(xhr);
@ -146,7 +146,7 @@
);
});
$('#save-mark-as-open-button').on('click', function (e)
$('#save-mark-as-open-button').on('click', function(e)
{
e.preventDefault();
@ -168,7 +168,6 @@ $('#save-mark-as-open-button').on('click', function (e)
jsonData = {};
jsonData.amount = jsonForm.amount;
jsonData.allow_subproduct_substitution = true;
jsonData.location_id = jsonForm.location_id;
if ($("#use_specific_stock_entry").is(":checked"))
{
@ -176,10 +175,10 @@ $('#save-mark-as-open-button').on('click', function (e)
}
Grocy.Api.Get('stock/products/' + jsonForm.product_id,
function (productDetails)
function(productDetails)
{
Grocy.Api.Post(apiUrl, jsonData,
function (result)
function(result)
{
$("#specific_stock_entry").find("option").remove().end().append("<option></option>");
if ($("#use_specific_stock_entry").is(":checked"))
@ -207,17 +206,16 @@ $('#save-mark-as-open-button').on('click', function (e)
$(".input-group-productamountpicker").trigger("change");
Grocy.Components.ProductPicker.Clear();
Grocy.Components.ProductPicker.GetInputElement().focus();
Grocy.Components.ProductCard.Refresh(jsonForm.product_id);
Grocy.FrontendHelpers.ValidateForm('consume-form');
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy("consume-form");
console.error(xhr);
}
);
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy("consume-form");
console.error(xhr);
@ -225,8 +223,7 @@ $('#save-mark-as-open-button').on('click', function (e)
);
});
var sumValue = 0;
var sumValueOpened = 0;
$("#location_id").on('change', function (e)
$("#location_id").on('change', function(e)
{
var locationId = $(e.target).val();
$("#specific_stock_entry").find("option").remove().end().append("<option></option>");
@ -248,12 +245,12 @@ $("#location_id").on('change', function (e)
if (gc.length == 4)
{
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries?query[]=stock_id=' + gc[3],
function (stockEntries)
function(stockEntries)
{
OnLocationChange(stockEntries[0].location_id, gc[3]);
$('#display_amount').val(stockEntries[0].amount);
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -274,7 +271,6 @@ $("#location_id").on('change', function (e)
function OnLocationChange(locationId, stockId)
{
sumValue = 0;
sumValueOpened = 0;
if (locationId)
{
@ -284,7 +280,7 @@ function OnLocationChange(locationId, stockId)
}
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries?include_sub_products=true',
function (stockEntries)
function(stockEntries)
{
stockEntries.forEach(stockEntry =>
{
@ -311,11 +307,6 @@ function OnLocationChange(locationId, stockId)
sumValue = sumValue + (stockEntry.amount || 0);
if (BoolVal(stockEntry.open))
{
sumValueOpened = sumValueOpened + (stockEntry.amount || 0);
}
if (stockEntry.stock_id == stockId)
{
$("#use_specific_stock_entry").click();
@ -324,15 +315,13 @@ function OnLocationChange(locationId, stockId)
}
});
Grocy.Api.Get('stock/products/' + Grocy.Components.ProductPicker.GetValue(),
function (productDetails)
function(productDetails)
{
current_productDetails = productDetails;
RefreshForm();
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -343,7 +332,7 @@ function OnLocationChange(locationId, stockId)
ScanModeSubmit();
}
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -351,7 +340,7 @@ function OnLocationChange(locationId, stockId)
}
}
Grocy.Components.ProductPicker.GetPicker().on('change', function (e)
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{
if (BoolVal(Grocy.UserSettings.scan_mode_consume_enabled))
{
@ -372,7 +361,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function (e)
Grocy.Components.ProductCard.Refresh(productId);
Grocy.Api.Get('stock/products/' + productId,
function (productDetails)
function(productDetails)
{
current_productDetails = productDetails;
@ -397,7 +386,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function (e)
$("#location_id").find("option").remove().end().append("<option></option>");
Grocy.Api.Get("stock/products/" + productId + '/locations?include_sub_products=true',
function (stockLocations)
function(stockLocations)
{
var setDefault = 0;
var stockAmountAtDefaultLocation = 0;
@ -447,7 +436,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function (e)
if (document.getElementById("product_id").getAttribute("barcode") != "null")
{
Grocy.Api.Get('objects/product_barcodes?query[]=barcode=' + document.getElementById("product_id").getAttribute("barcode"),
function (barcodeResult)
function(barcodeResult)
{
if (barcodeResult != null)
{
@ -473,14 +462,14 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function (e)
}
}
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
);
}
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -500,12 +489,21 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function (e)
Grocy.Components.ProductPicker.HideCustomError();
Grocy.FrontendHelpers.ValidateForm('consume-form');
setTimeout(function ()
setTimeout(function()
{
$('#display_amount').focus();
}, Grocy.FormFocusDelay);
if (productDetails.stock_amount == productDetails.stock_amount_opened || productDetails.product.enable_tare_weight_handling == 1 || productDetails.product.disable_open == 1)
{
$("#save-mark-as-open-button").addClass("disabled");
}
else
{
$("#save-mark-as-open-button").removeClass("disabled");
}
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -517,44 +515,28 @@ $('#display_amount').val(Grocy.UserSettings.stock_default_consume_amount);
$(".input-group-productamountpicker").trigger("change");
Grocy.FrontendHelpers.ValidateForm('consume-form');
$('#display_amount').on('focus', function (e)
$('#display_amount').on('focus', function(e)
{
$(this).select();
});
$('#price').on('focus', function (e)
$('#price').on('focus', function(e)
{
$(this).select();
});
$('#consume-form input').keyup(function (event)
$('#consume-form input').keyup(function(event)
{
Grocy.FrontendHelpers.ValidateForm('consume-form');
});
$('#consume-form select').change(function (event)
$('#consume-form select').change(function(event)
{
Grocy.FrontendHelpers.ValidateForm('consume-form');
});
$("#display_amount").on("keyup", function (e)
{
if (!current_productDetails)
{
return;
}
if ($("#display_amount").val() > sumValue - sumValueOpened || current_productDetails.product.enable_tare_weight_handling == 1 || current_productDetails.product.disable_open == 1)
{
$("#save-mark-as-open-button").addClass("disabled");
}
else
{
$("#save-mark-as-open-button").removeClass("disabled");
}
});
$('#consume-form input').keydown(function (event)
$('#consume-form input').keydown(function(event)
{
if (event.keyCode === 13) // Enter
{
@ -571,45 +553,28 @@ $('#consume-form input').keydown(function (event)
}
});
$("#specific_stock_entry").on("change", function (e)
$("#specific_stock_entry").on("change", function(e)
{
if ($(e.target).val() == "")
{
sumValue = 0;
sumValueOpened = 0;
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries?include_sub_products=true',
function (stockEntries)
function(stockEntries)
{
stockEntries.forEach(stockEntry =>
{
if (stockEntry.location_id == $("#location_id").val() || stockEntry.location_id == "")
{
sumValue = sumValue + (stockEntry.amount || 0);
if (BoolVal(stockEntry.open))
{
sumValueOpened = sumValueOpened + (stockEntry.amount || 0);
}
sumValue = sumValue + stockEntry.amount_aggregated;
}
});
$("#display_amount").attr("max", sumValue.toFixed(Grocy.UserSettings.stock_decimal_places_amounts));
if (sumValue == 0)
{
$("#display_amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location'));
}
if (sumValue == sumValueOpened || current_productDetails.product.enable_tare_weight_handling == 1 || current_productDetails.product.disable_open == 1)
{
$("#save-mark-as-open-button").addClass("disabled");
}
else
{
$("#save-mark-as-open-button").removeClass("disabled");
}
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -621,7 +586,7 @@ $("#specific_stock_entry").on("change", function (e)
}
});
$("#use_specific_stock_entry").on("change", function ()
$("#use_specific_stock_entry").on("change", function()
{
var value = $(this).is(":checked");
@ -640,7 +605,7 @@ $("#use_specific_stock_entry").on("change", function ()
Grocy.FrontendHelpers.ValidateForm("consume-form");
});
$("#qu_id").on("change", function ()
$("#qu_id").on("change", function()
{
RefreshForm();
});
@ -648,11 +613,11 @@ $("#qu_id").on("change", function ()
function UndoStockBooking(bookingId)
{
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {},
function (result)
function(result)
{
toastr.success(__t("Booking successfully undone"));
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -662,11 +627,11 @@ function UndoStockBooking(bookingId)
function UndoStockTransaction(transactionId)
{
Grocy.Api.Post('stock/transactions/' + transactionId.toString() + '/undo', {},
function (result)
function(result)
{
toastr.success(__t("Transaction successfully undone"));
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -692,12 +657,12 @@ if (GetUriParam("embedded") !== undefined)
}
// Default input field
setTimeout(function ()
setTimeout(function()
{
Grocy.Components.ProductPicker.GetInputElement().focus();
}, Grocy.FormFocusDelay);
$(document).on("change", "#scan-mode", function (e)
$(document).on("change", "#scan-mode", function(e)
{
if ($(this).prop("checked"))
{
@ -705,7 +670,7 @@ $(document).on("change", "#scan-mode", function (e)
}
});
$("#scan-mode-button").on("click", function (e)
$("#scan-mode-button").on("click", function(e)
{
$("#scan-mode").click();
$("#scan-mode-button").toggleClass("btn-success").toggleClass("btn-danger");
@ -762,15 +727,6 @@ function RefreshForm()
$("#display_amount").removeAttr("max");
}
if (sumValue == sumValueOpened || productDetails.product.enable_tare_weight_handling == 1 || productDetails.product.disable_open == 1)
{
$("#save-mark-as-open-button").addClass("disabled");
}
else
{
$("#save-mark-as-open-button").removeClass("disabled");
}
Grocy.FrontendHelpers.ValidateForm("consume-form");
}

View File

@ -104,7 +104,7 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
equipmentTable.search(value.accentNeutralise()).draw();
equipmentTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()

View File

@ -8,7 +8,7 @@
$('#locations-table tbody').removeClass("d-none");
locationsTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -16,16 +16,16 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
locationsTable.search(value.accentNeutralise()).draw();
locationsTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
locationsTable.search("").draw();
});
$(document).on('click', '.location-delete-button', function (e)
$(document).on('click', '.location-delete-button', function(e)
{
var objectName = $(e.currentTarget).attr('data-location-name');
var objectId = $(e.currentTarget).attr('data-location-id');
@ -43,16 +43,16 @@ $(document).on('click', '.location-delete-button', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.Api.Delete('objects/locations/' + objectId, {},
function (result)
function(result)
{
window.location.href = U('/locations');
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -62,7 +62,7 @@ $(document).on('click', '.location-delete-button', function (e)
});
});
$("#show-disabled").change(function ()
$("#show-disabled").change(function()
{
if (this.checked)
{

View File

@ -13,6 +13,6 @@ $("#login-button").on("click", function (e)
{
e.preventDefault();
$("#password_base64").val(btoa(String.fromCharCode(...new TextEncoder().encode($("#password_input").val()))));
$("#password_base64").val(btoa($("#password_input").val()));
$("#login-form").trigger("submit");
});

View File

@ -8,7 +8,7 @@
$('#apikeys-table tbody').removeClass("d-none");
apiKeysTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -16,16 +16,16 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
apiKeysTable.search(value.accentNeutralise()).draw();
apiKeysTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
apiKeysTable.search("").draw();
});
$(document).on('click', '.apikey-delete-button', function (e)
$(document).on('click', '.apikey-delete-button', function(e)
{
var button = $(e.currentTarget);
var objectName = button.attr('data-apikey-key');
@ -51,16 +51,16 @@ $(document).on('click', '.apikey-delete-button', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.Api.Delete('objects/api_keys/' + objectId, {},
function (result)
function(result)
{
window.location.href = U('/manageapikeys');
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -70,7 +70,7 @@ $(document).on('click', '.apikey-delete-button', function (e)
});
});
$(".apikey-show-qr-button").on("click", function ()
$(".apikey-show-qr-button").on("click", function()
{
var button = $(this);
var apiKey = button.data("apikey-key");
@ -89,20 +89,20 @@ $(".apikey-show-qr-button").on("click", function ()
});
});
$("#add-api-key-button").on("click", function (e)
$("#add-api-key-button").on("click", function(e)
{
$("#add-api-key-modal").modal("show");
});
$("#add-api-key-modal").on("shown.bs.modal", function (e)
$("#add-api-key-modal").on("shown.bs.modal", function(e)
{
setTimeout(function ()
setTimeout(function()
{
$("#description").focus();
}, Grocy.FormFocusDelay);
});
$("#new-api-key-button").on("click", function (e)
$("#new-api-key-button").on("click", function(e)
{
window.location.href = U("/manageapikeys/new?description=" + encodeURIComponent($("#description").val()));
});

View File

@ -16,7 +16,7 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
sessionsTable.search(value.accentNeutralise()).draw();
sessionsTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()

View File

@ -8,7 +8,7 @@
$('#mealplansections-table tbody').removeClass("d-none");
mealplanSectionsTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -16,16 +16,16 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
mealplanSectionsTable.search(value.accentNeutralise()).draw();
mealplanSectionsTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
mealplanSectionsTable.search("").draw();
});
$(document).on('click', '.mealplansection-delete-button', function (e)
$(document).on('click', '.mealplansection-delete-button', function(e)
{
var objectName = $(e.currentTarget).attr('data-mealplansection-name');
var objectId = $(e.currentTarget).attr('data-mealplansection-id');
@ -43,16 +43,16 @@ $(document).on('click', '.mealplansection-delete-button', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.Api.Delete('objects/meal_plan_sections/' + objectId, {},
function (result)
function(result)
{
window.location.href = U('/mealplansections');
},
function (xhr)
function(xhr)
{
console.error(xhr);
}

View File

@ -392,7 +392,86 @@ $(window).on("message", function (e)
}
});
if (Grocy.EditMode === 'create')
if (Grocy.EditMode == "create" && GetUriParam("copy-of") != undefined)
{
Grocy.Api.Get('objects/products/' + GetUriParam("copy-of"),
function (sourceProduct)
{
if (sourceProduct.parent_product_id != null)
{
Grocy.Components.ProductPicker.SetId(sourceProduct.parent_product_id);
}
if (sourceProduct.description)
{
$("#description").summernote("pasteHTML", sourceProduct.description);
}
$("#location_id").val(sourceProduct.location_id);
if (sourceProduct.shopping_location_id != null)
{
Grocy.Components.ShoppingLocationPicker.SetId(sourceProduct.shopping_location_id);
}
$("#min_stock_amount").val(sourceProduct.min_stock_amount);
if (BoolVal(sourceProduct.cumulate_min_stock_amount_of_sub_products))
{
$("#cumulate_min_stock_amount_of_sub_products").prop("checked", true);
}
$("#default_best_before_days").val(sourceProduct.default_best_before_days);
$("#default_best_before_days_after_open").val(sourceProduct.default_best_before_days_after_open);
if (sourceProduct.product_group_id != null)
{
$("#product_group_id").val(sourceProduct.product_group_id);
}
$("#qu_id_stock").val(sourceProduct.qu_id_stock);
$("#qu_id_purchase").val(sourceProduct.qu_id_purchase);
if (BoolVal(sourceProduct.enable_tare_weight_handling))
{
$("#enable_tare_weight_handling").prop("checked", true);
}
$("#tare_weight").val(sourceProduct.tare_weight);
if (BoolVal(sourceProduct.not_check_stock_fulfillment_for_recipes))
{
$("#not_check_stock_fulfillment_for_recipes").prop("checked", true);
}
if (sourceProduct.calories != null)
{
$("#calories").val(sourceProduct.calories);
}
$("#default_best_before_days_after_freezing").val(sourceProduct.default_best_before_days_after_freezing);
$("#default_best_before_days_after_thawing").val(sourceProduct.default_best_before_days_after_thawing);
$("#quick_consume_amount").val(sourceProduct.quick_consume_amount);
$("#quick_open_amount").val(sourceProduct.quick_open_amount);
$("#default_consume_location_id").val(sourceProduct.default_consume_location_id);
if (BoolVal(sourceProduct.no_own_stock))
{
$("#no_own_stock").prop("checked", true);
}
if (BoolVal(sourceProduct.hide_on_stock_overview))
{
$("#hide_on_stock_overview").prop("checked", true);
}
if (BoolVal(sourceProduct.auto_reprint_stock_label))
{
$("#auto_reprint_stock_label").prop("checked", true);
}
$("#default_stock_label_type").val(sourceProduct.default_stock_label_type);
if (BoolVal(sourceProduct.move_on_open))
{
$("#move_on_open").prop("checked", true);
}
if (BoolVal(sourceProduct.treat_opened_as_out_of_stock))
{
$("#treat_opened_as_out_of_stock").prop("checked", true);
}
Grocy.FrontendHelpers.ValidateForm('product-form');
},
function (xhr)
{
console.error(xhr);
}
);
}
else if (Grocy.EditMode === 'create')
{
if (Grocy.UserSettings.product_presets_location_id.toString() !== '-1')
{

View File

@ -8,7 +8,7 @@
$('#productgroups-table tbody').removeClass("d-none");
groupsTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -16,16 +16,16 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
groupsTable.search(value.accentNeutralise()).draw();
groupsTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
groupsTable.search("").draw();
});
$(document).on('click', '.product-group-delete-button', function (e)
$(document).on('click', '.product-group-delete-button', function(e)
{
var objectName = $(e.currentTarget).attr('data-group-name');
var objectId = $(e.currentTarget).attr('data-group-id');
@ -43,16 +43,16 @@ $(document).on('click', '.product-group-delete-button', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.Api.Delete('objects/product_groups/' + objectId, {},
function (result)
function(result)
{
window.location.href = U('/productgroups');
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -61,7 +61,7 @@ $(document).on('click', '.product-group-delete-button', function (e)
}
});
});
$(window).on("message", function (e)
$(window).on("message", function(e)
{
var data = e.originalEvent.data;
@ -71,7 +71,7 @@ $(window).on("message", function (e)
}
});
$("#show-disabled").change(function ()
$("#show-disabled").change(function()
{
if (this.checked)
{

View File

@ -20,7 +20,7 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
productsTable.search(value.accentNeutralise()).draw();
productsTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#product-group-filter").on("change", function ()
@ -32,7 +32,7 @@ $("#product-group-filter").on("change", function ()
}
else
{
productsTable.column(productsTable.colReorder.transpose(6)).search("^" + $.fn.dataTable.util.escapeRegex(value.accentNeutralise()) + "$", true, false).draw();
productsTable.column(productsTable.colReorder.transpose(6)).search("^" + $.fn.dataTable.util.escapeRegex(value) + "$", true, false).draw();
}
});
@ -175,21 +175,3 @@ $("#merge-products-save-button").on("click", function (e)
}
);
});
$(".product-copy-button").on('click', function (e)
{
e.preventDefault();
var objectId = $(e.currentTarget).attr("data-product-id");
Grocy.Api.Post("stock/products/" + objectId.toString() + "/copy", {},
function (result)
{
window.location.href = U("/product/" + result.created_object_id.toString());
},
function (xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
}
);
});

View File

@ -8,7 +8,7 @@
$('#qu-conversions-resolved-table tbody').removeClass("d-none");
quConversionsResolvedTable.columns.adjust().draw();
$("#quantity-unit-filter").on("change", function ()
$("#quantity-unit-filter").on("change", function()
{
var value = $("#quantity-unit-filter option:selected").text();
if (value === __t("All"))
@ -16,10 +16,10 @@ $("#quantity-unit-filter").on("change", function ()
value = "";
}
quConversionsResolvedTable.column([quConversionsResolvedTable.colReorder.transpose(1), quConversionsResolvedTable.colReorder.transpose(2)]).search(value.accentNeutralise()).draw();
quConversionsResolvedTable.column([quConversionsResolvedTable.colReorder.transpose(1), quConversionsResolvedTable.colReorder.transpose(2)]).search(value).draw();
});
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#quantity-unit-filter").val("all");
quConversionsResolvedTable.column([quConversionsResolvedTable.colReorder.transpose(1), quConversionsResolvedTable.colReorder.transpose(2)]).search("").draw();

View File

@ -8,7 +8,7 @@
$('#quantityunits-table tbody').removeClass("d-none");
quantityUnitsTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -16,16 +16,16 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
quantityUnitsTable.search(value.accentNeutralise()).draw();
quantityUnitsTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
quantityUnitsTable.search("").draw();
});
$(document).on('click', '.quantityunit-delete-button', function (e)
$(document).on('click', '.quantityunit-delete-button', function(e)
{
var objectName = $(e.currentTarget).attr('data-quantityunit-name');
var objectId = $(e.currentTarget).attr('data-quantityunit-id');
@ -43,16 +43,16 @@ $(document).on('click', '.quantityunit-delete-button', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.Api.Delete('objects/quantity_units/' + objectId, {},
function (result)
function(result)
{
window.location.href = U('/quantityunits');
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -62,7 +62,7 @@ $(document).on('click', '.quantityunit-delete-button', function (e)
});
});
$("#show-disabled").change(function ()
$("#show-disabled").change(function()
{
if (this.checked)
{

View File

@ -11,7 +11,7 @@
style: 'single',
selector: 'tr td:not(:first-child)'
},
'initComplete': function ()
'initComplete': function()
{
this.api().row({ order: 'current' }, 0).select();
}
@ -44,7 +44,7 @@ if (typeof recipe !== "undefined")
if (GetUriParam("search") !== undefined)
{
$("#search").val(GetUriParam("search"));
setTimeout(function ()
setTimeout(function()
{
$("#search").keyup();
}, 50);
@ -53,23 +53,23 @@ if (GetUriParam("search") !== undefined)
if (GetUriParam("status") !== undefined)
{
$("#status-filter").val(GetUriParam("status"));
setTimeout(function ()
setTimeout(function()
{
$("#status-filter").trigger("change");
}, 50);
}
$("a[data-toggle='tab']").on("shown.bs.tab", function (e)
$("a[data-toggle='tab']").on("shown.bs.tab", function(e)
{
var tabId = $(e.target).attr("id");
window.localStorage.setItem("recipes_last_tab_id", tabId);
});
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
recipesTables.search(value.accentNeutralise()).draw();
recipesTables.search(value).draw();
if (!value)
{
@ -84,7 +84,7 @@ $("#search").on("keyup", Delay(function ()
$(".recipe-gallery-item .card-title-search:not(:contains_case_insensitive(" + value + "))").parent().parent().parent().addClass("d-none");
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
$("#status-filter").val("all");
@ -92,7 +92,7 @@ $("#clear-filter-button").on("click", function ()
$("#status-filter").trigger("change");
});
$("#status-filter").on("change", function ()
$("#status-filter").on("change", function()
{
var value = $(this).val();
if (value === "all")
@ -100,7 +100,7 @@ $("#status-filter").on("change", function ()
value = "";
}
recipesTables.column(recipesTables.colReorder.transpose(6)).search(value.accentNeutralise()).draw();
recipesTables.column(recipesTables.colReorder.transpose(6)).search(value).draw();
$('.recipe-gallery-item').removeClass('d-none');
if (value !== "")
@ -129,7 +129,7 @@ $("#status-filter").on("change", function ()
}
});
$(".recipe-delete").on('click', function (e)
$(".recipe-delete").on('click', function(e)
{
e.preventDefault();
@ -149,16 +149,16 @@ $(".recipe-delete").on('click', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.Api.Delete('objects/recipes/' + objectId, {},
function (result)
function(result)
{
window.location.href = U('/recipes');
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -168,25 +168,25 @@ $(".recipe-delete").on('click', function (e)
});
});
$(".recipe-copy").on('click', function (e)
$(".recipe-copy").on('click', function(e)
{
e.preventDefault();
var objectId = $(e.currentTarget).attr('data-recipe-id');
Grocy.Api.Post("recipes/" + objectId.toString() + "/copy", {},
function (result)
function(result)
{
window.location.href = U('/recipes?recipe=' + result.created_object_id.toString());
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
}
);
});
$(document).on('click', '.recipe-shopping-list', function (e)
$(document).on('click', '.recipe-shopping-list', function(e)
{
var objectName = $(e.currentTarget).attr('data-recipe-name');
var objectId = $(e.currentTarget).attr('data-recipe-id');
@ -204,24 +204,24 @@ $(document).on('click', '.recipe-shopping-list', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.FrontendHelpers.BeginUiBusy();
var excludedProductIds = new Array();
$(".missing-recipe-pos-product-checkbox:checkbox:not(:checked)").each(function ()
$(".missing-recipe-pos-product-checkbox:checkbox:not(:checked)").each(function()
{
excludedProductIds.push($(this).data("product-id"));
});
Grocy.Api.Post('recipes/' + objectId + '/add-not-fulfilled-products-to-shoppinglist', { "excludedProductIds": excludedProductIds },
function (result)
function(result)
{
window.location.reload();
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
@ -232,7 +232,7 @@ $(document).on('click', '.recipe-shopping-list', function (e)
});
});
$(".recipe-consume").on('click', function (e)
$(".recipe-consume").on('click', function(e)
{
var objectName = $(e.currentTarget).attr('data-recipe-name');
var objectId = $(e.currentTarget).attr('data-recipe-id');
@ -251,19 +251,19 @@ $(".recipe-consume").on('click', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.FrontendHelpers.BeginUiBusy();
Grocy.Api.Post('recipes/' + objectId + '/consume', {},
function (result)
function(result)
{
Grocy.FrontendHelpers.EndUiBusy();
toastr.success(__t('Removed all in stock ingredients needed by recipe \"%s\" from stock', objectName));
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy();
Grocy.FrontendHelpers.ShowGenericError("A server error occured while processing your request", xhr.response);
@ -274,7 +274,7 @@ $(".recipe-consume").on('click', function (e)
});
});
recipesTables.on('select', function (e, dt, type, indexes)
recipesTables.on('select', function(e, dt, type, indexes)
{
if (type === 'row')
{
@ -302,7 +302,7 @@ recipesTables.on('select', function (e, dt, type, indexes)
cancel: {
label: __t('Close'),
className: 'btn-secondary responsive-button',
callback: function ()
callback: function()
{
$(".modal").last().modal("hide");
}
@ -313,7 +313,7 @@ recipesTables.on('select', function (e, dt, type, indexes)
}
});
$(".recipe-gallery-item").on("click", function (e)
$(".recipe-gallery-item").on("click", function(e)
{
e.preventDefault();
@ -336,7 +336,7 @@ $(".recipe-gallery-item").on("click", function (e)
cancel: {
label: __t('Close'),
className: 'btn-secondary responsive-button',
callback: function ()
callback: function()
{
$(".modal").last().modal("hide");
}
@ -346,12 +346,12 @@ $(".recipe-gallery-item").on("click", function (e)
}
});
$(".recipe-edit-button").on("click", function (e)
$(".recipe-edit-button").on("click", function(e)
{
e.stopPropagation();
});
$(".recipe-fullscreen").on('click', function (e)
$(".recipe-fullscreen").on('click', function(e)
{
e.preventDefault();
@ -374,7 +374,7 @@ $(".recipe-fullscreen").on('click', function (e)
}
});
$(".recipe-print").on('click', function (e)
$(".recipe-print").on('click', function(e)
{
e.preventDefault();
@ -387,24 +387,24 @@ $(".recipe-print").on('click', function (e)
window.print();
});
$('#servings-scale').keyup(function (event)
$('#servings-scale').keyup(function(event)
{
var data = {};
data.desired_servings = $(this).val();
Grocy.Api.Put('objects/recipes/' + $(this).data("recipe-id"), data,
function (result)
function(result)
{
window.location.reload();
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
);
});
$(document).on("click", ".missing-recipe-pos-select-button", function (e)
$(document).on("click", ".missing-recipe-pos-select-button", function(e)
{
e.preventDefault();
@ -414,7 +414,7 @@ $(document).on("click", ".missing-recipe-pos-select-button", function (e)
$(this).toggleClass("list-group-item-primary");
});
$(document).on("click", ".missing-recipe-pos-product-checkbox", function (e)
$(document).on("click", ".missing-recipe-pos-product-checkbox", function(e)
{
e.stopPropagation();
@ -427,12 +427,12 @@ if (window.location.hash === "#fullscreen")
$("#selectedRecipeToggleFullscreenButton").click();
}
$(document).on('click', '.recipe-grocycode-label-print', function (e)
$(document).on('click', '.recipe-grocycode-label-print', function(e)
{
e.preventDefault();
var recipeId = $(e.currentTarget).attr('data-recipe-id');
Grocy.Api.Get('recipes/' + recipeId + '/printlabel', function (labelData)
Grocy.Api.Get('recipes/' + recipeId + '/printlabel', function(labelData)
{
if (Grocy.Webhooks.labelprinter !== undefined)
{
@ -441,14 +441,14 @@ $(document).on('click', '.recipe-grocycode-label-print', function (e)
});
});
$(document).on('click', '.ingredient-done-button', function (e)
$(document).on('click', '.ingredient-done-button', function(e)
{
e.preventDefault();
$(e.currentTarget).parent().toggleClass("text-strike-through").toggleClass("text-muted");
});
$(document).on("click", ".add-to-mealplan-button", function (e)
$(document).on("click", ".add-to-mealplan-button", function(e)
{
Grocy.Components.DateTimePicker.Init(true);
Grocy.Components.DateTimePicker.SetValue(moment().format("YYYY-MM-DD"));
@ -461,7 +461,7 @@ $(document).on("click", ".add-to-mealplan-button", function (e)
$("#recipe_servings").focus();
});
$('#save-add-to-mealplan-button').on('click', function (e)
$('#save-add-to-mealplan-button').on('click', function(e)
{
e.preventDefault();
@ -474,19 +474,19 @@ $('#save-add-to-mealplan-button').on('click', function (e)
formData.day = Grocy.Components.DateTimePicker.GetValue();
Grocy.Api.Post('objects/meal_plan', formData,
function (result)
function(result)
{
toastr.success(__t("Successfully added the recipe to the meal plan"));
$("#add-to-mealplan-modal").modal("hide");
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
}
);
});
$('#add-to-mealplan-form input').keydown(function (event)
$('#add-to-mealplan-form input').keydown(function(event)
{
if (event.keyCode === 13) // Enter
{

View File

@ -43,7 +43,7 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
shoppingListTable.search(value.accentNeutralise()).draw();
shoppingListTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
@ -65,7 +65,7 @@ $("#status-filter").on("change", function ()
// Transfer CSS classes of selected element to dropdown element (for background)
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
shoppingListTable.column(shoppingListTable.colReorder.transpose(4)).search(value.accentNeutralise()).draw();
shoppingListTable.column(shoppingListTable.colReorder.transpose(4)).search(value).draw();
});
$("#selected-shopping-list").on("change", function ()

View File

@ -8,7 +8,7 @@ var locationsTable = $('#shoppinglocations-table').DataTable({
$('#shoppinglocations-table tbody').removeClass("d-none");
locationsTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -16,16 +16,16 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
locationsTable.search(value.accentNeutralise()).draw();
locationsTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
locationsTable.search("").draw();
});
$(document).on('click', '.shoppinglocation-delete-button', function (e)
$(document).on('click', '.shoppinglocation-delete-button', function(e)
{
var objectName = $(e.currentTarget).attr('data-shoppinglocation-name');
var objectId = $(e.currentTarget).attr('data-shoppinglocation-id');
@ -43,16 +43,16 @@ $(document).on('click', '.shoppinglocation-delete-button', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.Api.Delete('objects/shopping_locations/' + objectId, {},
function (result)
function(result)
{
window.location.href = U('/shoppinglocations');
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -62,7 +62,7 @@ $(document).on('click', '.shoppinglocation-delete-button', function (e)
});
});
$("#show-disabled").change(function ()
$("#show-disabled").change(function()
{
if (this.checked)
{

View File

@ -15,7 +15,7 @@
$('#stockentries-table tbody').removeClass("d-none");
stockEntriesTable.columns.adjust().draw();
$.fn.dataTable.ext.search.push(function (settings, data, dataIndex)
$.fn.dataTable.ext.search.push(function(settings, data, dataIndex)
{
var productId = Grocy.Components.ProductPicker.GetValue();
@ -27,7 +27,7 @@ $.fn.dataTable.ext.search.push(function (settings, data, dataIndex)
return false;
});
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#location-filter").val("all");
$("#location-filter").trigger("change");
@ -40,7 +40,7 @@ $("#clear-filter-button").on("click", function ()
stockEntriesTable.draw();
});
$("#location-filter").on("change", function ()
$("#location-filter").on("change", function()
{
var value = $(this).val();
var text = $("#location-filter option:selected").text();
@ -49,20 +49,20 @@ $("#location-filter").on("change", function ()
text = "";
}
stockEntriesTable.column(stockEntriesTable.colReorder.transpose(5)).search(text.accentNeutralise()).draw();
stockEntriesTable.column(stockEntriesTable.colReorder.transpose(5)).search(text).draw();
});
Grocy.Components.ProductPicker.GetPicker().on('change', function (e)
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{
stockEntriesTable.draw();
});
Grocy.Components.ProductPicker.GetInputElement().on('keyup', function (e)
Grocy.Components.ProductPicker.GetInputElement().on('keyup', function(e)
{
stockEntriesTable.draw();
});
$(document).on('click', '.stock-consume-button', function (e)
$(document).on('click', '.stock-consume-button', function(e)
{
e.preventDefault();
@ -77,10 +77,10 @@ $(document).on('click', '.stock-consume-button', function (e)
var wasSpoiled = $(e.currentTarget).hasClass("stock-consume-button-spoiled");
Grocy.Api.Post('stock/products/' + productId + '/consume', { 'amount': consumeAmount, 'spoiled': wasSpoiled, 'location_id': locationId, 'stock_entry_id': specificStockEntryId, 'exact_amount': true },
function (bookingResponse)
function(bookingResponse)
{
Grocy.Api.Get('stock/products/' + productId,
function (result)
function(result)
{
var toastMessage = __t('Removed %1$s of %2$s from stock', consumeAmount.toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + __n(consumeAmount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural, true), result.product.name);
if (wasSpoiled)
@ -94,14 +94,14 @@ $(document).on('click', '.stock-consume-button', function (e)
toastr.success(toastMessage);
Grocy.GetTopmostWindow().postMessage(WindowMessageBag("BroadcastMessage", WindowMessageBag("ProductChanged", productId)), Grocy.BaseUrl);
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
@ -109,7 +109,7 @@ $(document).on('click', '.stock-consume-button', function (e)
);
});
$(document).on('click', '.product-open-button', function (e)
$(document).on('click', '.product-open-button', function(e)
{
e.preventDefault();
@ -122,10 +122,10 @@ $(document).on('click', '.product-open-button', function (e)
var button = $(e.currentTarget);
Grocy.Api.Post('stock/products/' + productId + '/open', { 'amount': openAmount, 'stock_entry_id': specificStockEntryId },
function (bookingResponse)
function(bookingResponse)
{
Grocy.Api.Get('stock/products/' + productId,
function (result)
function(result)
{
button.addClass("disabled");
Grocy.FrontendHelpers.EndUiBusy();
@ -139,14 +139,14 @@ $(document).on('click', '.product-open-button', function (e)
RefreshStockEntryRow(stockRowId);
Grocy.GetTopmostWindow().postMessage(WindowMessageBag("BroadcastMessage", WindowMessageBag("ProductChanged", productId)), Grocy.BaseUrl);
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
@ -154,12 +154,12 @@ $(document).on('click', '.product-open-button', function (e)
);
});
$(document).on('click', '.stockentry-grocycode-label-print', function (e)
$(document).on('click', '.stockentry-grocycode-label-print', function(e)
{
e.preventDefault();
var stockId = $(e.currentTarget).attr('data-stock-id');
Grocy.Api.Get('stock/entry/' + stockId + '/printlabel', function (labelData)
Grocy.Api.Get('stock/entry/' + stockId + '/printlabel', function(labelData)
{
if (Grocy.Webhooks.labelprinter !== undefined)
{
@ -171,7 +171,7 @@ $(document).on('click', '.stockentry-grocycode-label-print', function (e)
function RefreshStockEntryRow(stockRowId)
{
Grocy.Api.Get("stock/entry/" + stockRowId,
function (result)
function(result)
{
var stockRow = $('#stock-' + stockRowId + '-row');
@ -183,7 +183,7 @@ function RefreshStockEntryRow(stockRowId)
if (result == null || result.amount == 0)
{
animateCSS("#stock-" + stockRowId + "-row", "fadeOut", function ()
animateCSS("#stock-" + stockRowId + "-row", "fadeOut", function()
{
$("#stock-" + stockRowId + "-row").addClass("d-none");
});
@ -225,21 +225,21 @@ function RefreshStockEntryRow(stockRowId)
var locationName = "";
Grocy.Api.Get("objects/locations/" + result.location_id,
function (locationResult)
function(locationResult)
{
locationName = locationResult.name;
$('#stock-' + stockRowId + '-location').attr('data-location-id', result.location_id);
$('#stock-' + stockRowId + '-location').text(locationName);
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
);
Grocy.Api.Get("stock/products/" + result.product_id,
function (productDetails)
function(productDetails)
{
if (!result.price)
{
@ -254,7 +254,7 @@ function RefreshStockEntryRow(stockRowId)
$(".product-open-button[data-stockrow-id='" + stockRowId + "']").addClass("disabled");
}
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -268,14 +268,14 @@ function RefreshStockEntryRow(stockRowId)
{
var shoppingLocationName = "";
Grocy.Api.Get("objects/shopping_locations/" + result.shopping_location_id,
function (shoppingLocationResult)
function(shoppingLocationResult)
{
shoppingLocationName = shoppingLocationResult.name;
$('#stock-' + stockRowId + '-shopping-location').attr('data-shopping-location-id', result.location_id);
$('#stock-' + stockRowId + '-shopping-location').text(shoppingLocationName);
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -298,13 +298,13 @@ function RefreshStockEntryRow(stockRowId)
}
// Needs to be delayed because of the animation above the date-text would be wrong if fired immediately...
setTimeout(function ()
setTimeout(function()
{
RefreshContextualTimeago("#stock-" + stockRowId + "-row");
RefreshLocaleNumberDisplay("#stock-" + stockRowId + "-row");
}, Grocy.FormFocusDelay);
},
function (xhr)
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
@ -312,13 +312,13 @@ function RefreshStockEntryRow(stockRowId)
);
}
$(window).on("message", function (e)
$(window).on("message", function(e)
{
var data = e.originalEvent.data;
if (data.Message == "ProductChanged")
{
$(".stock-consume-button[data-product-id='" + data.Payload + "']").each(function ()
$(".stock-consume-button[data-product-id='" + data.Payload + "']").each(function()
{
RefreshStockEntryRow($(this).attr("data-stockrow-id"));
});
@ -330,12 +330,12 @@ Grocy.Components.ProductPicker.GetPicker().trigger('change');
function UndoStockBookingEntry(bookingId, stockRowId, productId)
{
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {},
function (result)
function(result)
{
Grocy.GetTopmostWindow().postMessage(WindowMessageBag("BroadcastMessage", WindowMessageBag("ProductChanged", productId)), Grocy.BaseUrl);
toastr.success(__t("Booking successfully undone"));
},
function (xhr)
function(xhr)
{
console.error(xhr);
}

View File

@ -8,7 +8,7 @@
$('#stock-journal-table tbody').removeClass("d-none");
stockJournalTable.columns.adjust().draw();
$("#product-filter").on("change", function ()
$("#product-filter").on("change", function()
{
var value = $(this).val();
if (value === "all")
@ -23,7 +23,7 @@ $("#product-filter").on("change", function ()
window.location.reload();
});
$("#transaction-type-filter").on("change", function ()
$("#transaction-type-filter").on("change", function()
{
var value = $(this).val();
var text = $("#transaction-type-filter option:selected").text();
@ -32,10 +32,10 @@ $("#transaction-type-filter").on("change", function ()
text = "";
}
stockJournalTable.column(stockJournalTable.colReorder.transpose(4)).search(text.accentNeutralise()).draw();
stockJournalTable.column(stockJournalTable.colReorder.transpose(4)).search(text).draw();
});
$("#location-filter").on("change", function ()
$("#location-filter").on("change", function()
{
var value = $(this).val();
var text = $("#location-filter option:selected").text();
@ -44,10 +44,10 @@ $("#location-filter").on("change", function ()
text = "";
}
stockJournalTable.column(stockJournalTable.colReorder.transpose(5)).search(text.accentNeutralise()).draw();
stockJournalTable.column(stockJournalTable.colReorder.transpose(5)).search(text).draw();
});
$("#user-filter").on("change", function ()
$("#user-filter").on("change", function()
{
var value = $(this).val();
var text = $("#user-filter option:selected").text();
@ -56,16 +56,16 @@ $("#user-filter").on("change", function ()
text = "";
}
stockJournalTable.column(stockJournalTable.colReorder.transpose(6)).search(text.accentNeutralise()).draw();
stockJournalTable.column(stockJournalTable.colReorder.transpose(6)).search(text).draw();
});
$("#daterange-filter").on("change", function ()
$("#daterange-filter").on("change", function()
{
UpdateUriParam("months", $(this).val());
window.location.reload();
});
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -73,10 +73,10 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
stockJournalTable.search(value.accentNeutralise()).draw();
stockJournalTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
$("#transaction-type-filter").val("all");
@ -104,7 +104,7 @@ if (typeof GetUriParam("months") !== "undefined")
$("#daterange-filter").val(GetUriParam("months"));
}
$(document).on('click', '.undo-stock-booking-button', function (e)
$(document).on('click', '.undo-stock-booking-button', function(e)
{
e.preventDefault();
@ -118,7 +118,7 @@ $(document).on('click', '.undo-stock-booking-button', function (e)
}
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {},
function (result)
function(result)
{
correspondingBookingsRoot.addClass("text-muted");
correspondingBookingsRoot.find("span.name-anchor").addClass("text-strike-through").after("<br>" + __t("Undone on") + " " + moment().format("YYYY-MM-DD HH:mm:ss") + " <time class='timeago timeago-contextual' datetime='" + moment().format("YYYY-MM-DD HH:mm:ss") + "'></time>");
@ -126,7 +126,7 @@ $(document).on('click', '.undo-stock-booking-button', function (e)
RefreshContextualTimeago("#stock-booking-" + bookingId + "-row");
toastr.success(__t("Booking successfully undone"));
},
function (xhr)
function(xhr)
{
console.error(xhr);
toastr.error(__t(JSON.parse(xhr.response).error_message));
@ -134,12 +134,12 @@ $(document).on('click', '.undo-stock-booking-button', function (e)
);
});
$(document).on('click', '.product-grocycode-label-print', function (e)
$(document).on('click', '.product-grocycode-label-print', function(e)
{
e.preventDefault();
var productId = $(e.currentTarget).attr('data-product-id');
Grocy.Api.Get('stock/products/' + productId + '/printlabel', function (labelData)
Grocy.Api.Get('stock/products/' + productId + '/printlabel', function(labelData)
{
if (Grocy.Webhooks.labelprinter !== undefined)
{

View File

@ -8,7 +8,7 @@ var journalSummaryTable = $('#stock-journal-summary-table').DataTable({
$('#stock-journal-summary-table tbody').removeClass("d-none");
journalSummaryTable.columns.adjust().draw();
$("#product-filter").on("change", function ()
$("#product-filter").on("change", function()
{
var value = $(this).val();
var text = $("#product-filter option:selected").text();
@ -18,11 +18,11 @@ $("#product-filter").on("change", function ()
}
else
{
journalSummaryTable.column(journalSummaryTable.colReorder.transpose(1)).search("^" + $.fn.dataTable.util.escapeRegex(text.accentNeutralise()) + "$", true, false).draw();
journalSummaryTable.column(journalSummaryTable.colReorder.transpose(1)).search("^" + $.fn.dataTable.util.escapeRegex(text) + "$", true, false).draw();
}
});
$("#transaction-type-filter").on("change", function ()
$("#transaction-type-filter").on("change", function()
{
var value = $(this).val();
var text = $("#transaction-type-filter option:selected").text();
@ -31,10 +31,10 @@ $("#transaction-type-filter").on("change", function ()
text = "";
}
journalSummaryTable.column(journalSummaryTable.colReorder.transpose(2)).search(text.accentNeutralise()).draw();
journalSummaryTable.column(journalSummaryTable.colReorder.transpose(2)).search(text).draw();
});
$("#user-filter").on("change", function ()
$("#user-filter").on("change", function()
{
var value = $(this).val();
var text = $("#user-filter option:selected").text();
@ -43,10 +43,10 @@ $("#user-filter").on("change", function ()
text = "";
}
journalSummaryTable.column(journalSummaryTable.colReorder.transpose(3)).search(text.accentNeutralise()).draw();
journalSummaryTable.column(journalSummaryTable.colReorder.transpose(3)).search(text).draw();
});
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -54,10 +54,10 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
journalSummaryTable.search(value.accentNeutralise()).draw();
journalSummaryTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
$("#transaction-type-filter").val("all");

View File

@ -49,7 +49,7 @@ $("#location-filter").on("change", function ()
value = "xx" + value + "xx";
}
stockOverviewTable.column(stockOverviewTable.colReorder.transpose(6)).search(value.accentNeutralise()).draw();
stockOverviewTable.column(stockOverviewTable.colReorder.transpose(6)).search(value).draw();
});
$("#product-group-filter").on("change", function ()
@ -64,7 +64,7 @@ $("#product-group-filter").on("change", function ()
value = "xx" + value + "xx";
}
stockOverviewTable.column(stockOverviewTable.colReorder.transpose(8)).search(value.accentNeutralise()).draw();
stockOverviewTable.column(stockOverviewTable.colReorder.transpose(8)).search(value).draw();
});
$("#status-filter").on("change", function ()
@ -78,7 +78,7 @@ $("#status-filter").on("change", function ()
// Transfer CSS classes of selected element to dropdown element (for background)
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
stockOverviewTable.column(stockOverviewTable.colReorder.transpose(7)).search(value.accentNeutralise()).draw();
stockOverviewTable.column(stockOverviewTable.colReorder.transpose(7)).search(value).draw();
});
$(".status-filter-message").on("click", function ()
@ -108,7 +108,7 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
stockOverviewTable.search(value.accentNeutralise()).draw();
stockOverviewTable.search(value).draw();
}, Grocy.FormFocusDelay));
$(document).on('click', '.product-grocycode-label-print', function (e)

View File

@ -8,7 +8,7 @@
$('#taskcategories-table tbody').removeClass("d-none");
categoriesTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -16,16 +16,16 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
categoriesTable.search(value.accentNeutralise()).draw();
categoriesTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
categoriesTable.search("").draw();
});
$(document).on('click', '.task-category-delete-button', function (e)
$(document).on('click', '.task-category-delete-button', function(e)
{
var objectName = $(e.currentTarget).attr('data-category-name');
var objectId = $(e.currentTarget).attr('data-category-id');
@ -43,16 +43,16 @@ $(document).on('click', '.task-category-delete-button', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.Api.Delete('objects/task_categories/' + objectId, {},
function (result)
function(result)
{
window.location.href = U('/taskcategories');
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -62,7 +62,7 @@ $(document).on('click', '.task-category-delete-button', function (e)
});
});
$("#show-disabled").change(function ()
$("#show-disabled").change(function()
{
if (this.checked)
{

View File

@ -17,7 +17,7 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
tasksTable.search(value.accentNeutralise()).draw();
tasksTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#status-filter").on("change", function ()
@ -31,7 +31,7 @@ $("#status-filter").on("change", function ()
// Transfer CSS classes of selected element to dropdown element (for background)
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
tasksTable.column(tasksTable.colReorder.transpose(5)).search(value.accentNeutralise()).draw();
tasksTable.column(tasksTable.colReorder.transpose(5)).search(value).draw();
});
$("#user-filter").on("change", function ()
@ -46,7 +46,7 @@ $("#user-filter").on("change", function ()
"^" + $.fn.dataTable.util.escapeRegex(value) + "$"
}
tasksTable.column(tasksTable.colReorder.transpose(4)).search(value.accentNeutralise(), true, false).draw();
tasksTable.column(tasksTable.colReorder.transpose(4)).search(value, true, false).draw();
});
$("#category-filter").on("change", function ()
@ -57,7 +57,7 @@ $("#category-filter").on("change", function ()
value = "";
}
tasksTable.column(tasksTable.colReorder.transpose(3)).search(value.accentNeutralise()).draw();
tasksTable.column(tasksTable.colReorder.transpose(3)).search(value).draw();
});
$("#clear-filter-button").on("click", function ()

View File

@ -8,7 +8,7 @@
$('#userentities-table tbody').removeClass("d-none");
userentitiesTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -16,16 +16,16 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
userentitiesTable.search(value.accentNeutralise()).draw();
userentitiesTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
userentitiesTable.search("").draw();
});
$(document).on('click', '.userentity-delete-button', function (e)
$(document).on('click', '.userentity-delete-button', function(e)
{
var objectName = $(e.currentTarget).attr('data-userentity-name');
var objectId = $(e.currentTarget).attr('data-userentity-id');
@ -43,16 +43,16 @@ $(document).on('click', '.userentity-delete-button', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.Api.Delete('objects/userentities/' + objectId, {},
function (result)
function(result)
{
window.location.href = U('/userentities');
},
function (xhr)
function(xhr)
{
console.error(xhr);
}

View File

@ -8,7 +8,7 @@
$('#userfields-table tbody').removeClass("d-none");
userfieldsTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -16,10 +16,10 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
userfieldsTable.search(value.accentNeutralise()).draw();
userfieldsTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#entity-filter").on("change", function ()
$("#entity-filter").on("change", function()
{
var value = $("#entity-filter option:selected").text();
if (value === __t("All"))
@ -27,11 +27,11 @@ $("#entity-filter").on("change", function ()
value = "";
}
userfieldsTable.column(userfieldsTable.colReorder.transpose(1)).search(value.accentNeutralise()).draw();
userfieldsTable.column(userfieldsTable.colReorder.transpose(1)).search(value).draw();
$("#new-userfield-button").attr("href", U("/userfield/new?embedded&entity=" + value));
});
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
$("#entity-filter").val("all");
@ -39,7 +39,7 @@ $("#clear-filter-button").on("click", function ()
userfieldsTable.search("").draw();
});
$(document).on('click', '.userfield-delete-button', function (e)
$(document).on('click', '.userfield-delete-button', function(e)
{
var objectName = $(e.currentTarget).attr('data-userfield-name');
var objectId = $(e.currentTarget).attr('data-userfield-id');
@ -57,16 +57,16 @@ $(document).on('click', '.userfield-delete-button', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.Api.Delete('objects/userfields/' + objectId, {},
function (result)
function(result)
{
window.location.href = U('/userfields');
},
function (xhr)
function(xhr)
{
console.error(xhr);
}
@ -80,7 +80,7 @@ if (GetUriParam("entity"))
{
$("#entity-filter").val(GetUriParam("entity"));
$("#entity-filter").trigger("change");
setTimeout(function ()
setTimeout(function()
{
$("#name").focus();
}, Grocy.FormFocusDelay);

View File

@ -46,7 +46,7 @@ $('#save-user-button').on('click', function (e)
jsonData.picture_file_name = RandomString() + CleanFileName($("#user-picture")[0].files[0].name);
}
jsonData.password_base64 = btoa(String.fromCharCode(...new TextEncoder().encode(jsonData.password)));
jsonData.password_base64 = btoa(jsonData.password);
delete jsonData.password;
delete jsonData.password_confirm;
delete jsonData.change_password;

View File

@ -8,7 +8,7 @@
$('.userobjects-table tbody').removeClass("d-none");
userobjectsTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -16,16 +16,16 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
userobjectsTable.search(value.accentNeutralise()).draw();
userobjectsTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
userobjectsTable.search("").draw();
});
$(document).on('click', '.userobject-delete-button', function (e)
$(document).on('click', '.userobject-delete-button', function(e)
{
var objectId = $(e.currentTarget).attr('data-userobject-id');
@ -42,16 +42,16 @@ $(document).on('click', '.userobject-delete-button', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.Api.Delete('objects/userobjects/' + objectId, {},
function (result)
function(result)
{
window.location.reload();
},
function (xhr)
function(xhr)
{
console.error(xhr);
}

View File

@ -8,7 +8,7 @@
$('#users-table tbody').removeClass("d-none");
usersTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function ()
$("#search").on("keyup", Delay(function()
{
var value = $(this).val();
if (value === "all")
@ -16,16 +16,16 @@ $("#search").on("keyup", Delay(function ()
value = "";
}
usersTable.search(value.accentNeutralise()).draw();
usersTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function ()
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
usersTable.search("").draw();
});
$(document).on('click', '.user-delete-button', function (e)
$(document).on('click', '.user-delete-button', function(e)
{
var objectName = $(e.currentTarget).attr('data-user-username');
var objectId = $(e.currentTarget).attr('data-user-id');
@ -43,16 +43,16 @@ $(document).on('click', '.user-delete-button', function (e)
className: 'btn-danger'
}
},
callback: function (result)
callback: function(result)
{
if (result === true)
{
Grocy.Api.Delete('users/' + objectId, {},
function (result)
function(result)
{
window.location.href = U('/users');
},
function (xhr)
function(xhr)
{
console.error(xhr);
}

View File

@ -207,7 +207,6 @@ $app->group('/api', function (RouteCollectorProxy $group)
$group->post('/stock/products/{productId}/inventory', [StockApiController::class, 'InventoryProduct']);
$group->post('/stock/products/{productId}/open', [StockApiController::class, 'OpenProduct']);
$group->post('/stock/products/{productIdToKeep}/merge/{productIdToRemove}', [StockApiController::class, 'MergeProducts']);
$group->post('/stock/products/{productId}/copy', [StockApiController::class, 'CopyProduct']);
$group->get('/stock/products/by-barcode/{barcode}', [StockApiController::class, 'ProductDetailsByBarcode']);
$group->post('/stock/products/by-barcode/{barcode}/add', [StockApiController::class, 'AddProductByBarcode']);
$group->post('/stock/products/by-barcode/{barcode}/consume', [StockApiController::class, 'ConsumeProductByBarcode']);

View File

@ -36,25 +36,20 @@ class DatabaseService
}
}
if ($params === null)
if ($params == null)
{
if ($pdo->exec($sql) === false)
{
throw new \Exception(implode(' | ', $pdo->errorInfo()));
throw new \Exception($pdo->errorInfo());
}
}
else
{
$cmd = $pdo->prepare($sql);
if ($cmd === false)
{
throw new \Exception(implode(' | ', $pdo->errorInfo()));
}
if ($cmd->execute($params) === false)
{
throw new \Exception(implode(' | ', $cmd->errorInfo()));
throw new \Exception($pdo->errorInfo());
}
}

View File

@ -400,24 +400,26 @@ class StockService extends BaseService
if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION)
{
$potentialStockEntries = $this->GetProductStockEntries($productId, $locationId, false, $allowSubproductSubstitution);
if ($locationId === null)
{
// Consume from any location
$potentialStockEntries = $this->GetProductStockEntries($productId, false, $allowSubproductSubstitution);
}
else
{
// Consume only from the supplied location
$potentialStockEntries = $this->GetProductStockEntriesForLocation($productId, $locationId, false, $allowSubproductSubstitution);
}
if ($specificStockEntryId !== 'default')
{
$potentialStockEntries = FindAllObjectsInArrayByPropertyValue($potentialStockEntries, 'stock_id', $specificStockEntryId);
}
if ($locationId === null)
$productStockAmount = $productDetails->stock_amount_aggregated;
if (round($amount, 2) > round($productStockAmount, 2))
{
$productStockAmountTotal = $this->DB->stock()->where('product_id = :1', $productId)->sum('amount');
}
else
{
$productStockAmountTotal = $this->DB->stock()->where('product_id = :1 AND location_id = :2', $productId, $locationId)->sum('amount');
}
if ($amount > $productStockAmountTotal)
{
throw new \Exception('Amount to be consumed cannot be > current unopened stock amount (if supplied, at the desired location)');
throw new \Exception('Amount to be consumed cannot be > current stock amount (if supplied, at the desired location)');
}
if ($transactionId === null)
@ -863,7 +865,7 @@ class StockService extends BaseService
return $returnData;
}
public function GetProductStockEntries(int $productId, $locationId = null, $excludeOpened = false, $allowSubproductSubstitution = false)
public function GetProductStockEntries(int $productId, $excludeOpened = false, $allowSubproductSubstitution = false)
{
$sqlWhereProductId = 'product_id = ' . $productId;
if ($allowSubproductSubstitution)
@ -877,13 +879,7 @@ class StockService extends BaseService
$sqlWhereAndOpen = 'AND open = 0';
}
$sqlWhereAndLocationId = '';
if ($locationId !== null)
{
$sqlWhereAndLocationId = 'AND location_id = ' . $locationId;
}
return $this->DB->stock_next_use()->where($sqlWhereProductId . ' ' . $sqlWhereAndOpen . ' ' . $sqlWhereAndLocationId);
return $this->DB->stock_next_use()->where($sqlWhereProductId . ' ' . $sqlWhereAndOpen);
}
public function GetLocationStockEntries($locationId)
@ -896,6 +892,12 @@ class StockService extends BaseService
return $this->DB->stock()->where('location_id', $locationId);
}
public function GetProductStockEntriesForLocation($productId, $locationId, $excludeOpened = false, $allowSubproductSubstitution = false)
{
$stockEntries = $this->GetProductStockEntries($productId, $excludeOpened, $allowSubproductSubstitution);
return FindAllObjectsInArrayByPropertyValue($stockEntries, 'location_id', $locationId);
}
public function GetProductStockLocations(int $productId, $allowSubproductSubstitution = false)
{
$sqlWhereProductId = 'product_id = ' . $productId;
@ -976,7 +978,7 @@ class StockService extends BaseService
return null;
}
public function OpenProduct(int $productId, float $amount, $locationId = null, $specificStockEntryId = 'default', &$transactionId = null, $allowSubproductSubstitution = false)
public function OpenProduct(int $productId, float $amount, $specificStockEntryId = 'default', &$transactionId = null, $allowSubproductSubstitution = false)
{
if (!$this->ProductExists($productId))
{
@ -991,24 +993,17 @@ class StockService extends BaseService
}
$productDetails = (object)$this->GetProductDetails($productId);
$potentialStockEntries = $this->GetProductStockEntries($productId, $locationId, true, $allowSubproductSubstitution);
$productStockAmountUnopened = $productDetails->stock_amount_aggregated - $productDetails->stock_amount_opened_aggregated;
$potentialStockEntries = $this->GetProductStockEntries($productId, true, $allowSubproductSubstitution);
if ($product->enable_tare_weight_handling == 1)
{
throw new \Exception('Opening tare weight handling enabled products is not supported');
}
if ($locationId === null)
{
$productStockAmountUnopened = $this->DB->stock()->where('product_id = :1 AND open = 0', $productId)->sum('amount');
}
else
{
$productStockAmountUnopened = $this->DB->stock()->where('product_id = :1 AND open = 0 AND location_id = :2', $productId, $locationId)->sum('amount');
}
if ($amount > $productStockAmountUnopened)
{
throw new \Exception('Amount to be opened cannot be > current unopened stock amount (if supplied, at the desired location)');
throw new \Exception('Amount to be opened cannot be > current unopened stock amount');
}
if ($specificStockEntryId !== 'default')
@ -1295,10 +1290,16 @@ class StockService extends BaseService
{
// Hard fail for now, as we not yet support transferring tare weight enabled products
throw new \Exception('Transferring tare weight enabled products is not yet possible');
if ($amount < $productDetails->product->tare_weight)
{
throw new \Exception('The amount cannot be lower than the defined tare weight');
}
$amount = abs($amount - $productDetails->stock_amount - $productDetails->product->tare_weight);
}
$productStockAmountAtFromLocation = $this->DB->stock()->where('product_id = :1 AND location_id = :2', $productId, $locationIdFrom)->sum('amount');
$potentialStockEntriesAtFromLocation = $this->GetProductStockEntries($productId, $locationIdFrom);
$potentialStockEntriesAtFromLocation = $this->GetProductStockEntriesForLocation($productId, $locationIdFrom);
if ($amount > $productStockAmountAtFromLocation)
{
@ -1729,54 +1730,6 @@ class StockService extends BaseService
DatabaseService::GetInstance()->GetDbConnectionRaw()->commit();
}
public function CopyProduct(int $productId)
{
if (!$this->ProductExists($productId))
{
throw new \Exception('Product does not exist');
}
$newName = LocalizationService::GetInstance()->__t('Copy of %s', $this->DB->products($productId)->name);
DatabaseService::GetInstance()->ExecuteDbStatement(
'INSERT INTO products
(name, description, product_group_id, active, location_id, shopping_location_id, qu_id_purchase, qu_id_stock, min_stock_amount,
default_best_before_days, default_best_before_days_after_open, default_best_before_days_after_freezing,
default_best_before_days_after_thawing, picture_file_name, enable_tare_weight_handling, tare_weight, not_check_stock_fulfillment_for_recipes,
parent_product_id, calories, cumulate_min_stock_amount_of_sub_products, due_type, quick_consume_amount, hide_on_stock_overview, default_stock_label_type,
should_not_be_frozen, treat_opened_as_out_of_stock, no_own_stock, default_consume_location_id, move_on_open, row_created_timestamp, qu_id_consume,
auto_reprint_stock_label, quick_open_amount, qu_id_price, disable_open, default_purchase_price_type)
SELECT :new_name, description, product_group_id, active, location_id, shopping_location_id, qu_id_purchase, qu_id_stock, min_stock_amount,
default_best_before_days, default_best_before_days_after_open, default_best_before_days_after_freezing,
default_best_before_days_after_thawing, picture_file_name, enable_tare_weight_handling, tare_weight, not_check_stock_fulfillment_for_recipes,
parent_product_id, calories, cumulate_min_stock_amount_of_sub_products, due_type, quick_consume_amount, hide_on_stock_overview, default_stock_label_type,
should_not_be_frozen, treat_opened_as_out_of_stock, no_own_stock, default_consume_location_id, move_on_open, row_created_timestamp, qu_id_consume,
auto_reprint_stock_label, quick_open_amount, qu_id_price, disable_open, default_purchase_price_type
FROM products
WHERE id = :product_id',
['product_id' => $productId, 'new_name' => $newName]
);
$lastInsertId = $this->DB->lastInsertId();
DatabaseService::GetInstance()->ExecuteDbStatement(
'INSERT INTO quantity_unit_conversions
(product_id, from_qu_id, to_qu_id, factor)
SELECT :last_insert_id, from_qu_id, to_qu_id, factor
FROM quantity_unit_conversions q
WHERE q.product_id = :product_id
AND q.id = (
SELECT MIN(q2.id)
FROM quantity_unit_conversions q2
WHERE q2.product_id = q.product_id
AND MIN(q2.from_qu_id, q2.to_qu_id) = MIN(q.from_qu_id, q.to_qu_id)
AND MAX(q2.from_qu_id, q2.to_qu_id) = MAX(q.from_qu_id, q.to_qu_id)
)', // The corresponding inverse conversion is automatically created by a trigger
['product_id' => $productId, 'last_insert_id' => $lastInsertId]
);
return $lastInsertId;
}
public function CompactStockEntries($productId = null)
{
if ($productId == null)

View File

@ -1,4 +1,4 @@
{
"Version": "4.7.1",
"ReleaseDate": "2026-09-04"
"Version": "4.7.0",
"ReleaseDate": "2026-08-28"
}

View File

@ -160,10 +160,9 @@
<i class="fa-solid fa-ellipsis-v"></i>
</button>
<div class="table-inline-menu dropdown-menu dropdown-menu-right">
<a class="dropdown-item product-copy-button"
data-product-id="{{ $product->id }}"
<a class="dropdown-item"
type="button"
href="#">
href="{{ $U('/product/new?copy-of=') }}{{ $product->id }}">
<span class="dropdown-item-text">{{ $__t('Copy') }}</span>
</a>
<a class="dropdown-item merge-products-button"

View File

@ -190,7 +190,7 @@
type="button"
href="#"
data-recipe-id="{{ $recipe->id }}">
<span class="dropdown-item-text">{{ $__t('Copy') }}</span>
<span class="dropdown-item-text">{{ $__t('Copy recipe') }}</span>
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item"

View File

@ -275,9 +275,9 @@ summernote@^0.9.1:
integrity sha512-5Hfuey6+N0XIbk8ZpkGEVDmrkRVRHZWKBhw/072i9/TJLaWUKboB+KOyyd6AzDP2CQs1O6or4zRTU8HY30kt4w==
swagger-ui-dist@^5.2.0:
version "5.32.15"
resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.32.15.tgz#0bbaa62695104dbf2db06ad9bbbaaa127d830614"
integrity sha512-TSFER+rFQlf1nzk6WvKkMaHTxAPQ3eAAxigFThnxQedSREanfZgSbJFayZVs/ULnSbNdrJOb99vLD6xpb3R3eg==
version "5.32.14"
resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.32.14.tgz#bb3a3e738bf17bc2ee2640a711fcc44be76fde4c"
integrity sha512-nOA2pSQhcmODMUQZpJHYKNuwniDUqcOWGNaSCOoZv12FdOSJ9JxV95HtyRGNMqEBj6h6lCNTy20TgZDYTSuUIg==
dependencies:
"@scarf/scarf" "=1.4.0"