Compare commits

...

16 Commits

Author SHA1 Message Date
Bernd Bestel
41206cb901
Fixed changelog file name 2026-09-16 22:14:31 +02:00
Bernd Bestel
80f68d4e93
Removed obsolete code comment 2026-09-16 21:32:02 +02:00
Bernd Bestel
f1e92931c9
Support location_id in product open API endpoints (closes #2989) 2026-09-16 21:27:36 +02:00
Bernd Bestel
7d15c46bbd
Prepared next release 2026-09-04 17:51:18 +02:00
Bernd Bestel
41f150fc95
Updated dependencies 2026-09-04 17:48:16 +02:00
Bernd Bestel
e6a1c11775
Pulled translations from Transifex 2026-09-04 17:47:39 +02:00
Bernd Bestel
da8be4d73f
Fixed CopyProduct did not copy product specific QU conversions (references #2789) 2026-09-04 10:45:04 +02:00
Bernd Bestel
1d5730523d
Optimized viewcache clear handling for dev mode 2026-09-04 10:44:32 +02:00
Bernd Bestel
10b7b446cf
Optimized ExecuteDbStatement error handling 2026-09-04 10:44:01 +02:00
Bernd Bestel
3c8edb515c
New API endpoint for copying a product (fixes #2789) 2026-09-03 20:55:40 +02:00
Bernd Bestel
85dc601b37
Return HTTP 500 for configuration validation problems (closes #2983) 2026-09-03 19:50:39 +02:00
Bernd Bestel
e83699b346
Fix API route detection in ExceptionController (references #2977) 2026-09-03 19:49:23 +02:00
Bernd Bestel
b6ee94341a
Apply accentNeutralise() to all DataTable search inputs (fixes #2982) 2026-09-03 16:59:18 +02:00
Bernd Bestel
0cf4d557af
UTF-8 encode Base64 in-transit passwords (fixes #2981, references #2892) 2026-09-02 23:52:31 +02:00
Bernd Bestel
020841117f
Make API route detection independent of real URL path (fixes #2977) 2026-08-30 11:11:37 +02:00
Bernd Bestel
d9889fd157
Fixed special purpose API Keys authentication (fixes #2976) 2026-08-29 20:09:17 +02:00
86 changed files with 753 additions and 649 deletions

View File

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

View File

@ -0,0 +1,17 @@
### 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

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

View File

@ -555,6 +555,12 @@ class StockApiController extends BaseApiController
throw new \Exception('An amount is required'); 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'; $specificStockEntryId = 'default';
if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id'])) if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id']))
{ {
@ -568,7 +574,7 @@ class StockApiController extends BaseApiController
} }
$transactionId = null; $transactionId = null;
$transactionId = StockService::GetInstance()->OpenProduct($args['productId'], $requestBody['amount'], $specificStockEntryId, $transactionId, $allowSubproductSubstitution); $transactionId = StockService::GetInstance()->OpenProduct($args['productId'], $requestBody['amount'], $locationId, $specificStockEntryId, $transactionId, $allowSubproductSubstitution);
$args['transactionId'] = $transactionId; $args['transactionId'] = $transactionId;
return $this->StockTransactions($request, $response, $args); return $this->StockTransactions($request, $response, $args);
} }
@ -648,7 +654,7 @@ class StockApiController extends BaseApiController
$allowSubproductSubstitution = true; $allowSubproductSubstitution = true;
} }
return $this->FilteredApiResponse($response, StockService::GetInstance()->GetProductStockEntries($args['productId'], false, $allowSubproductSubstitution), $request->getQueryParams()); return $this->FilteredApiResponse($response, StockService::GetInstance()->GetProductStockEntries($args['productId'], null, false, $allowSubproductSubstitution), $request->getQueryParams());
} }
public function LocationStockEntries(Request $request, Response $response, array $args) public function LocationStockEntries(Request $request, Response $response, array $args)
@ -926,4 +932,25 @@ class StockApiController extends BaseApiController
return $this->GenericErrorResponse($response, $ex->getMessage()); 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(); $response = $this->ResponseFactory->createResponse();
$isApiRoute = string_starts_with($request->getUri()->getPath(), '/api/'); $isApiRoute = str_contains($request->getUri()->getPath(), '/api/');
if (!defined('GROCY_AUTHENTICATED')) if (!defined('GROCY_AUTHENTICATED'))
{ {

View File

@ -2255,6 +2255,10 @@
"allow_subproduct_substitution": { "allow_subproduct_substitution": {
"type": "boolean", "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": "`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": { "example": {
@ -2333,6 +2337,53 @@
} }
} }
}, },
"/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}": { "/stock/products/{productIdToKeep}/merge/{productIdToRemove}": {
"post": { "post": {
"summary": "Merges two products into one", "summary": "Merges two products into one",
@ -2552,7 +2603,7 @@
}, },
"allow_subproduct_substitution": { "allow_subproduct_substitution": {
"type": "boolean", "type": "boolean",
"description": "`rue` when any in stock sub product should be used when the given product is a parent product and currently not in stock" "description": "`true` when any in stock sub product should be used when the given product is a parent product and currently not in stock"
} }
}, },
"example": { "example": {
@ -2775,7 +2826,11 @@
}, },
"allow_subproduct_substitution": { "allow_subproduct_substitution": {
"type": "boolean", "type": "boolean",
"description": "`rue` when any in stock sub product should be used when the given product is a parent product and currently not in stock" "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": { "example": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
# 鳥山 陽平 <kagemomiji@gmail.com>, 2020 # 鳥山 陽平 <kagemomiji@gmail.com>, 2020
# Cisco Benjamin <metallica@vivaldi.net>, 2020 # Cisco Benjamin <metallica@vivaldi.net>, 2020
# Takuya Mizuguchi, 2024 # Takuya Mizuguchi, 2024
# pigmentblue15, 2025 # pigmentblue15, 2026
# #
msgid "" msgid ""
msgstr "" msgstr ""
@ -11,7 +11,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n" "POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
"PO-Revision-Date: 2019-05-01 17:42+0000\n" "PO-Revision-Date: 2019-05-01 17:42+0000\n"
"Last-Translator: pigmentblue15, 2025\n" "Last-Translator: pigmentblue15, 2026\n"
"Language-Team: Japanese (https://app.transifex.com/grocy/teams/93189/ja/)\n" "Language-Team: Japanese (https://app.transifex.com/grocy/teams/93189/ja/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -76,7 +76,7 @@ msgid "Cheese"
msgstr "チーズ" msgstr "チーズ"
msgid "Cold cuts" msgid "Cold cuts"
msgstr "コールドカット" msgstr "スライスハム"
msgid "Paprika" msgid "Paprika"
msgstr "パプリカ" msgstr "パプリカ"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@
$('#batteries-journal-table tbody').removeClass("d-none"); $('#batteries-journal-table tbody').removeClass("d-none");
batteriesJournalTable.columns.adjust().draw(); batteriesJournalTable.columns.adjust().draw();
$("#battery-filter").on("change", function() $("#battery-filter").on("change", function ()
{ {
var value = $(this).val(); var value = $(this).val();
if (value === "all") if (value === "all")
@ -23,7 +23,7 @@ $("#battery-filter").on("change", function()
window.location.reload(); window.location.reload();
}); });
$("#search").on("keyup", Delay(function() $("#search").on("keyup", Delay(function ()
{ {
var value = $(this).val(); var value = $(this).val();
if (value === "all") if (value === "all")
@ -31,10 +31,10 @@ $("#search").on("keyup", Delay(function()
value = ""; value = "";
} }
batteriesJournalTable.search(value).draw(); batteriesJournalTable.search(value.accentNeutralise()).draw();
}, Grocy.FormFocusDelay)); }, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function() $("#clear-filter-button").on("click", function ()
{ {
$("#search").val(""); $("#search").val("");
$("#battery-filter").val("all"); $("#battery-filter").val("all");
@ -45,7 +45,7 @@ $("#clear-filter-button").on("click", function()
window.location.reload(); window.location.reload();
}); });
$("#daterange-filter").on("change", function() $("#daterange-filter").on("change", function ()
{ {
UpdateUriParam("months", $(this).val()); UpdateUriParam("months", $(this).val());
window.location.reload(); window.location.reload();
@ -61,7 +61,7 @@ if (typeof GetUriParam("months") !== "undefined")
$("#daterange-filter").val(GetUriParam("months")); $("#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(); e.preventDefault();
@ -69,7 +69,7 @@ $(document).on('click', '.undo-battery-execution-button', function(e)
var chargeCycleId = $(e.currentTarget).attr('data-charge-cycle-id'); var chargeCycleId = $(e.currentTarget).attr('data-charge-cycle-id');
Grocy.Api.Post('batteries/charge-cycles/' + chargeCycleId.toString() + '/undo', {}, Grocy.Api.Post('batteries/charge-cycles/' + chargeCycleId.toString() + '/undo', {},
function(result) function (result)
{ {
element.closest("tr").addClass("text-muted"); 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>"); 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"); RefreshContextualTimeago("#charge-cycle-" + chargeCycleId + "-row");
toastr.success(__t("Charge cycle successfully undone")); toastr.success(__t("Charge cycle successfully undone"));
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }

View File

@ -10,7 +10,7 @@
$('#batteries-overview-table tbody').removeClass("d-none"); $('#batteries-overview-table tbody').removeClass("d-none");
batteriesOverviewTable.columns.adjust().draw(); batteriesOverviewTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function() $("#search").on("keyup", Delay(function ()
{ {
var value = $(this).val(); var value = $(this).val();
if (value === "all") if (value === "all")
@ -18,10 +18,10 @@ $("#search").on("keyup", Delay(function()
value = ""; value = "";
} }
batteriesOverviewTable.search(value).draw(); batteriesOverviewTable.search(value.accentNeutralise()).draw();
}, Grocy.FormFocusDelay)); }, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function() $("#clear-filter-button").on("click", function ()
{ {
$("#search").val(""); $("#search").val("");
$("#status-filter").val("all"); $("#status-filter").val("all");
@ -29,7 +29,7 @@ $("#clear-filter-button").on("click", function()
batteriesOverviewTable.search("").draw(); batteriesOverviewTable.search("").draw();
}); });
$("#status-filter").on("change", function() $("#status-filter").on("change", function ()
{ {
var value = $(this).val(); var value = $(this).val();
if (value === "all") if (value === "all")
@ -40,17 +40,17 @@ $("#status-filter").on("change", function()
// Transfer CSS classes of selected element to dropdown element (for background) // Transfer CSS classes of selected element to dropdown element (for background)
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control"); $(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
batteriesOverviewTable.column(batteriesOverviewTable.colReorder.transpose(5)).search(value).draw(); batteriesOverviewTable.column(batteriesOverviewTable.colReorder.transpose(5)).search(value.accentNeutralise()).draw();
}); });
$(".status-filter-message").on("click", function() $(".status-filter-message").on("click", function ()
{ {
var value = $(this).data("status-filter"); var value = $(this).data("status-filter");
$("#status-filter").val(value); $("#status-filter").val(value);
$("#status-filter").trigger("change"); $("#status-filter").trigger("change");
}); });
$(document).on('click', '.track-charge-cycle-button', function(e) $(document).on('click', '.track-charge-cycle-button', function (e)
{ {
e.preventDefault(); 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'); var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss');
Grocy.Api.Post('batteries/' + batteryId + '/charge', { 'tracked_time': trackedTime }, Grocy.Api.Post('batteries/' + batteryId + '/charge', { 'tracked_time': trackedTime },
function() function ()
{ {
Grocy.Api.Get('batteries/' + batteryId, Grocy.Api.Get('batteries/' + batteryId,
function(result) function (result)
{ {
var batteryRow = $('#battery-' + batteryId + '-row'); var batteryRow = $('#battery-' + batteryId + '-row');
var nextXDaysThreshold = moment().add($("#info-due-soon-batteries").data("next-x-days"), "days"); 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"); RefreshContextualTimeago("#battery-" + batteryId + "-row");
RefreshStatistics(); RefreshStatistics();
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy(); Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr); console.error(xhr);
} }
); );
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy(); Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr); 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(); e.preventDefault();
var batteryId = $(e.currentTarget).attr('data-battery-id'); 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) if (Grocy.Webhooks.labelprinter !== undefined)
{ {
@ -130,7 +130,7 @@ function RefreshStatistics()
{ {
var nextXDays = $("#info-due-soon-batteries").data("next-x-days"); var nextXDays = $("#info-due-soon-batteries").data("next-x-days");
Grocy.Api.Get('batteries', Grocy.Api.Get('batteries',
function(result) function (result)
{ {
var dueTodayCount = 0; var dueTodayCount = 0;
var dueSoonCount = 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-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')); $("#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); console.error(xhr);
} }

View File

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

View File

@ -9,7 +9,7 @@
$('#chores-journal-table tbody').removeClass("d-none"); $('#chores-journal-table tbody').removeClass("d-none");
choresJournalTable.columns.adjust().draw(); choresJournalTable.columns.adjust().draw();
$("#chore-filter").on("change", function() $("#chore-filter").on("change", function ()
{ {
var value = $(this).val(); var value = $(this).val();
if (value === "all") if (value === "all")
@ -24,13 +24,13 @@ $("#chore-filter").on("change", function()
window.location.reload(); window.location.reload();
}); });
$("#daterange-filter").on("change", function() $("#daterange-filter").on("change", function ()
{ {
UpdateUriParam("months", $(this).val()); UpdateUriParam("months", $(this).val());
window.location.reload(); window.location.reload();
}); });
$("#search").on("keyup", Delay(function() $("#search").on("keyup", Delay(function ()
{ {
var value = $(this).val(); var value = $(this).val();
if (value === "all") if (value === "all")
@ -38,10 +38,10 @@ $("#search").on("keyup", Delay(function()
value = ""; value = "";
} }
choresJournalTable.search(value).draw(); choresJournalTable.search(value.accentNeutralise()).draw();
}, Grocy.FormFocusDelay)); }, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function() $("#clear-filter-button").on("click", function ()
{ {
$("#search").val(""); $("#search").val("");
$("#daterange-filter").val("24"); $("#daterange-filter").val("24");
@ -66,7 +66,7 @@ if (typeof GetUriParam("months") !== "undefined")
$("#daterange-filter").val(GetUriParam("months")); $("#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(); e.preventDefault();
@ -74,7 +74,7 @@ $(document).on('click', '.undo-chore-execution-button', function(e)
var executionId = $(e.currentTarget).attr('data-execution-id'); var executionId = $(e.currentTarget).attr('data-execution-id');
Grocy.Api.Post('chores/executions/' + executionId.toString() + '/undo', {}, Grocy.Api.Post('chores/executions/' + executionId.toString() + '/undo', {},
function(result) function (result)
{ {
element.closest("tr").addClass("text-muted"); 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>"); 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"); RefreshContextualTimeago("#chore-execution-" + executionId + "-row");
toastr.success(__t("Chore execution successfully undone")); toastr.success(__t("Chore execution successfully undone"));
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }

View File

@ -11,7 +11,7 @@
$('#chores-overview-table tbody').removeClass("d-none"); $('#chores-overview-table tbody').removeClass("d-none");
choresOverviewTable.columns.adjust().draw(); choresOverviewTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function() $("#search").on("keyup", Delay(function ()
{ {
var value = $(this).val(); var value = $(this).val();
if (value === "all") if (value === "all")
@ -19,10 +19,10 @@ $("#search").on("keyup", Delay(function()
value = ""; value = "";
} }
choresOverviewTable.search(value).draw(); choresOverviewTable.search(value.accentNeutralise()).draw();
}, Grocy.FormFocusDelay)); }, Grocy.FormFocusDelay));
$("#status-filter").on("change", function() $("#status-filter").on("change", function ()
{ {
var value = $(this).val(); var value = $(this).val();
if (value === "all") if (value === "all")
@ -33,10 +33,10 @@ $("#status-filter").on("change", function()
// Transfer CSS classes of selected element to dropdown element (for background) // Transfer CSS classes of selected element to dropdown element (for background)
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control"); $(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
choresOverviewTable.column(choresOverviewTable.colReorder.transpose(5)).search(value).draw(); choresOverviewTable.column(choresOverviewTable.colReorder.transpose(5)).search(value.accentNeutralise()).draw();
}); });
$("#user-filter").on("change", function() $("#user-filter").on("change", function ()
{ {
var value = $(this).val(); var value = $(this).val();
if (value === "all") if (value === "all")
@ -47,7 +47,7 @@ $("#user-filter").on("change", function()
// Transfer CSS classes of selected element to dropdown element (for background) // Transfer CSS classes of selected element to dropdown element (for background)
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control"); $(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
choresOverviewTable.column(choresOverviewTable.colReorder.transpose(6)).search(value).draw(); choresOverviewTable.column(choresOverviewTable.colReorder.transpose(6)).search(value.accentNeutralise()).draw();
if (value) 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(""); $("#search").val("");
$("#status-filter").val("all"); $("#status-filter").val("all");
@ -66,21 +66,21 @@ $("#clear-filter-button").on("click", function()
RemoveUriParam("user"); RemoveUriParam("user");
}); });
$(".status-filter-message").on("click", function() $(".status-filter-message").on("click", function ()
{ {
var value = $(this).data("status-filter"); var value = $(this).data("status-filter");
$("#status-filter").val(value); $("#status-filter").val(value);
$("#status-filter").trigger("change"); $("#status-filter").trigger("change");
}); });
$(".user-filter-message").on("click", function() $(".user-filter-message").on("click", function ()
{ {
var value = $(this).data("user-filter"); var value = $(this).data("user-filter");
$("#user-filter").val(value); $("#user-filter").val(value);
$("#user-filter").trigger("change"); $("#user-filter").trigger("change");
}); });
$(document).on('click', '.track-chore-button', function(e) $(document).on('click', '.track-chore-button', function (e)
{ {
e.preventDefault(); e.preventDefault();
@ -92,7 +92,7 @@ $(document).on('click', '.track-chore-button', function(e)
var now = $(e.currentTarget).hasClass("now"); var now = $(e.currentTarget).hasClass("now");
Grocy.Api.Get('chores/' + choreId, Grocy.Api.Get('chores/' + choreId,
function(choreDetails) function (choreDetails)
{ {
var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss'); var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss');
if ((skipped || !now) && choreDetails.next_estimated_execution_time != null) 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 }, Grocy.Api.Post('chores/' + choreId + '/execute', { 'tracked_time': trackedTime, 'skipped': skipped },
function() function ()
{ {
Grocy.Api.Get('chores/' + choreId, Grocy.Api.Get('chores/' + choreId,
function(result) function (result)
{ {
var choreRow = $('#chore-' + choreId + '-row'); var choreRow = $('#chore-' + choreId + '-row');
var nextXDaysThreshold = moment().add($("#info-due-soon-chores").data("next-x-days"), "days"); 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(); RefreshStatistics();
// Delay due to delayed/animated set of new timestamps above // Delay due to delayed/animated set of new timestamps above
setTimeout(function() setTimeout(function ()
{ {
RefreshContextualTimeago("#chore-" + choreId + "-row"); RefreshContextualTimeago("#chore-" + choreId + "-row");
@ -186,21 +186,21 @@ $(document).on('click', '.track-chore-button', function(e)
$(".input-group-filter").trigger("change"); $(".input-group-filter").trigger("change");
}, Grocy.FormFocusDelay); }, Grocy.FormFocusDelay);
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy(); Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr); console.error(xhr);
} }
); );
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy(); Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr); console.error(xhr);
} }
); );
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy("choretracking-form"); Grocy.FrontendHelpers.EndUiBusy("choretracking-form");
console.error(xhr); 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(); e.preventDefault();
var choreId = $(e.currentTarget).attr('data-chore-id'); 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) if (Grocy.Webhooks.labelprinter !== undefined)
{ {
@ -226,7 +226,7 @@ function RefreshStatistics()
{ {
var nextXDays = $("#info-due-soon-chores").data("next-x-days"); var nextXDays = $("#info-due-soon-chores").data("next-x-days");
Grocy.Api.Get('chores', Grocy.Api.Get('chores',
function(result) function (result)
{ {
var dueTodayCount = 0; var dueTodayCount = 0;
var dueSoonCount = 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-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')); $("#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); console.error(xhr);
} }
); );
} }
$(document).on("click", ".reschedule-chore-button", function(e) $(document).on("click", ".reschedule-chore-button", function (e)
{ {
e.preventDefault(); e.preventDefault();
var choreId = $(e.currentTarget).attr("data-chore-id"); var choreId = $(e.currentTarget).attr("data-chore-id");
Grocy.EditObjectId = choreId; 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"); var prefillDate = choreDetails.next_estimated_execution_time || moment().format("YYYY-MM-DD HH:mm:ss");
if (choreDetails.chore.rescheduled_date) 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(); 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() }, 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 }, Grocy.Api.Post('chores/executions/calculate-next-assignments', { "chore_id": Grocy.EditObjectId },
function(result) function (result)
{ {
window.location.reload(); window.location.reload();
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }
); );
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response); 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(); e.preventDefault();
Grocy.Api.Put('objects/chores/' + Grocy.EditObjectId, { "rescheduled_date": null, "rescheduled_next_execution_assigned_to_user_id": null }, 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 }, Grocy.Api.Post('chores/executions/calculate-next-assignments', { "chore_id": Grocy.EditObjectId },
function(result) function (result)
{ {
window.location.reload(); window.location.reload();
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }
); );
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response); 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(); e.preventDefault();
@ -40,10 +40,10 @@
var bookingResponse = null; var bookingResponse = null;
Grocy.Api.Get('stock/products/' + jsonForm.product_id, Grocy.Api.Get('stock/products/' + jsonForm.product_id,
function(productDetails) function (productDetails)
{ {
Grocy.Api.Post(apiUrl, jsonData, Grocy.Api.Post(apiUrl, jsonData,
function(result) function (result)
{ {
if (BoolVal(Grocy.UserSettings.scan_mode_consume_enabled)) if (BoolVal(Grocy.UserSettings.scan_mode_consume_enabled))
{ {
@ -59,14 +59,14 @@
jsonDataBarcode.product_id = jsonForm.product_id; jsonDataBarcode.product_id = jsonForm.product_id;
Grocy.Api.Post('objects/product_barcodes', jsonDataBarcode, Grocy.Api.Post('objects/product_barcodes', jsonDataBarcode,
function(result) function (result)
{ {
$("#flow-info-InplaceAddBarcodeToExistingProduct").addClass("d-none"); $("#flow-info-InplaceAddBarcodeToExistingProduct").addClass("d-none");
$('#barcode-lookup-disabled-hint').addClass('d-none'); $('#barcode-lookup-disabled-hint').addClass('d-none');
$('#barcode-lookup-hint').removeClass('d-none'); $('#barcode-lookup-hint').removeClass('d-none');
window.history.replaceState({}, document.title, U("/consume")); window.history.replaceState({}, document.title, U("/consume"));
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy("consume-form"); Grocy.FrontendHelpers.EndUiBusy("consume-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response); Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
@ -130,7 +130,7 @@
$("#consume-exact-amount-group").addClass("d-none"); $("#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.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
Grocy.FrontendHelpers.EndUiBusy("consume-form"); Grocy.FrontendHelpers.EndUiBusy("consume-form");
@ -138,7 +138,7 @@
} }
); );
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy("consume-form"); Grocy.FrontendHelpers.EndUiBusy("consume-form");
console.error(xhr); 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(); e.preventDefault();
@ -168,6 +168,7 @@ $('#save-mark-as-open-button').on('click', function(e)
jsonData = {}; jsonData = {};
jsonData.amount = jsonForm.amount; jsonData.amount = jsonForm.amount;
jsonData.allow_subproduct_substitution = true; jsonData.allow_subproduct_substitution = true;
jsonData.location_id = jsonForm.location_id;
if ($("#use_specific_stock_entry").is(":checked")) if ($("#use_specific_stock_entry").is(":checked"))
{ {
@ -175,10 +176,10 @@ $('#save-mark-as-open-button').on('click', function(e)
} }
Grocy.Api.Get('stock/products/' + jsonForm.product_id, Grocy.Api.Get('stock/products/' + jsonForm.product_id,
function(productDetails) function (productDetails)
{ {
Grocy.Api.Post(apiUrl, jsonData, Grocy.Api.Post(apiUrl, jsonData,
function(result) function (result)
{ {
$("#specific_stock_entry").find("option").remove().end().append("<option></option>"); $("#specific_stock_entry").find("option").remove().end().append("<option></option>");
if ($("#use_specific_stock_entry").is(":checked")) if ($("#use_specific_stock_entry").is(":checked"))
@ -206,16 +207,17 @@ $('#save-mark-as-open-button').on('click', function(e)
$(".input-group-productamountpicker").trigger("change"); $(".input-group-productamountpicker").trigger("change");
Grocy.Components.ProductPicker.Clear(); Grocy.Components.ProductPicker.Clear();
Grocy.Components.ProductPicker.GetInputElement().focus(); Grocy.Components.ProductPicker.GetInputElement().focus();
Grocy.Components.ProductCard.Refresh(jsonForm.product_id);
Grocy.FrontendHelpers.ValidateForm('consume-form'); Grocy.FrontendHelpers.ValidateForm('consume-form');
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy("consume-form"); Grocy.FrontendHelpers.EndUiBusy("consume-form");
console.error(xhr); console.error(xhr);
} }
); );
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy("consume-form"); Grocy.FrontendHelpers.EndUiBusy("consume-form");
console.error(xhr); console.error(xhr);
@ -223,7 +225,8 @@ $('#save-mark-as-open-button').on('click', function(e)
); );
}); });
var sumValue = 0; var sumValue = 0;
$("#location_id").on('change', function(e) var sumValueOpened = 0;
$("#location_id").on('change', function (e)
{ {
var locationId = $(e.target).val(); var locationId = $(e.target).val();
$("#specific_stock_entry").find("option").remove().end().append("<option></option>"); $("#specific_stock_entry").find("option").remove().end().append("<option></option>");
@ -245,12 +248,12 @@ $("#location_id").on('change', function(e)
if (gc.length == 4) if (gc.length == 4)
{ {
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries?query[]=stock_id=' + gc[3], 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]); OnLocationChange(stockEntries[0].location_id, gc[3]);
$('#display_amount').val(stockEntries[0].amount); $('#display_amount').val(stockEntries[0].amount);
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }
@ -271,6 +274,7 @@ $("#location_id").on('change', function(e)
function OnLocationChange(locationId, stockId) function OnLocationChange(locationId, stockId)
{ {
sumValue = 0; sumValue = 0;
sumValueOpened = 0;
if (locationId) if (locationId)
{ {
@ -280,7 +284,7 @@ function OnLocationChange(locationId, stockId)
} }
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries?include_sub_products=true', Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries?include_sub_products=true',
function(stockEntries) function (stockEntries)
{ {
stockEntries.forEach(stockEntry => stockEntries.forEach(stockEntry =>
{ {
@ -307,6 +311,11 @@ function OnLocationChange(locationId, stockId)
sumValue = sumValue + (stockEntry.amount || 0); sumValue = sumValue + (stockEntry.amount || 0);
if (BoolVal(stockEntry.open))
{
sumValueOpened = sumValueOpened + (stockEntry.amount || 0);
}
if (stockEntry.stock_id == stockId) if (stockEntry.stock_id == stockId)
{ {
$("#use_specific_stock_entry").click(); $("#use_specific_stock_entry").click();
@ -315,13 +324,15 @@ function OnLocationChange(locationId, stockId)
} }
}); });
Grocy.Api.Get('stock/products/' + Grocy.Components.ProductPicker.GetValue(), Grocy.Api.Get('stock/products/' + Grocy.Components.ProductPicker.GetValue(),
function(productDetails) function (productDetails)
{ {
current_productDetails = productDetails; current_productDetails = productDetails;
RefreshForm(); RefreshForm();
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }
@ -332,7 +343,7 @@ function OnLocationChange(locationId, stockId)
ScanModeSubmit(); ScanModeSubmit();
} }
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }
@ -340,7 +351,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)) if (BoolVal(Grocy.UserSettings.scan_mode_consume_enabled))
{ {
@ -361,7 +372,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
Grocy.Components.ProductCard.Refresh(productId); Grocy.Components.ProductCard.Refresh(productId);
Grocy.Api.Get('stock/products/' + productId, Grocy.Api.Get('stock/products/' + productId,
function(productDetails) function (productDetails)
{ {
current_productDetails = productDetails; current_productDetails = productDetails;
@ -386,7 +397,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
$("#location_id").find("option").remove().end().append("<option></option>"); $("#location_id").find("option").remove().end().append("<option></option>");
Grocy.Api.Get("stock/products/" + productId + '/locations?include_sub_products=true', Grocy.Api.Get("stock/products/" + productId + '/locations?include_sub_products=true',
function(stockLocations) function (stockLocations)
{ {
var setDefault = 0; var setDefault = 0;
var stockAmountAtDefaultLocation = 0; var stockAmountAtDefaultLocation = 0;
@ -436,7 +447,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
if (document.getElementById("product_id").getAttribute("barcode") != "null") if (document.getElementById("product_id").getAttribute("barcode") != "null")
{ {
Grocy.Api.Get('objects/product_barcodes?query[]=barcode=' + document.getElementById("product_id").getAttribute("barcode"), Grocy.Api.Get('objects/product_barcodes?query[]=barcode=' + document.getElementById("product_id").getAttribute("barcode"),
function(barcodeResult) function (barcodeResult)
{ {
if (barcodeResult != null) if (barcodeResult != null)
{ {
@ -462,14 +473,14 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
} }
} }
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }
); );
} }
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }
@ -489,21 +500,12 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
Grocy.Components.ProductPicker.HideCustomError(); Grocy.Components.ProductPicker.HideCustomError();
Grocy.FrontendHelpers.ValidateForm('consume-form'); Grocy.FrontendHelpers.ValidateForm('consume-form');
setTimeout(function() setTimeout(function ()
{ {
$('#display_amount').focus(); $('#display_amount').focus();
}, Grocy.FormFocusDelay); }, 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); console.error(xhr);
} }
@ -515,28 +517,44 @@ $('#display_amount').val(Grocy.UserSettings.stock_default_consume_amount);
$(".input-group-productamountpicker").trigger("change"); $(".input-group-productamountpicker").trigger("change");
Grocy.FrontendHelpers.ValidateForm('consume-form'); Grocy.FrontendHelpers.ValidateForm('consume-form');
$('#display_amount').on('focus', function(e) $('#display_amount').on('focus', function (e)
{ {
$(this).select(); $(this).select();
}); });
$('#price').on('focus', function(e) $('#price').on('focus', function (e)
{ {
$(this).select(); $(this).select();
}); });
$('#consume-form input').keyup(function (event)
$('#consume-form input').keyup(function(event)
{ {
Grocy.FrontendHelpers.ValidateForm('consume-form'); Grocy.FrontendHelpers.ValidateForm('consume-form');
}); });
$('#consume-form select').change(function(event) $('#consume-form select').change(function (event)
{ {
Grocy.FrontendHelpers.ValidateForm('consume-form'); Grocy.FrontendHelpers.ValidateForm('consume-form');
}); });
$('#consume-form input').keydown(function(event) $("#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)
{ {
if (event.keyCode === 13) // Enter if (event.keyCode === 13) // Enter
{ {
@ -553,28 +571,45 @@ $('#consume-form input').keydown(function(event)
} }
}); });
$("#specific_stock_entry").on("change", function(e) $("#specific_stock_entry").on("change", function (e)
{ {
if ($(e.target).val() == "") if ($(e.target).val() == "")
{ {
sumValue = 0; sumValue = 0;
sumValueOpened = 0;
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries?include_sub_products=true', Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries?include_sub_products=true',
function(stockEntries) function (stockEntries)
{ {
stockEntries.forEach(stockEntry => stockEntries.forEach(stockEntry =>
{ {
if (stockEntry.location_id == $("#location_id").val() || stockEntry.location_id == "") if (stockEntry.location_id == $("#location_id").val() || stockEntry.location_id == "")
{ {
sumValue = sumValue + stockEntry.amount_aggregated; sumValue = sumValue + (stockEntry.amount || 0);
if (BoolVal(stockEntry.open))
{
sumValueOpened = sumValueOpened + (stockEntry.amount || 0);
}
} }
}); });
$("#display_amount").attr("max", sumValue.toFixed(Grocy.UserSettings.stock_decimal_places_amounts)); $("#display_amount").attr("max", sumValue.toFixed(Grocy.UserSettings.stock_decimal_places_amounts));
if (sumValue == 0) if (sumValue == 0)
{ {
$("#display_amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location')); $("#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); console.error(xhr);
} }
@ -586,7 +621,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"); var value = $(this).is(":checked");
@ -605,7 +640,7 @@ $("#use_specific_stock_entry").on("change", function()
Grocy.FrontendHelpers.ValidateForm("consume-form"); Grocy.FrontendHelpers.ValidateForm("consume-form");
}); });
$("#qu_id").on("change", function() $("#qu_id").on("change", function ()
{ {
RefreshForm(); RefreshForm();
}); });
@ -613,11 +648,11 @@ $("#qu_id").on("change", function()
function UndoStockBooking(bookingId) function UndoStockBooking(bookingId)
{ {
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {}, Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {},
function(result) function (result)
{ {
toastr.success(__t("Booking successfully undone")); toastr.success(__t("Booking successfully undone"));
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }
@ -627,11 +662,11 @@ function UndoStockBooking(bookingId)
function UndoStockTransaction(transactionId) function UndoStockTransaction(transactionId)
{ {
Grocy.Api.Post('stock/transactions/' + transactionId.toString() + '/undo', {}, Grocy.Api.Post('stock/transactions/' + transactionId.toString() + '/undo', {},
function(result) function (result)
{ {
toastr.success(__t("Transaction successfully undone")); toastr.success(__t("Transaction successfully undone"));
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }
@ -657,12 +692,12 @@ if (GetUriParam("embedded") !== undefined)
} }
// Default input field // Default input field
setTimeout(function() setTimeout(function ()
{ {
Grocy.Components.ProductPicker.GetInputElement().focus(); Grocy.Components.ProductPicker.GetInputElement().focus();
}, Grocy.FormFocusDelay); }, Grocy.FormFocusDelay);
$(document).on("change", "#scan-mode", function(e) $(document).on("change", "#scan-mode", function (e)
{ {
if ($(this).prop("checked")) if ($(this).prop("checked"))
{ {
@ -670,7 +705,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").click();
$("#scan-mode-button").toggleClass("btn-success").toggleClass("btn-danger"); $("#scan-mode-button").toggleClass("btn-success").toggleClass("btn-danger");
@ -727,6 +762,15 @@ function RefreshForm()
$("#display_amount").removeAttr("max"); $("#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"); Grocy.FrontendHelpers.ValidateForm("consume-form");
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -392,86 +392,7 @@ $(window).on("message", function (e)
} }
}); });
if (Grocy.EditMode == "create" && GetUriParam("copy-of") != undefined) if (Grocy.EditMode === 'create')
{
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') if (Grocy.UserSettings.product_presets_location_id.toString() !== '-1')
{ {

View File

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

View File

@ -20,7 +20,7 @@ $("#search").on("keyup", Delay(function ()
value = ""; value = "";
} }
productsTable.search(value).draw(); productsTable.search(value.accentNeutralise()).draw();
}, Grocy.FormFocusDelay)); }, Grocy.FormFocusDelay));
$("#product-group-filter").on("change", function () $("#product-group-filter").on("change", function ()
@ -32,7 +32,7 @@ $("#product-group-filter").on("change", function ()
} }
else else
{ {
productsTable.column(productsTable.colReorder.transpose(6)).search("^" + $.fn.dataTable.util.escapeRegex(value) + "$", true, false).draw(); productsTable.column(productsTable.colReorder.transpose(6)).search("^" + $.fn.dataTable.util.escapeRegex(value.accentNeutralise()) + "$", true, false).draw();
} }
}); });
@ -175,3 +175,21 @@ $("#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"); $('#qu-conversions-resolved-table tbody').removeClass("d-none");
quConversionsResolvedTable.columns.adjust().draw(); quConversionsResolvedTable.columns.adjust().draw();
$("#quantity-unit-filter").on("change", function() $("#quantity-unit-filter").on("change", function ()
{ {
var value = $("#quantity-unit-filter option:selected").text(); var value = $("#quantity-unit-filter option:selected").text();
if (value === __t("All")) if (value === __t("All"))
@ -16,10 +16,10 @@ $("#quantity-unit-filter").on("change", function()
value = ""; value = "";
} }
quConversionsResolvedTable.column([quConversionsResolvedTable.colReorder.transpose(1), quConversionsResolvedTable.colReorder.transpose(2)]).search(value).draw(); quConversionsResolvedTable.column([quConversionsResolvedTable.colReorder.transpose(1), quConversionsResolvedTable.colReorder.transpose(2)]).search(value.accentNeutralise()).draw();
}); });
$("#clear-filter-button").on("click", function() $("#clear-filter-button").on("click", function ()
{ {
$("#quantity-unit-filter").val("all"); $("#quantity-unit-filter").val("all");
quConversionsResolvedTable.column([quConversionsResolvedTable.colReorder.transpose(1), quConversionsResolvedTable.colReorder.transpose(2)]).search("").draw(); quConversionsResolvedTable.column([quConversionsResolvedTable.colReorder.transpose(1), quConversionsResolvedTable.colReorder.transpose(2)]).search("").draw();

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@
$('#stockentries-table tbody').removeClass("d-none"); $('#stockentries-table tbody').removeClass("d-none");
stockEntriesTable.columns.adjust().draw(); 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(); var productId = Grocy.Components.ProductPicker.GetValue();
@ -27,7 +27,7 @@ $.fn.dataTable.ext.search.push(function(settings, data, dataIndex)
return false; return false;
}); });
$("#clear-filter-button").on("click", function() $("#clear-filter-button").on("click", function ()
{ {
$("#location-filter").val("all"); $("#location-filter").val("all");
$("#location-filter").trigger("change"); $("#location-filter").trigger("change");
@ -40,7 +40,7 @@ $("#clear-filter-button").on("click", function()
stockEntriesTable.draw(); stockEntriesTable.draw();
}); });
$("#location-filter").on("change", function() $("#location-filter").on("change", function ()
{ {
var value = $(this).val(); var value = $(this).val();
var text = $("#location-filter option:selected").text(); var text = $("#location-filter option:selected").text();
@ -49,20 +49,20 @@ $("#location-filter").on("change", function()
text = ""; text = "";
} }
stockEntriesTable.column(stockEntriesTable.colReorder.transpose(5)).search(text).draw(); stockEntriesTable.column(stockEntriesTable.colReorder.transpose(5)).search(text.accentNeutralise()).draw();
}); });
Grocy.Components.ProductPicker.GetPicker().on('change', function(e) Grocy.Components.ProductPicker.GetPicker().on('change', function (e)
{ {
stockEntriesTable.draw(); stockEntriesTable.draw();
}); });
Grocy.Components.ProductPicker.GetInputElement().on('keyup', function(e) Grocy.Components.ProductPicker.GetInputElement().on('keyup', function (e)
{ {
stockEntriesTable.draw(); stockEntriesTable.draw();
}); });
$(document).on('click', '.stock-consume-button', function(e) $(document).on('click', '.stock-consume-button', function (e)
{ {
e.preventDefault(); e.preventDefault();
@ -77,10 +77,10 @@ $(document).on('click', '.stock-consume-button', function(e)
var wasSpoiled = $(e.currentTarget).hasClass("stock-consume-button-spoiled"); 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 }, 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, 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); 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) if (wasSpoiled)
@ -94,14 +94,14 @@ $(document).on('click', '.stock-consume-button', function(e)
toastr.success(toastMessage); toastr.success(toastMessage);
Grocy.GetTopmostWindow().postMessage(WindowMessageBag("BroadcastMessage", WindowMessageBag("ProductChanged", productId)), Grocy.BaseUrl); Grocy.GetTopmostWindow().postMessage(WindowMessageBag("BroadcastMessage", WindowMessageBag("ProductChanged", productId)), Grocy.BaseUrl);
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy(); Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr); console.error(xhr);
} }
); );
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy(); Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr); 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(); e.preventDefault();
@ -122,10 +122,10 @@ $(document).on('click', '.product-open-button', function(e)
var button = $(e.currentTarget); var button = $(e.currentTarget);
Grocy.Api.Post('stock/products/' + productId + '/open', { 'amount': openAmount, 'stock_entry_id': specificStockEntryId }, Grocy.Api.Post('stock/products/' + productId + '/open', { 'amount': openAmount, 'stock_entry_id': specificStockEntryId },
function(bookingResponse) function (bookingResponse)
{ {
Grocy.Api.Get('stock/products/' + productId, Grocy.Api.Get('stock/products/' + productId,
function(result) function (result)
{ {
button.addClass("disabled"); button.addClass("disabled");
Grocy.FrontendHelpers.EndUiBusy(); Grocy.FrontendHelpers.EndUiBusy();
@ -139,14 +139,14 @@ $(document).on('click', '.product-open-button', function(e)
RefreshStockEntryRow(stockRowId); RefreshStockEntryRow(stockRowId);
Grocy.GetTopmostWindow().postMessage(WindowMessageBag("BroadcastMessage", WindowMessageBag("ProductChanged", productId)), Grocy.BaseUrl); Grocy.GetTopmostWindow().postMessage(WindowMessageBag("BroadcastMessage", WindowMessageBag("ProductChanged", productId)), Grocy.BaseUrl);
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy(); Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr); console.error(xhr);
} }
); );
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy(); Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr); 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(); e.preventDefault();
var stockId = $(e.currentTarget).attr('data-stock-id'); 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) if (Grocy.Webhooks.labelprinter !== undefined)
{ {
@ -171,7 +171,7 @@ $(document).on('click', '.stockentry-grocycode-label-print', function(e)
function RefreshStockEntryRow(stockRowId) function RefreshStockEntryRow(stockRowId)
{ {
Grocy.Api.Get("stock/entry/" + stockRowId, Grocy.Api.Get("stock/entry/" + stockRowId,
function(result) function (result)
{ {
var stockRow = $('#stock-' + stockRowId + '-row'); var stockRow = $('#stock-' + stockRowId + '-row');
@ -183,7 +183,7 @@ function RefreshStockEntryRow(stockRowId)
if (result == null || result.amount == 0) if (result == null || result.amount == 0)
{ {
animateCSS("#stock-" + stockRowId + "-row", "fadeOut", function() animateCSS("#stock-" + stockRowId + "-row", "fadeOut", function ()
{ {
$("#stock-" + stockRowId + "-row").addClass("d-none"); $("#stock-" + stockRowId + "-row").addClass("d-none");
}); });
@ -225,21 +225,21 @@ function RefreshStockEntryRow(stockRowId)
var locationName = ""; var locationName = "";
Grocy.Api.Get("objects/locations/" + result.location_id, Grocy.Api.Get("objects/locations/" + result.location_id,
function(locationResult) function (locationResult)
{ {
locationName = locationResult.name; locationName = locationResult.name;
$('#stock-' + stockRowId + '-location').attr('data-location-id', result.location_id); $('#stock-' + stockRowId + '-location').attr('data-location-id', result.location_id);
$('#stock-' + stockRowId + '-location').text(locationName); $('#stock-' + stockRowId + '-location').text(locationName);
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }
); );
Grocy.Api.Get("stock/products/" + result.product_id, Grocy.Api.Get("stock/products/" + result.product_id,
function(productDetails) function (productDetails)
{ {
if (!result.price) if (!result.price)
{ {
@ -254,7 +254,7 @@ function RefreshStockEntryRow(stockRowId)
$(".product-open-button[data-stockrow-id='" + stockRowId + "']").addClass("disabled"); $(".product-open-button[data-stockrow-id='" + stockRowId + "']").addClass("disabled");
} }
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }
@ -268,14 +268,14 @@ function RefreshStockEntryRow(stockRowId)
{ {
var shoppingLocationName = ""; var shoppingLocationName = "";
Grocy.Api.Get("objects/shopping_locations/" + result.shopping_location_id, Grocy.Api.Get("objects/shopping_locations/" + result.shopping_location_id,
function(shoppingLocationResult) function (shoppingLocationResult)
{ {
shoppingLocationName = shoppingLocationResult.name; shoppingLocationName = shoppingLocationResult.name;
$('#stock-' + stockRowId + '-shopping-location').attr('data-shopping-location-id', result.location_id); $('#stock-' + stockRowId + '-shopping-location').attr('data-shopping-location-id', result.location_id);
$('#stock-' + stockRowId + '-shopping-location').text(shoppingLocationName); $('#stock-' + stockRowId + '-shopping-location').text(shoppingLocationName);
}, },
function(xhr) function (xhr)
{ {
console.error(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... // 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"); RefreshContextualTimeago("#stock-" + stockRowId + "-row");
RefreshLocaleNumberDisplay("#stock-" + stockRowId + "-row"); RefreshLocaleNumberDisplay("#stock-" + stockRowId + "-row");
}, Grocy.FormFocusDelay); }, Grocy.FormFocusDelay);
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy(); Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr); 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; var data = e.originalEvent.data;
if (data.Message == "ProductChanged") 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")); RefreshStockEntryRow($(this).attr("data-stockrow-id"));
}); });
@ -330,12 +330,12 @@ Grocy.Components.ProductPicker.GetPicker().trigger('change');
function UndoStockBookingEntry(bookingId, stockRowId, productId) function UndoStockBookingEntry(bookingId, stockRowId, productId)
{ {
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {}, Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {},
function(result) function (result)
{ {
Grocy.GetTopmostWindow().postMessage(WindowMessageBag("BroadcastMessage", WindowMessageBag("ProductChanged", productId)), Grocy.BaseUrl); Grocy.GetTopmostWindow().postMessage(WindowMessageBag("BroadcastMessage", WindowMessageBag("ProductChanged", productId)), Grocy.BaseUrl);
toastr.success(__t("Booking successfully undone")); toastr.success(__t("Booking successfully undone"));
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }

View File

@ -8,7 +8,7 @@
$('#stock-journal-table tbody').removeClass("d-none"); $('#stock-journal-table tbody').removeClass("d-none");
stockJournalTable.columns.adjust().draw(); stockJournalTable.columns.adjust().draw();
$("#product-filter").on("change", function() $("#product-filter").on("change", function ()
{ {
var value = $(this).val(); var value = $(this).val();
if (value === "all") if (value === "all")
@ -23,7 +23,7 @@ $("#product-filter").on("change", function()
window.location.reload(); window.location.reload();
}); });
$("#transaction-type-filter").on("change", function() $("#transaction-type-filter").on("change", function ()
{ {
var value = $(this).val(); var value = $(this).val();
var text = $("#transaction-type-filter option:selected").text(); var text = $("#transaction-type-filter option:selected").text();
@ -32,10 +32,10 @@ $("#transaction-type-filter").on("change", function()
text = ""; text = "";
} }
stockJournalTable.column(stockJournalTable.colReorder.transpose(4)).search(text).draw(); stockJournalTable.column(stockJournalTable.colReorder.transpose(4)).search(text.accentNeutralise()).draw();
}); });
$("#location-filter").on("change", function() $("#location-filter").on("change", function ()
{ {
var value = $(this).val(); var value = $(this).val();
var text = $("#location-filter option:selected").text(); var text = $("#location-filter option:selected").text();
@ -44,10 +44,10 @@ $("#location-filter").on("change", function()
text = ""; text = "";
} }
stockJournalTable.column(stockJournalTable.colReorder.transpose(5)).search(text).draw(); stockJournalTable.column(stockJournalTable.colReorder.transpose(5)).search(text.accentNeutralise()).draw();
}); });
$("#user-filter").on("change", function() $("#user-filter").on("change", function ()
{ {
var value = $(this).val(); var value = $(this).val();
var text = $("#user-filter option:selected").text(); var text = $("#user-filter option:selected").text();
@ -56,16 +56,16 @@ $("#user-filter").on("change", function()
text = ""; text = "";
} }
stockJournalTable.column(stockJournalTable.colReorder.transpose(6)).search(text).draw(); stockJournalTable.column(stockJournalTable.colReorder.transpose(6)).search(text.accentNeutralise()).draw();
}); });
$("#daterange-filter").on("change", function() $("#daterange-filter").on("change", function ()
{ {
UpdateUriParam("months", $(this).val()); UpdateUriParam("months", $(this).val());
window.location.reload(); window.location.reload();
}); });
$("#search").on("keyup", Delay(function() $("#search").on("keyup", Delay(function ()
{ {
var value = $(this).val(); var value = $(this).val();
if (value === "all") if (value === "all")
@ -73,10 +73,10 @@ $("#search").on("keyup", Delay(function()
value = ""; value = "";
} }
stockJournalTable.search(value).draw(); stockJournalTable.search(value.accentNeutralise()).draw();
}, Grocy.FormFocusDelay)); }, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function() $("#clear-filter-button").on("click", function ()
{ {
$("#search").val(""); $("#search").val("");
$("#transaction-type-filter").val("all"); $("#transaction-type-filter").val("all");
@ -104,7 +104,7 @@ if (typeof GetUriParam("months") !== "undefined")
$("#daterange-filter").val(GetUriParam("months")); $("#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(); e.preventDefault();
@ -118,7 +118,7 @@ $(document).on('click', '.undo-stock-booking-button', function(e)
} }
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {}, Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {},
function(result) function (result)
{ {
correspondingBookingsRoot.addClass("text-muted"); 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>"); 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"); RefreshContextualTimeago("#stock-booking-" + bookingId + "-row");
toastr.success(__t("Booking successfully undone")); toastr.success(__t("Booking successfully undone"));
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
toastr.error(__t(JSON.parse(xhr.response).error_message)); 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(); e.preventDefault();
var productId = $(e.currentTarget).attr('data-product-id'); 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) 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"); $('#stock-journal-summary-table tbody').removeClass("d-none");
journalSummaryTable.columns.adjust().draw(); journalSummaryTable.columns.adjust().draw();
$("#product-filter").on("change", function() $("#product-filter").on("change", function ()
{ {
var value = $(this).val(); var value = $(this).val();
var text = $("#product-filter option:selected").text(); var text = $("#product-filter option:selected").text();
@ -18,11 +18,11 @@ $("#product-filter").on("change", function()
} }
else else
{ {
journalSummaryTable.column(journalSummaryTable.colReorder.transpose(1)).search("^" + $.fn.dataTable.util.escapeRegex(text) + "$", true, false).draw(); journalSummaryTable.column(journalSummaryTable.colReorder.transpose(1)).search("^" + $.fn.dataTable.util.escapeRegex(text.accentNeutralise()) + "$", true, false).draw();
} }
}); });
$("#transaction-type-filter").on("change", function() $("#transaction-type-filter").on("change", function ()
{ {
var value = $(this).val(); var value = $(this).val();
var text = $("#transaction-type-filter option:selected").text(); var text = $("#transaction-type-filter option:selected").text();
@ -31,10 +31,10 @@ $("#transaction-type-filter").on("change", function()
text = ""; text = "";
} }
journalSummaryTable.column(journalSummaryTable.colReorder.transpose(2)).search(text).draw(); journalSummaryTable.column(journalSummaryTable.colReorder.transpose(2)).search(text.accentNeutralise()).draw();
}); });
$("#user-filter").on("change", function() $("#user-filter").on("change", function ()
{ {
var value = $(this).val(); var value = $(this).val();
var text = $("#user-filter option:selected").text(); var text = $("#user-filter option:selected").text();
@ -43,10 +43,10 @@ $("#user-filter").on("change", function()
text = ""; text = "";
} }
journalSummaryTable.column(journalSummaryTable.colReorder.transpose(3)).search(text).draw(); journalSummaryTable.column(journalSummaryTable.colReorder.transpose(3)).search(text.accentNeutralise()).draw();
}); });
$("#search").on("keyup", Delay(function() $("#search").on("keyup", Delay(function ()
{ {
var value = $(this).val(); var value = $(this).val();
if (value === "all") if (value === "all")
@ -54,10 +54,10 @@ $("#search").on("keyup", Delay(function()
value = ""; value = "";
} }
journalSummaryTable.search(value).draw(); journalSummaryTable.search(value.accentNeutralise()).draw();
}, Grocy.FormFocusDelay)); }, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function() $("#clear-filter-button").on("click", function ()
{ {
$("#search").val(""); $("#search").val("");
$("#transaction-type-filter").val("all"); $("#transaction-type-filter").val("all");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -400,26 +400,24 @@ class StockService extends BaseService
if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION) if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION)
{ {
if ($locationId === null) $potentialStockEntries = $this->GetProductStockEntries($productId, $locationId, false, $allowSubproductSubstitution);
{
// 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') if ($specificStockEntryId !== 'default')
{ {
$potentialStockEntries = FindAllObjectsInArrayByPropertyValue($potentialStockEntries, 'stock_id', $specificStockEntryId); $potentialStockEntries = FindAllObjectsInArrayByPropertyValue($potentialStockEntries, 'stock_id', $specificStockEntryId);
} }
$productStockAmount = $productDetails->stock_amount_aggregated; if ($locationId === null)
if (round($amount, 2) > round($productStockAmount, 2))
{ {
throw new \Exception('Amount to be consumed cannot be > current stock amount (if supplied, at the desired location)'); $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)');
} }
if ($transactionId === null) if ($transactionId === null)
@ -865,7 +863,7 @@ class StockService extends BaseService
return $returnData; return $returnData;
} }
public function GetProductStockEntries(int $productId, $excludeOpened = false, $allowSubproductSubstitution = false) public function GetProductStockEntries(int $productId, $locationId = null, $excludeOpened = false, $allowSubproductSubstitution = false)
{ {
$sqlWhereProductId = 'product_id = ' . $productId; $sqlWhereProductId = 'product_id = ' . $productId;
if ($allowSubproductSubstitution) if ($allowSubproductSubstitution)
@ -879,7 +877,13 @@ class StockService extends BaseService
$sqlWhereAndOpen = 'AND open = 0'; $sqlWhereAndOpen = 'AND open = 0';
} }
return $this->DB->stock_next_use()->where($sqlWhereProductId . ' ' . $sqlWhereAndOpen); $sqlWhereAndLocationId = '';
if ($locationId !== null)
{
$sqlWhereAndLocationId = 'AND location_id = ' . $locationId;
}
return $this->DB->stock_next_use()->where($sqlWhereProductId . ' ' . $sqlWhereAndOpen . ' ' . $sqlWhereAndLocationId);
} }
public function GetLocationStockEntries($locationId) public function GetLocationStockEntries($locationId)
@ -892,12 +896,6 @@ class StockService extends BaseService
return $this->DB->stock()->where('location_id', $locationId); 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) public function GetProductStockLocations(int $productId, $allowSubproductSubstitution = false)
{ {
$sqlWhereProductId = 'product_id = ' . $productId; $sqlWhereProductId = 'product_id = ' . $productId;
@ -978,7 +976,7 @@ class StockService extends BaseService
return null; return null;
} }
public function OpenProduct(int $productId, float $amount, $specificStockEntryId = 'default', &$transactionId = null, $allowSubproductSubstitution = false) public function OpenProduct(int $productId, float $amount, $locationId = null, $specificStockEntryId = 'default', &$transactionId = null, $allowSubproductSubstitution = false)
{ {
if (!$this->ProductExists($productId)) if (!$this->ProductExists($productId))
{ {
@ -993,17 +991,24 @@ class StockService extends BaseService
} }
$productDetails = (object)$this->GetProductDetails($productId); $productDetails = (object)$this->GetProductDetails($productId);
$productStockAmountUnopened = $productDetails->stock_amount_aggregated - $productDetails->stock_amount_opened_aggregated; $potentialStockEntries = $this->GetProductStockEntries($productId, $locationId, true, $allowSubproductSubstitution);
$potentialStockEntries = $this->GetProductStockEntries($productId, true, $allowSubproductSubstitution);
if ($product->enable_tare_weight_handling == 1) if ($product->enable_tare_weight_handling == 1)
{ {
throw new \Exception('Opening tare weight handling enabled products is not supported'); 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) if ($amount > $productStockAmountUnopened)
{ {
throw new \Exception('Amount to be opened cannot be > current unopened stock amount'); throw new \Exception('Amount to be opened cannot be > current unopened stock amount (if supplied, at the desired location)');
} }
if ($specificStockEntryId !== 'default') if ($specificStockEntryId !== 'default')
@ -1290,16 +1295,10 @@ class StockService extends BaseService
{ {
// Hard fail for now, as we not yet support transferring tare weight enabled products // 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'); 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'); $productStockAmountAtFromLocation = $this->DB->stock()->where('product_id = :1 AND location_id = :2', $productId, $locationIdFrom)->sum('amount');
$potentialStockEntriesAtFromLocation = $this->GetProductStockEntriesForLocation($productId, $locationIdFrom); $potentialStockEntriesAtFromLocation = $this->GetProductStockEntries($productId, $locationIdFrom);
if ($amount > $productStockAmountAtFromLocation) if ($amount > $productStockAmountAtFromLocation)
{ {
@ -1730,6 +1729,54 @@ class StockService extends BaseService
DatabaseService::GetInstance()->GetDbConnectionRaw()->commit(); 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) public function CompactStockEntries($productId = null)
{ {
if ($productId == null) if ($productId == null)

View File

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

View File

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

View File

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

View File

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