mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 09:39:57 +00:00
Distinguish expiry/best before dates (closes #851)
This commit is contained in:
parent
1d50d5dd22
commit
b393998601
@ -70,7 +70,7 @@ The following shorthands are available:
|
||||
- Example: `20190417` will be converted to `2019-04-17`
|
||||
- `YYYYMMe` or `YYYYMM+` gets expanded to the end of the given month in the given year in proper notation
|
||||
- Example: `201807e` will be converted to `2018-07-31`
|
||||
- `x` gets expanded to `2999-12-31` (which I use for products which never expire)
|
||||
- `x` gets expanded to `2999-12-31` (which I use for products which are never overdue)
|
||||
- Down/up arrow keys will increase/decrease the date by 1 day
|
||||
- Right/left arrow keys will increase/decrease the date by 1 week
|
||||
- Shift + down/up arrow keys will increase/decrease the date by 1 month
|
||||
|
@ -29,13 +29,23 @@ _- (Because the stock quantity unit is now the base for everything, it cannot be
|
||||
- (Thanks @fipwmaqzufheoxq92ebc for the initial work on this)
|
||||
|
||||
### Stock improvements/fixes
|
||||
- Changes about best before dates: It's now possible to distinguish between best before dates and expiration dates:
|
||||
- New product option "Due date type" (defaults to "Best before date")
|
||||
- Wording changes:
|
||||
- All current places where "Best before date" was used now use "Due date"
|
||||
- Products with `Due date type = Best before date` (so all existing products) are "due" or "overdue" (they don't "expire" or are "expired")
|
||||
- Products with `Due date type = Expiration date` (new option) can "expire" or are "expired"
|
||||
- Color changes:
|
||||
- Prducts which are due soon or expire soon are (still) highlighted in yellow
|
||||
- Products which are overdue are highlighted in grey (there is is also a new filter button on the stock overview page for them)
|
||||
- Products which are expired (new option) are highlighted in red
|
||||
- When creating a quantity unit conversion it's now possible to automatically create the inverse conversion (thanks @kriddles)
|
||||
- On purchase there is now a warning shown, when the best before date of the purchased product is earlier than the next best before date in stock (enabled by default, can be disabled by a new stock setting (top right corner settings menu))
|
||||
- On purchase there is now a warning shown, when the due date of the purchased product is earlier than the next due date in stock (enabled by default, can be disabled by a new stock setting (top right corner settings menu))
|
||||
- The amount to be used for the "quick consume/open buttons" on the stock overview page can now be configured per product (new product option "Quick consume amount", defaults to 1)
|
||||
- Products can now be duplicated (new button on the products list page, all fields will be preset from the copied product, except the name)
|
||||
- Optimized/clarified what the total/unit price is on the purchase page (thanks @kriddles)
|
||||
- On the purchase page the amount field is now displayed above/before the best before date for better `TAB` handling (thanks @kriddles)
|
||||
- Changed that when `FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING` is disabled, products now get internally a best before of "never expires" (aka `2999-12-31`) instead of today (thanks @kriddles)
|
||||
- On the purchase page the amount field is now displayed above/before the due date for better `TAB` handling (thanks @kriddles)
|
||||
- Changed that when `FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING` is disabled, products now get internally a due date of "never overdue" (aka `2999-12-31`) instead of today (thanks @kriddles)
|
||||
- Products can now be hidden instead of deleted to prevent problems / missing information on existing references (new checkbox on the product edit page) (thanks @kriddles)
|
||||
- On the stock journal page, it's now visible if a consume-booking was spoiled
|
||||
- It's now tracked who made a stock change (currently logged in user, visible on the stock journal page) (thanks @fipwmaqzufheoxq92ebc)
|
||||
@ -53,7 +63,7 @@ _- (Because the stock quantity unit is now the base for everything, it cannot be
|
||||
- Fixed that when adding products through a product picker workflow and when the created products contains special characters, the product was not preselected on the previous page (thanks @Forceu)
|
||||
- Fixed that when editing a product the default store was not visible / always empty regardless if the product had one set (thanks @kriddles)
|
||||
- Fixed that `FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT` (option to configure if opened products should be considered for minimum stock amounts) was not handled correctly (thanks @teddybeermaniac)
|
||||
- Fixed that the "Expiring soon" sum (yellow filter button) on the stock overview page didn't include products which expire today (thanks @fipwmaqzufheoxq92ebc)
|
||||
- Fixed that the "Due soon" sum (yellow filter button) on the stock overview page didn't include products which are due today (thanks @fipwmaqzufheoxq92ebc)
|
||||
- Fixed that the shopping cart icon on the stock overview page was also shown if the product was on an already deleted shopping list (if enabled) (thanks @fipwmaqzufheoxq92ebc)
|
||||
- Fixed that when editing a stock entry without a price, the price field was prefilled with `1`
|
||||
- Fixed that the location & product groups filter on the stock overview page used a contains search instead of an exact search
|
||||
@ -73,7 +83,7 @@ _- (Because the stock quantity unit is now the base for everything, it cannot be
|
||||
|
||||
### Recipe improvements/fixes
|
||||
- It's now possible to print recipes (button next to the recipe title) (thanks @zsarnett)
|
||||
- Changed that recipe costs are now based on the costs of the products picked by the default consume rule "First expiring first, then first in first out" (thanks @kriddles)
|
||||
- Changed that recipe costs are now based on the costs of the products picked by the default consume rule "First due first, then first in first out" (thanks @kriddles)
|
||||
- Recipe costs were based on the last purchase price per product before, so this now better reflects the current real costs
|
||||
- Improved the recipe add workflow (a recipe called "New recipe" is now not automatically created when starting to add a recipe) (thanks @zsarnett)
|
||||
- Fixed that images on the recipe gallery view were not scaled correctly on larger screens (thanks @zsarnett)
|
||||
@ -145,6 +155,11 @@ _- (Because the stock quantity unit is now the base for everything, it cannot be
|
||||
- The product object no longer has a field `barcodes` with a comma separated barcode list, instead barcodes are now stored in a separate table/entity `product_barcodes` (use the existing "Generic entity interactions" endpoints to access them)
|
||||
- The endpoint `/objects/{entity}/search` was removed (use the existing `/objects/{entity}` endpoint with new new filter capabilities mentioned below)
|
||||
- The output / field names of `ProductDetailsResponse` have slightly changed (endpoint `/stock/products/{productId}`)
|
||||
- Endpoint `/stock/volatile`
|
||||
- The query parameter `expring_days` was renamed to `due_soon_days`
|
||||
- The field `expiring_products` was renamed to `due_products`
|
||||
- The field `expired_products` now only contains expired products (so them with `Due date type = Expiration date`)
|
||||
- The new field `overdue_products` contains only overdue products (so them with `Due date type = Best before date`)
|
||||
- For better integration (apps), it's now possible to show a QR-Code for API keys (thanks @fipwmaqzufheoxq92ebc)
|
||||
- New QR-Code button on the "Manage API keys"-page (top right corner settings menu), the QR-Codes contains `<API-Url>|<API-Key>`
|
||||
- And on the calendar page when using the button "Share/Integrate calendar (iCal)", there the QR-Codes contains the Share-URL (which is displayed in the textbox above)
|
||||
@ -174,7 +189,7 @@ _- (Because the stock quantity unit is now the base for everything, it cannot be
|
||||
- `<=` less or equal
|
||||
- `<value>` is the value to search for
|
||||
- New endpoints `/stock/journal` and `/stock/journal/summary` to get the stock journal (thanks @fipwmaqzufheoxq92ebc)
|
||||
- New endpoint `/stock/shoppinglist/add-expired-products` to add all currently in-stock but expired products to a shopping list (thanks @m-byte)
|
||||
- New endpoint `/stock/shoppinglist/add-overdue-products` to add all currently in-stock but overdue products to a shopping list (thanks @m-byte)
|
||||
- New endpoints GET/POST/PUT `/users/{userId}/permissions` for the new user permissions feature mentioned above
|
||||
- Performance improvements of the `/stock/products/*` endpoints (thanks @fipwmaqzufheoxq92ebc)
|
||||
- Fixed that the endpoint `/objects/{entity}/{objectId}` always returned successfully, even when the given object not exists (now returns `404` when the object is not found) (thanks @fipwmaqzufheoxq92ebc)
|
||||
|
@ -113,17 +113,17 @@ DefaultUserSetting('product_presets_product_group_id', -1); // Default product g
|
||||
DefaultUserSetting('product_presets_qu_id', -1); // Default quantity unit id for new products (-1 means no quantity unit is preset)
|
||||
DefaultUserSetting('stock_decimal_places_amounts', 4); // Default decimal places allowed for amounts
|
||||
DefaultUserSetting('stock_decimal_places_prices', 2); // Default decimal places allowed for prices
|
||||
DefaultUserSetting('stock_expiring_soon_days', 5);
|
||||
DefaultUserSetting('stock_due_soon_days', 5);
|
||||
DefaultUserSetting('stock_default_purchase_amount', 0);
|
||||
DefaultUserSetting('stock_default_consume_amount', 1);
|
||||
DefaultUserSetting('scan_mode_consume_enabled', false);
|
||||
DefaultUserSetting('scan_mode_purchase_enabled', false);
|
||||
DefaultUserSetting('show_icon_on_stock_overview_page_when_product_is_on_shopping_list', true);
|
||||
DefaultUserSetting('show_purchased_date_on_purchase', false); // Wheter the purchased date should be editable on purchase (defaults to today otherwise)
|
||||
DefaultUserSetting('show_warning_on_purchase_when_best_before_date_is_earlier_than_next', true); // Show a warning on purchase when the best before date of the purchased product is earlier than the next best before date in stock
|
||||
DefaultUserSetting('show_warning_on_purchase_when_due_date_is_earlier_than_next', true); // Show a warning on purchase when the due date of the purchased product is earlier than the next due date in stock
|
||||
|
||||
// Shopping list settings
|
||||
DefaultUserSetting('shopping_list_to_stock_workflow_auto_submit_when_prefilled', false); // Automatically do the booking using the last price and the amount of the shopping list item, if the product has "Default best before days" set
|
||||
DefaultUserSetting('shopping_list_to_stock_workflow_auto_submit_when_prefilled', false); // Automatically do the booking using the last price and the amount of the shopping list item, if the product has "Default due days" set
|
||||
DefaultUserSetting('shopping_list_show_calendar', false);
|
||||
|
||||
// Recipe settings
|
||||
@ -174,7 +174,7 @@ Setting('FEATURE_FLAG_STOCK_LOCATION_TRACKING', true);
|
||||
Setting('FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING', true);
|
||||
Setting('FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING', true);
|
||||
Setting('FEATURE_FLAG_STOCK_PRODUCT_FREEZING', true);
|
||||
Setting('FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_FIELD_NUMBER_PAD', true); // Activate the number pad in best-before-date fields on (supported) mobile browsers
|
||||
Setting('FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_FIELD_NUMBER_PAD', true); // Activate the number pad in due date fields on (supported) mobile browsers
|
||||
Setting('FEATURE_FLAG_SHOPPINGLIST_MULTIPLE_LISTS', true);
|
||||
Setting('FEATURE_FLAG_CHORES_ASSIGNMENTS', true);
|
||||
|
||||
|
@ -31,7 +31,7 @@ class StockApiController extends BaseApiController
|
||||
}
|
||||
}
|
||||
|
||||
public function AddExpiredProductsToShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
public function AddOverdueProductsToShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_ADD);
|
||||
|
||||
@ -46,7 +46,7 @@ class StockApiController extends BaseApiController
|
||||
$listId = intval($requestBody['list_id']);
|
||||
}
|
||||
|
||||
$this->getStockService()->AddExpiredProductsToShoppingList($listId);
|
||||
$this->getStockService()->AddOverdueProductsToShoppingList($listId);
|
||||
return $this->EmptyApiResponse($response);
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
@ -304,16 +304,18 @@ class StockApiController extends BaseApiController
|
||||
{
|
||||
$nextXDays = 5;
|
||||
|
||||
if (isset($request->getQueryParams()['expiring_days']) && !empty($request->getQueryParams()['expiring_days']) && is_numeric($request->getQueryParams()['expiring_days']))
|
||||
if (isset($request->getQueryParams()['due_soon_days']) && !empty($request->getQueryParams()['due_soon_days']) && is_numeric($request->getQueryParams()['due_soon_days']))
|
||||
{
|
||||
$nextXDays = $request->getQueryParams()['expiring_days'];
|
||||
$nextXDays = $request->getQueryParams()['due_soon_days'];
|
||||
}
|
||||
|
||||
$expiringProducts = $this->getStockService()->GetExpiringProducts($nextXDays, true);
|
||||
$expiredProducts = $this->getStockService()->GetExpiringProducts(-1);
|
||||
$dueProducts = $this->getStockService()->GetDueProducts($nextXDays, true);
|
||||
$overdueProducts = $this->getStockService()->GetDueProducts(-1);
|
||||
$expiredProducts = $this->getStockService()->GetExpiredProducts();
|
||||
$missingProducts = $this->getStockService()->GetMissingProducts();
|
||||
return $this->ApiResponse($response, [
|
||||
'expiring_products' => $expiringProducts,
|
||||
'due_products' => $dueProducts,
|
||||
'overdue_products' => $overdueProducts,
|
||||
'expired_products' => $expiredProducts,
|
||||
'missing_products' => $missingProducts
|
||||
]);
|
||||
|
@ -85,7 +85,7 @@ class StockController extends BaseController
|
||||
public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$usersService = $this->getUsersService();
|
||||
$nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['stock_expiring_soon_days'];
|
||||
$nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['stock_due_soon_days'];
|
||||
|
||||
return $this->renderPage($response, 'stockoverview', [
|
||||
'currentStock' => $this->getStockService()->GetCurrentStockOverview(),
|
||||
@ -425,7 +425,7 @@ class StockController extends BaseController
|
||||
public function Stockentries(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$usersService = $this->getUsersService();
|
||||
$nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['stock_expiring_soon_days'];
|
||||
$nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['stock_due_soon_days'];
|
||||
|
||||
return $this->renderPage($response, 'stockentries', [
|
||||
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
|
||||
|
@ -1258,7 +1258,7 @@
|
||||
},
|
||||
"/stock": {
|
||||
"get": {
|
||||
"summary": "Returns all products which are currently in stock incl. the next expiring date per product",
|
||||
"summary": "Returns all products which are currently in stock incl. the next due date per product",
|
||||
"tags": [
|
||||
"Stock"
|
||||
],
|
||||
@ -1350,7 +1350,7 @@
|
||||
"best_before_date": {
|
||||
"type": "string",
|
||||
"format": "date",
|
||||
"description": "The best before date of the product to add, when omitted, the current date is used"
|
||||
"description": "The due date of the product to add, when omitted, the current date is used"
|
||||
},
|
||||
"price": {
|
||||
"type": "number",
|
||||
@ -1416,16 +1416,16 @@
|
||||
},
|
||||
"/stock/volatile": {
|
||||
"get": {
|
||||
"summary": "Returns all products which are expiring soon, are already expired or currently missing",
|
||||
"summary": "Returns all products which are due soon, overdue, expired or currently missing",
|
||||
"tags": [
|
||||
"Stock"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "expiring_days",
|
||||
"name": "due_soon_days",
|
||||
"required": false,
|
||||
"description": "The number of days in which products are considered expiring soon",
|
||||
"description": "The number of days in which products are considered to be due soon",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 5
|
||||
@ -1558,7 +1558,7 @@
|
||||
},
|
||||
"/stock/products/{productId}/entries": {
|
||||
"get": {
|
||||
"summary": "Returns all stock entries of the given product in order of next use (first expiring first, then first in first out)",
|
||||
"summary": "Returns all stock entries of the given product in order of next use (first due first, then first in first out)",
|
||||
"tags": [
|
||||
"Stock"
|
||||
],
|
||||
@ -1707,7 +1707,7 @@
|
||||
"best_before_date": {
|
||||
"type": "string",
|
||||
"format": "date",
|
||||
"description": "The best before date of the product to add, when omitted, the current date is used"
|
||||
"description": "The due date of the product to add, when omitted, the current date is used"
|
||||
},
|
||||
"transaction_type": {
|
||||
"$ref": "#/components/internalSchemas/StockTransactionType"
|
||||
@ -1956,7 +1956,7 @@
|
||||
"best_before_date": {
|
||||
"type": "string",
|
||||
"format": "date",
|
||||
"description": "The best before date which applies to added products"
|
||||
"description": "The due date which applies to added products"
|
||||
},
|
||||
"shopping_location_id": {
|
||||
"type": "number",
|
||||
@ -2139,7 +2139,7 @@
|
||||
"best_before_date": {
|
||||
"type": "string",
|
||||
"format": "date",
|
||||
"description": "The best before date of the product to add, when omitted, the current date is used"
|
||||
"description": "The due date of the product to add, when omitted, the current date is used"
|
||||
},
|
||||
"transaction_type": {
|
||||
"$ref": "#/components/internalSchemas/StockTransactionType"
|
||||
@ -2383,7 +2383,7 @@
|
||||
"best_before_date": {
|
||||
"type": "string",
|
||||
"format": "date",
|
||||
"description": "The best before date which applies to added products"
|
||||
"description": "The due date which applies to added products"
|
||||
},
|
||||
"location_id": {
|
||||
"type": "number",
|
||||
@ -2530,9 +2530,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/stock/shoppinglist/add-expired-products": {
|
||||
"/stock/shoppinglist/add-overdue-products": {
|
||||
"post": {
|
||||
"summary": "Adds expired products to the given shopping list",
|
||||
"summary": "Adds overdue products to the given shopping list",
|
||||
"tags": [
|
||||
"Stock"
|
||||
],
|
||||
@ -4195,7 +4195,7 @@
|
||||
"stock_amount_opened": {
|
||||
"type": "integer"
|
||||
},
|
||||
"next_best_before_date": {
|
||||
"next_due_date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
@ -4282,7 +4282,7 @@
|
||||
"avg_price": null,
|
||||
"oldest_price": null,
|
||||
"last_shopping_location_id": null,
|
||||
"next_best_before_date": "2019-07-07",
|
||||
"next_due_date": "2019-07-07",
|
||||
"location": {
|
||||
"id": "4",
|
||||
"name": "Candy cupboard",
|
||||
@ -4893,7 +4893,7 @@
|
||||
"best_before_date": {
|
||||
"type": "string",
|
||||
"format": "date",
|
||||
"description": "The next best before date for this product"
|
||||
"description": "The next due date for this product"
|
||||
},
|
||||
"is_aggregated_amount": {
|
||||
"type": "boolean",
|
||||
@ -4941,7 +4941,13 @@
|
||||
"CurrentVolatilStockResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expiring_products": {
|
||||
"due_products": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/CurrentStockResponse"
|
||||
}
|
||||
},
|
||||
"overdue_products": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/CurrentStockResponse"
|
||||
|
@ -20,6 +20,11 @@ msgid_plural "%s products expiring"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%s is due"
|
||||
msgid_plural "%s are due"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "within the next day"
|
||||
msgid_plural "within the next %s days"
|
||||
msgstr[0] ""
|
||||
@ -30,6 +35,11 @@ msgid_plural "%s products are already expired"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%s product is overdue"
|
||||
msgid_plural "%s products are overdue"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%s product is below defined min. stock amount"
|
||||
msgid_plural "%s products are below defined min. stock amount"
|
||||
msgstr[0] ""
|
||||
@ -46,9 +56,6 @@ msgstr[1] ""
|
||||
msgid "Amount"
|
||||
msgstr ""
|
||||
|
||||
msgid "Next best before date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Logout"
|
||||
msgstr ""
|
||||
|
||||
@ -262,9 +269,6 @@ msgstr ""
|
||||
msgid "Add products that are below defined min. stock amount"
|
||||
msgstr ""
|
||||
|
||||
msgid "For purchases this amount of days will be added to today for the best before date suggestion"
|
||||
msgstr ""
|
||||
|
||||
msgid "This means 1 %1$s purchased will be converted into %2$s %3$s in stock"
|
||||
msgstr ""
|
||||
|
||||
@ -598,15 +602,9 @@ msgstr[1] ""
|
||||
msgid "in singular form"
|
||||
msgstr ""
|
||||
|
||||
msgid "Never expires"
|
||||
msgstr ""
|
||||
|
||||
msgid "This cannot be lower than %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "-1 means that this product never expires"
|
||||
msgstr ""
|
||||
|
||||
msgid "Quantity unit"
|
||||
msgstr ""
|
||||
|
||||
@ -869,18 +867,12 @@ msgstr ""
|
||||
msgid "Use a specific stock item"
|
||||
msgstr ""
|
||||
|
||||
msgid "The first item in this list would be picked by the default rule which is \"First expiring first, then first in first out\""
|
||||
msgid "The first item in this list would be picked by the default rule which is \"First due first, then first in first out\""
|
||||
msgstr ""
|
||||
|
||||
msgid "Mark %1$s of %2$s as open"
|
||||
msgstr ""
|
||||
|
||||
msgid "When this product was marked as opened, the best before date will be replaced by today + this amount of days (a value of 0 disables this)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default best before days after opened"
|
||||
msgstr ""
|
||||
|
||||
msgid "Marked %1$s of %2$s as opened"
|
||||
msgstr ""
|
||||
|
||||
@ -896,7 +888,7 @@ msgstr ""
|
||||
msgid "%s opened"
|
||||
msgstr ""
|
||||
|
||||
msgid "Product expires"
|
||||
msgid "Product due"
|
||||
msgstr ""
|
||||
|
||||
msgid "Task due"
|
||||
@ -1194,15 +1186,6 @@ msgstr ""
|
||||
msgid "A predefined list of values, one per line"
|
||||
msgstr ""
|
||||
|
||||
msgid "Chores due soon days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Batteries due to be charged soon days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tasks due soon days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Products"
|
||||
msgstr ""
|
||||
|
||||
@ -1520,7 +1503,7 @@ msgstr ""
|
||||
msgid "There are no units available at this location"
|
||||
msgstr ""
|
||||
|
||||
msgid "Amount: %1$s; Expires on %2$s; Bought on %3$s"
|
||||
msgid "Amount: %1$s; Due on %2$s; Bought on %3$s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Transfered %1$s of %2$s from %3$s to %4$s"
|
||||
@ -1625,18 +1608,6 @@ msgstr ""
|
||||
msgid "When moving products from/to a freezer location, the products best before date is automatically adjusted according to the product settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "On moving this product to a freezer location (so when freezing it), the best before date will be replaced by today + this amount of days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default best before days after freezing"
|
||||
msgstr ""
|
||||
|
||||
msgid "On moving this product from a freezer location (so when thawing it), the best before date will be replaced by today + this amount of days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default best before days after thawing"
|
||||
msgstr ""
|
||||
|
||||
msgid "This cannot be the same as the \"From\" location"
|
||||
msgstr ""
|
||||
|
||||
@ -1757,7 +1728,7 @@ msgstr ""
|
||||
msgid "Not enough in stock (not included in costs), %s ingredient missing"
|
||||
msgstr ""
|
||||
|
||||
msgid "Based on the prices of the default consume rule which is \"First expiring first, then first in first out\""
|
||||
msgid "Based on the prices of the default consume rule which is \"First due first, then first in first out\""
|
||||
msgstr ""
|
||||
|
||||
msgid "Not enough in stock (not included in costs), %1$s missing, %2$s already on shopping list"
|
||||
@ -1928,10 +1899,10 @@ msgstr ""
|
||||
msgid "This is the default quantity unit used when adding this product to the shopping list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show a warning when the best before date of the purchased product is earlier than the next best before date in stock"
|
||||
msgid "Show a warning when the due date of the purchased product is earlier than the next due date in stock"
|
||||
msgstr ""
|
||||
|
||||
msgid "There are items in stock which expire earlier"
|
||||
msgid "There are items in stock which are due earlier"
|
||||
msgstr ""
|
||||
|
||||
msgid "When enabled, the amount will always be filled with 1 after changing/scanning a product and if all fields could be automatically populated (by product defaults), the transaction is automatically submitted"
|
||||
@ -1948,3 +1919,70 @@ msgstr ""
|
||||
|
||||
msgid "Are you sure to remove this barcode?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Due date type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Based on the selected type, the highlighting on the stock overview page will be different"
|
||||
msgstr ""
|
||||
|
||||
msgid "Means that the product is maybe still safe to be consumed after its due date is reached"
|
||||
msgstr ""
|
||||
|
||||
msgid "Expiration date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Means that the product is not safe to be consumed after its due date is reached"
|
||||
msgstr ""
|
||||
|
||||
msgid "For purchases this amount of days will be added to today for the due date suggestion"
|
||||
msgstr ""
|
||||
|
||||
msgid "-1 means that this product wille be never overdue"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default due days"
|
||||
msgstr ""
|
||||
|
||||
msgid "When this product was marked as opened, the expiry date will be replaced by today + this amount of days (a value of 0 disables this)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default due days after opened"
|
||||
msgstr ""
|
||||
|
||||
msgid "On moving this product to a freezer location (so when freezing it), the expiry date will be replaced by today + this amount of days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default due days after freezing"
|
||||
msgstr ""
|
||||
|
||||
msgid "On moving this product from a freezer location (so when thawing it), the due date will be replaced by today + this amount of days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default due days after thawing"
|
||||
msgstr ""
|
||||
|
||||
msgid "Next due date"
|
||||
msgstr ""
|
||||
|
||||
msgid "%s product is due"
|
||||
msgid_plural "%s products are due"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "Due date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Never overdue"
|
||||
msgstr ""
|
||||
|
||||
msgid "%s product is expired"
|
||||
msgid_plural "%s products are expired"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "Expired"
|
||||
msgstr ""
|
||||
|
||||
msgid "Due soon days"
|
||||
msgstr ""
|
||||
|
@ -68,6 +68,7 @@ CREATE TABLE products (
|
||||
parent_product_id INT,
|
||||
calories INTEGER,
|
||||
cumulate_min_stock_amount_of_sub_products TINYINT DEFAULT 0,
|
||||
due_type TINYINT NOT NULL DEFAULT 1 CHECK(due_type IN (1, 2)),
|
||||
quick_consume_amount REAL NOT NULL DEFAULT 1,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
@ -23,15 +23,18 @@ SELECT
|
||||
p.calories AS product_calories,
|
||||
sc.amount * p.calories AS calories,
|
||||
sc.amount_aggregated * p.calories AS calories_aggregated,
|
||||
p.quick_consume_amount
|
||||
p.quick_consume_amount,
|
||||
p.due_type
|
||||
FROM (
|
||||
SELECT *
|
||||
FROM stock_current
|
||||
WHERE best_before_date IS NOT NULL
|
||||
UNION
|
||||
SELECT id, 0, 0, 0, null, 0, 0, 0
|
||||
FROM stock_missing_products_including_opened
|
||||
WHERE id NOT IN (SELECT product_id FROM stock_current)
|
||||
SELECT m.id, 0, 0, 0, null, 0, 0, 0, p.due_type
|
||||
FROM stock_missing_products_including_opened m
|
||||
JOIN products p
|
||||
ON m.id = p.id
|
||||
WHERE m.id NOT IN (SELECT product_id FROM stock_current)
|
||||
) sc
|
||||
LEFT JOIN products p
|
||||
ON sc.product_id = p.id;
|
||||
@ -61,15 +64,18 @@ SELECT
|
||||
p.calories AS product_calories,
|
||||
sc.amount * p.calories AS calories,
|
||||
sc.amount_aggregated * p.calories AS calories_aggregated,
|
||||
p.quick_consume_amount
|
||||
p.quick_consume_amount,
|
||||
p.due_type
|
||||
FROM (
|
||||
SELECT *
|
||||
FROM stock_current
|
||||
WHERE best_before_date IS NOT NULL
|
||||
UNION
|
||||
SELECT id, 0, 0, 0, null, 0, 0, 0
|
||||
FROM stock_missing_products
|
||||
WHERE id NOT IN (SELECT product_id FROM stock_current)
|
||||
SELECT m.id, 0, 0, 0, null, 0, 0, 0, p.due_type
|
||||
FROM stock_missing_products m
|
||||
JOIN products p
|
||||
ON m.id = p.id
|
||||
WHERE m.id NOT IN (SELECT product_id FROM stock_current)
|
||||
) sc
|
||||
LEFT JOIN products p
|
||||
ON sc.product_id = p.id;
|
||||
|
@ -9,7 +9,8 @@ SELECT
|
||||
MIN(s.best_before_date) AS best_before_date,
|
||||
IFNULL((SELECT SUM(amount) FROM stock WHERE product_id = pr.parent_product_id AND open = 1), 0) AS amount_opened,
|
||||
IFNULL((SELECT SUM(amount) FROM stock WHERE product_id IN (SELECT sub_product_id FROM products_resolved WHERE parent_product_id = pr.parent_product_id) AND open = 1), 0) * IFNULL(qucr.factor, 1) AS amount_opened_aggregated,
|
||||
CASE WHEN COUNT(p_sub.parent_product_id) > 0 THEN 1 ELSE 0 END AS is_aggregated_amount
|
||||
CASE WHEN COUNT(p_sub.parent_product_id) > 0 THEN 1 ELSE 0 END AS is_aggregated_amount,
|
||||
MAX(p_parent.due_type) AS due_type
|
||||
FROM products_resolved pr
|
||||
JOIN stock s
|
||||
ON pr.sub_product_id = s.product_id
|
||||
@ -37,10 +38,14 @@ SELECT
|
||||
MIN(s.best_before_date) AS best_before_date,
|
||||
IFNULL((SELECT SUM(amount) FROM stock WHERE product_id = s.product_id AND open = 1), 0) AS amount_opened,
|
||||
IFNULL((SELECT SUM(amount) FROM stock WHERE product_id = s.product_id AND open = 1), 0) AS amount_opened_aggregated,
|
||||
0 AS is_aggregated_amount
|
||||
0 AS is_aggregated_amount,
|
||||
MAX(p_sub.due_type) AS due_type
|
||||
FROM products_resolved pr
|
||||
JOIN stock s
|
||||
ON pr.sub_product_id = s.product_id
|
||||
JOIN products p_sub
|
||||
ON pr.sub_product_id = p_sub.id
|
||||
AND p_sub.active = 1
|
||||
WHERE pr.parent_product_id != pr.sub_product_id
|
||||
GROUP BY pr.sub_product_id
|
||||
HAVING SUM(s.amount) > 0;
|
||||
|
@ -1 +1 @@
|
||||
update user_settings set key = "stock_expiring_soon_days" where key = "stock_expring_soon_days";
|
||||
update user_settings set key = "stock_due_soon_days" where key = "stock_expring_soon_days";
|
||||
|
@ -460,7 +460,8 @@ canvas.drawingBuffer {
|
||||
|
||||
.warning-message,
|
||||
.error-message,
|
||||
.normal-message {
|
||||
.normal-message,
|
||||
.secondary-message {
|
||||
padding: 12px;
|
||||
font-weight: bold;
|
||||
width: fit-content;
|
||||
@ -485,6 +486,12 @@ canvas.drawingBuffer {
|
||||
border-top-color: #4c63b6;
|
||||
}
|
||||
|
||||
.secondary-message {
|
||||
background-color: #e1e4e8;
|
||||
color: #4e575f;
|
||||
border-top-color: #68696b;
|
||||
}
|
||||
|
||||
.status-filter-message,
|
||||
.user-filter-message {
|
||||
display: inline-block;
|
||||
|
@ -219,7 +219,7 @@ $("#location_id").on('change', function(e)
|
||||
$("#specific_stock_entry").append($("<option>", {
|
||||
value: stockEntry.stock_id,
|
||||
amount: stockEntry.amount,
|
||||
text: __t("Amount: %1$s; Expires on %2$s; Bought on %3$s", stockEntry.amount, moment(stockEntry.best_before_date).format("YYYY-MM-DD"), moment(stockEntry.purchased_date).format("YYYY-MM-DD")) + "; " + openTxt
|
||||
text: __t("Amount: %1$s; Due on %2$s; Bought on %3$s", stockEntry.amount, moment(stockEntry.best_before_date).format("YYYY-MM-DD"), moment(stockEntry.purchased_date).format("YYYY-MM-DD")) + "; " + openTxt
|
||||
}));
|
||||
|
||||
sumValue = sumValue + parseFloat(stockEntry.amount);
|
||||
|
@ -99,11 +99,11 @@ $('#save-purchase-button').on('click', function(e)
|
||||
toastr.success(successMessage);
|
||||
Grocy.Components.ProductPicker.FinishFlow();
|
||||
|
||||
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && BoolVal(Grocy.UserSettings.show_warning_on_purchase_when_best_before_date_is_earlier_than_next))
|
||||
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && BoolVal(Grocy.UserSettings.show_warning_on_purchase_when_due_date_is_earlier_than_next))
|
||||
{
|
||||
if (moment(jsonData.best_before_date).isBefore(CurrentProductDetails.next_best_before_date))
|
||||
if (moment(jsonData.best_before_date).isBefore(CurrentProductDetails.next_due_date))
|
||||
{
|
||||
toastr.warning(__t("There are items in stock which expire earlier"));
|
||||
toastr.warning(__t("There are items in stock which are due earlier"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,9 +174,9 @@ $(document).on('click', '#add-products-below-min-stock-amount', function(e)
|
||||
);
|
||||
});
|
||||
|
||||
$(document).on('click', '#add-expired-products', function(e)
|
||||
$(document).on('click', '#add-overdue-products', function(e)
|
||||
{
|
||||
Grocy.Api.Post('stock/shoppinglist/add-expired-products', { "list_id": $("#selected-shopping-list").val() },
|
||||
Grocy.Api.Post('stock/shoppinglist/add-overdue-products', { "list_id": $("#selected-shopping-list").val() },
|
||||
function(result)
|
||||
{
|
||||
window.location.href = U('/shoppinglist?list=' + $("#selected-shopping-list").val());
|
||||
|
@ -148,7 +148,7 @@ function RefreshStockEntryRow(stockRowId)
|
||||
}
|
||||
else
|
||||
{
|
||||
var expiringThreshold = moment().add(Grocy.UserSettings.stock_expiring_soon_days, "days");
|
||||
var dueThreshold = moment().add(Grocy.UserSettings.stock_due_soon_days, "days");
|
||||
var now = moment();
|
||||
var bestBeforeDate = moment(result.best_before_date);
|
||||
|
||||
@ -159,9 +159,16 @@ function RefreshStockEntryRow(stockRowId)
|
||||
stockRow.removeAttr("style");
|
||||
if (now.isAfter(bestBeforeDate))
|
||||
{
|
||||
stockRow.addClass("table-danger");
|
||||
if (stockRow.attr("data-due-type") == 1)
|
||||
{
|
||||
stockRow.addClass("table-secondary");
|
||||
}
|
||||
else
|
||||
{
|
||||
stockRow.addClass("table-danger");
|
||||
}
|
||||
}
|
||||
else if (bestBeforeDate.isBefore(expiringThreshold))
|
||||
else if (bestBeforeDate.isBefore(dueThreshold))
|
||||
{
|
||||
stockRow.addClass("table-warning");
|
||||
}
|
||||
@ -169,8 +176,8 @@ function RefreshStockEntryRow(stockRowId)
|
||||
animateCSS("#stock-" + stockRowId + "-row td:not(:first)", "shake");
|
||||
|
||||
$('#stock-' + stockRowId + '-amount').text(result.amount);
|
||||
$('#stock-' + stockRowId + '-best-before-date').text(result.best_before_date);
|
||||
$('#stock-' + stockRowId + '-best-before-date-timeago').attr('datetime', result.best_before_date + ' 23:59:59');
|
||||
$('#stock-' + stockRowId + '-due-date').text(result.best_before_date);
|
||||
$('#stock-' + stockRowId + '-due-date-timeago').attr('datetime', result.best_before_date + ' 23:59:59');
|
||||
|
||||
$(".stock-consume-button").attr('data-location-id', result.location_id);
|
||||
|
||||
|
@ -233,12 +233,13 @@ function RefreshStatistics()
|
||||
}
|
||||
);
|
||||
|
||||
var nextXDays = $("#info-expiring-products").data("next-x-days");
|
||||
Grocy.Api.Get('stock/volatile?expiring_days=' + nextXDays,
|
||||
var nextXDays = $("#info-duesoon-products").data("next-x-days");
|
||||
Grocy.Api.Get('stock/volatile?due_soon_days=' + nextXDays,
|
||||
function(result)
|
||||
{
|
||||
$("#info-expiring-products").html('<span class="d-block d-md-none">' + result.expiring_products.length + ' <i class="fas fa-clock"></i></span><span class="d-none d-md-block">' + __n(result.expiring_products.length, '%s product expires', '%s products expiring') + ' ' + __n(nextXDays, 'within the next day', 'within the next %s days') + '</span>');
|
||||
$("#info-expired-products").html('<span class="d-block d-md-none">' + result.expired_products.length + ' <i class="fas fa-times-circle"></i></span><span class="d-none d-md-block">' + __n(result.expired_products.length, '%s product is already expired', '%s products are already expired') + '</span>');
|
||||
$("#info-duesoon-products").html('<span class="d-block d-md-none">' + result.due_products.length + ' <i class="fas fa-clock"></i></span><span class="d-none d-md-block">' + __n(result.due_products.length, '%s product is due', '%s products are due') + ' ' + __n(nextXDays, 'within the next day', 'within the next %s days') + '</span>');
|
||||
$("#info-overdue-products").html('<span class="d-block d-md-none">' + result.overdue_products.length + ' <i class="fas fa-times-circle"></i></span><span class="d-none d-md-block">' + __n(result.overdue_products.length, '%s product is overdue', '%s products are overdue') + '</span>');
|
||||
$("#info-expired-products").html('<span class="d-block d-md-none">' + result.expired_products.length + ' <i class="fas fa-times-circle"></i></span><span class="d-none d-md-block">' + __n(result.expired_products.length, '%s product is expired', '%s products are expired') + '</span>');
|
||||
$("#info-missing-products").html('<span class="d-block d-md-none">' + result.missing_products.length + ' <i class="fas fa-exclamation-circle"></i></span><span class="d-none d-md-block">' + __n(result.missing_products.length, '%s product is below defined min. stock amount', '%s products are below defined min. stock amount') + '</span>');
|
||||
},
|
||||
function(xhr)
|
||||
@ -263,20 +264,28 @@ function RefreshProductRow(productId)
|
||||
}
|
||||
|
||||
var productRow = $('#product-' + productId + '-row');
|
||||
var expiringThreshold = moment().add($("#info-expiring-products").data("next-x-days"), "days");
|
||||
var dueSoonThreshold = moment().add($("#info-duesoon-products").data("next-x-days"), "days");
|
||||
var now = moment();
|
||||
var nextBestBeforeDate = moment(result.next_best_before_date);
|
||||
var nextDueDate = moment(result.next_due_date);
|
||||
|
||||
productRow.removeClass("table-warning");
|
||||
productRow.removeClass("table-danger");
|
||||
productRow.removeClass("table-secondary");
|
||||
productRow.removeClass("table-info");
|
||||
productRow.removeClass("d-none");
|
||||
productRow.removeAttr("style");
|
||||
if (now.isAfter(nextBestBeforeDate))
|
||||
if (now.isAfter(nextDueDate))
|
||||
{
|
||||
productRow.addClass("table-danger");
|
||||
if (result.product.due_type == 1)
|
||||
{
|
||||
productRow.addClass("table-secondary");
|
||||
}
|
||||
else
|
||||
{
|
||||
productRow.addClass("table-danger");
|
||||
}
|
||||
}
|
||||
else if (nextBestBeforeDate.isBefore(expiringThreshold))
|
||||
else if (nextDueDate.isBefore(dueSoonThreshold))
|
||||
{
|
||||
productRow.addClass("table-warning");
|
||||
}
|
||||
@ -297,8 +306,8 @@ function RefreshProductRow(productId)
|
||||
$('#product-' + productId + '-amount').text(result.stock_amount);
|
||||
$('#product-' + productId + '-consume-all-button').attr('data-consume-amount', result.stock_amount);
|
||||
$('#product-' + productId + '-value').text(result.stock_value);
|
||||
$('#product-' + productId + '-next-best-before-date').text(result.next_best_before_date);
|
||||
$('#product-' + productId + '-next-best-before-date-timeago').attr('datetime', result.next_best_before_date);
|
||||
$('#product-' + productId + '-next-due-date').text(result.next_due_date);
|
||||
$('#product-' + productId + '-next-due-date-timeago').attr('datetime', result.next_due_date);
|
||||
|
||||
var openedAmount = result.stock_amount_opened || 0;
|
||||
if (openedAmount > 0)
|
||||
@ -316,8 +325,8 @@ function RefreshProductRow(productId)
|
||||
}
|
||||
}
|
||||
|
||||
$('#product-' + productId + '-next-best-before-date').text(result.next_best_before_date);
|
||||
$('#product-' + productId + '-next-best-before-date-timeago').attr('datetime', result.next_best_before_date + ' 23:59:59');
|
||||
$('#product-' + productId + '-next-due-date').text(result.next_due_date);
|
||||
$('#product-' + productId + '-next-due-date-timeago').attr('datetime', result.next_due_date + ' 23:59:59');
|
||||
|
||||
if (result.stock_amount_opened > 0)
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
$("#product_presets_location_id").val(Grocy.UserSettings.product_presets_location_id);
|
||||
$("#product_presets_product_group_id").val(Grocy.UserSettings.product_presets_product_group_id);
|
||||
$("#product_presets_qu_id").val(Grocy.UserSettings.product_presets_qu_id);
|
||||
$("#stock_expiring_soon_days").val(Grocy.UserSettings.stock_expiring_soon_days);
|
||||
$("#stock_due_soon_days").val(Grocy.UserSettings.stock_due_soon_days);
|
||||
$("#stock_default_purchase_amount").val(Grocy.UserSettings.stock_default_purchase_amount);
|
||||
$("#stock_default_consume_amount").val(Grocy.UserSettings.stock_default_consume_amount);
|
||||
$("#stock_decimal_places_amounts").val(Grocy.UserSettings.stock_decimal_places_amounts);
|
||||
@ -16,9 +16,9 @@ if (BoolVal(Grocy.UserSettings.show_purchased_date_on_purchase))
|
||||
$("#show_purchased_date_on_purchase").prop("checked", true);
|
||||
}
|
||||
|
||||
if (BoolVal(Grocy.UserSettings.show_warning_on_purchase_when_best_before_date_is_earlier_than_next))
|
||||
if (BoolVal(Grocy.UserSettings.show_warning_on_purchase_when_due_date_is_earlier_than_next))
|
||||
{
|
||||
$("#show_warning_on_purchase_when_best_before_date_is_earlier_than_next").prop("checked", true);
|
||||
$("#show_warning_on_purchase_when_due_date_is_earlier_than_next").prop("checked", true);
|
||||
}
|
||||
|
||||
RefreshLocaleNumberInput();
|
||||
|
@ -278,7 +278,7 @@ $("#location_id_from").on('change', function(e)
|
||||
$("#specific_stock_entry").append($("<option>", {
|
||||
value: stockEntry.stock_id,
|
||||
amount: stockEntry.amount,
|
||||
text: __t("Amount: %1$s; Expires on %2$s; Bought on %3$s", stockEntry.amount, moment(stockEntry.best_before_date).format("YYYY-MM-DD"), moment(stockEntry.purchased_date).format("YYYY-MM-DD")) + "; " + openTxt
|
||||
text: __t("Amount: %1$s; Due on %2$s; Bought on %3$s", stockEntry.amount, moment(stockEntry.best_before_date).format("YYYY-MM-DD"), moment(stockEntry.purchased_date).format("YYYY-MM-DD")) + "; " + openTxt
|
||||
}));
|
||||
if (stockEntry.stock_id == stockId)
|
||||
{
|
||||
|
@ -210,7 +210,7 @@ $app->group('/api', function (RouteCollectorProxy $group) {
|
||||
if (GROCY_FEATURE_FLAG_SHOPPINGLIST)
|
||||
{
|
||||
$group->post('/stock/shoppinglist/add-missing-products', '\Grocy\Controllers\StockApiController:AddMissingProductsToShoppingList');
|
||||
$group->post('/stock/shoppinglist/add-expired-products', '\Grocy\Controllers\StockApiController:AddExpiredProductsToShoppingList');
|
||||
$group->post('/stock/shoppinglist/add-overdue-products', '\Grocy\Controllers\StockApiController:AddOverdueProductsToShoppingList');
|
||||
$group->post('/stock/shoppinglist/clear', '\Grocy\Controllers\StockApiController:ClearShoppingList');
|
||||
$group->post('/stock/shoppinglist/add-product', '\Grocy\Controllers\StockApiController:AddProductToShoppingList');
|
||||
$group->post('/stock/shoppinglist/remove-product', '\Grocy\Controllers\StockApiController:RemoveProductFromShoppingList');
|
||||
|
@ -13,7 +13,7 @@ class CalendarService extends BaseService
|
||||
if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
|
||||
{
|
||||
$products = $this->getDatabase()->products();
|
||||
$titlePrefix = $this->getLocalizationService()->__t('Product expires') . ': ';
|
||||
$titlePrefix = $this->getLocalizationService()->__t('Product due') . ': ';
|
||||
|
||||
foreach ($this->getStockService()->GetCurrentStock() as $currentStockEntry)
|
||||
{
|
||||
|
@ -85,7 +85,7 @@ class DemoDataGeneratorService extends BaseService
|
||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$this->__t_sql('Sieved tomatoes')}', 5, 5, 5, 1, 3); --17
|
||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$this->__t_sql('Salami')}', 2, 3, 3, 1, 6); --18
|
||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$this->__t_sql('Toast')}', 3, 5, 5, 1, 2); --19
|
||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, default_best_before_days_after_freezing, default_best_before_days_after_thawing) VALUES ('{$this->__t_sql('Minced meat')}', 2, 3, 3, 1, 4, 180, 2); --20
|
||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, default_best_before_days_after_freezing, default_best_before_days_after_thawing, due_type) VALUES ('{$this->__t_sql('Minced meat')}', 2, 3, 3, 1, 4, 180, 2, 2); --20
|
||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, enable_tare_weight_handling, tare_weight, calories) VALUES ('{$this->__t_sql('Flour')}', 3, 8, 8, 1, 3, 1, 500, 2); --21
|
||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, calories) VALUES ('{$this->__t_sql('Sugar')}', 3, 3, 3, 1, 3, 4); --22
|
||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, calories) VALUES ('{$this->__t_sql('Milk')}', 2, 10, 10, 1, 6, 1); --23
|
||||
@ -250,6 +250,7 @@ class DemoDataGeneratorService extends BaseService
|
||||
$stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||
$stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||
$stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||
$stockService->AddProduct(20, 1, date('Y-m-d', strtotime('-1 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||
$stockService->AddProduct(21, 1500, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||
$stockService->AddProduct(21, 2500, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||
$stockService->AddProduct(22, 1, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||
|
@ -52,25 +52,24 @@ class StockService extends BaseService
|
||||
}
|
||||
}
|
||||
|
||||
public function AddExpiredProductsToShoppingList($listId = 1)
|
||||
public function AddOverdueProductsToShoppingList($listId = 1)
|
||||
{
|
||||
if (!$this->ShoppingListExists($listId))
|
||||
{
|
||||
throw new \Exception('Shopping list does not exist');
|
||||
}
|
||||
|
||||
$expiredProducts = $this->GetExpiringProducts(-1);
|
||||
$overdueProducts = $this->GetDueProducts(-1);
|
||||
|
||||
foreach ($expiredProducts as $expiredProduct)
|
||||
foreach ($overdueProducts as $overdueProduct)
|
||||
{
|
||||
$product = $this->getDatabase()->products()->where('id', $expiredProduct->product_id)->fetch();
|
||||
|
||||
$alreadyExistingEntry = $this->getDatabase()->shopping_list()->where('product_id', $expiredProduct->product_id)->fetch();
|
||||
$product = $this->getDatabase()->products()->where('id', $overdueProduct->product_id)->fetch();
|
||||
|
||||
$alreadyExistingEntry = $this->getDatabase()->shopping_list()->where('product_id', $overdueProduct->product_id)->fetch();
|
||||
if (!$alreadyExistingEntry)
|
||||
{
|
||||
$shoppinglistRow = $this->getDatabase()->shopping_list()->createRow([
|
||||
'product_id' => $expiredProduct->product_id,
|
||||
'product_id' => $overdueProduct->product_id,
|
||||
'amount' => 1,
|
||||
'shopping_list_id' => $listId
|
||||
]);
|
||||
@ -101,7 +100,7 @@ class StockService extends BaseService
|
||||
$amount = $amount - floatval($productDetails->stock_amount) - floatval($productDetails->product->tare_weight);
|
||||
}
|
||||
|
||||
//Sets the default best before date, if none is supplied
|
||||
//Set the default due date, if none is supplied
|
||||
if ($bestBeforeDate == null)
|
||||
{
|
||||
if (intval($productDetails->product->default_best_before_days) == -1)
|
||||
@ -479,12 +478,13 @@ class StockService extends BaseService
|
||||
}
|
||||
}
|
||||
|
||||
public function GetExpiringProducts(int $days = 5, bool $excludeExpired = false)
|
||||
public function GetDueProducts(int $days = 5, bool $excludeOverdue = false)
|
||||
{
|
||||
$currentStock = $this->GetCurrentStock(false);
|
||||
$currentStock = FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d 23:59:59', strtotime("+$days days")), '<');
|
||||
$currentStock = FindAllObjectsInArrayByPropertyValue($currentStock, 'due_type', 1);
|
||||
|
||||
if ($excludeExpired)
|
||||
if ($excludeOverdue)
|
||||
{
|
||||
$currentStock = FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d 23:59:59', strtotime('-1 days')), '>');
|
||||
}
|
||||
@ -492,6 +492,15 @@ class StockService extends BaseService
|
||||
return $currentStock;
|
||||
}
|
||||
|
||||
public function GetExpiredProducts()
|
||||
{
|
||||
$currentStock = $this->GetCurrentStock(false);
|
||||
$currentStock = FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d 23:59:59', strtotime("-1 days")), '<');
|
||||
$currentStock = FindAllObjectsInArrayByPropertyValue($currentStock, 'due_type', 2);
|
||||
|
||||
return $currentStock;
|
||||
}
|
||||
|
||||
public function GetMissingProducts()
|
||||
{
|
||||
$sql = 'SELECT * FROM stock_missing_products_including_opened';
|
||||
@ -550,7 +559,7 @@ class StockService extends BaseService
|
||||
$product = $this->getDatabase()->products($productId);
|
||||
$productBarcodes = $this->getDatabase()->product_barcodes()->where('product_id', $productId)->fetchAll();
|
||||
$productLastUsed = $this->getDatabase()->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->where('undone', 0)->max('used_date');
|
||||
$nextBestBeforeDate = $this->getDatabase()->stock()->where('product_id', $productId)->min('best_before_date');
|
||||
$nextDueDate = $this->getDatabase()->stock()->where('product_id', $productId)->min('best_before_date');
|
||||
$quPurchase = $this->getDatabase()->quantity_units($product->qu_id_purchase);
|
||||
$quStock = $this->getDatabase()->quantity_units($product->qu_id_stock);
|
||||
$location = $this->getDatabase()->locations($product->location_id);
|
||||
@ -584,7 +593,7 @@ class StockService extends BaseService
|
||||
'oldest_price' => $oldestPrice,
|
||||
'last_shopping_location_id' => $lastShoppingLocation,
|
||||
'default_shopping_location_id' => $product->shopping_location_id,
|
||||
'next_best_before_date' => $nextBestBeforeDate,
|
||||
'next_due_date' => $nextDueDate,
|
||||
'location' => $location,
|
||||
'average_shelf_life_days' => $averageShelfLifeDays,
|
||||
'spoil_rate_percent' => $spoilRate,
|
||||
@ -630,7 +639,7 @@ class StockService extends BaseService
|
||||
public function GetProductStockEntries($productId, $excludeOpened = false, $allowSubproductSubstitution = false, $ordered = true)
|
||||
{
|
||||
// In order of next use:
|
||||
// First expiring first, then first in first out
|
||||
// First due first, then first in first out
|
||||
|
||||
$sqlWhereProductId = 'product_id = :1';
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
@include('components.numberpicker', array(
|
||||
'id' => 'batteries_due_soon_days',
|
||||
'additionalAttributes' => 'data-setting-key="batteries_due_soon_days"',
|
||||
'label' => 'Batteries due to be charged soon days',
|
||||
'label' => 'Due soon days',
|
||||
'min' => 1,
|
||||
'invalidFeedback' => $__t('This cannot be lower than %s', '1'),
|
||||
'additionalCssClasses' => 'user-setting-control'
|
||||
|
@ -19,7 +19,7 @@
|
||||
@include('components.numberpicker', array(
|
||||
'id' => 'chores_due_soon_days',
|
||||
'additionalAttributes' => 'data-setting-key="chores_due_soon_days"',
|
||||
'label' => 'Chores due soon days',
|
||||
'label' => 'Due soon days',
|
||||
'min' => 1,
|
||||
'invalidFeedback' => $__t('This cannot be lower than %s', '1'),
|
||||
'additionalCssClasses' => 'user-setting-control'
|
||||
|
@ -118,7 +118,7 @@
|
||||
for="use_specific_stock_entry">{{ $__t('Use a specific stock item') }}
|
||||
<i class="fas fa-question-circle text-muted"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('The first item in this list would be picked by the default rule which is "First expiring first, then first in first out"') }}"></i>
|
||||
title="{{ $__t('The first item in this list would be picked by the default rule which is "First due first, then first in first out"') }}"></i>
|
||||
</label>
|
||||
</div>
|
||||
<select disabled
|
||||
|
@ -60,17 +60,17 @@
|
||||
@endphp
|
||||
@include('components.datetimepicker', array(
|
||||
'id' => 'best_before_date',
|
||||
'label' => 'Best before',
|
||||
'label' => 'Due date',
|
||||
'hint' => 'This will apply to added products',
|
||||
'format' => 'YYYY-MM-DD',
|
||||
'initWithNow' => false,
|
||||
'limitEndToNow' => false,
|
||||
'limitStartToNow' => false,
|
||||
'invalidFeedback' => $__t('A best before date is required'),
|
||||
'invalidFeedback' => $__t('A due date is required'),
|
||||
'nextInputSelector' => '#best_before_date',
|
||||
'additionalGroupCssClasses' => 'date-only-datetimepicker',
|
||||
'shortcutValue' => '2999-12-31',
|
||||
'shortcutLabel' => 'Never expires',
|
||||
'shortcutLabel' => 'Never overdue',
|
||||
'earlierThanInfoLimit' => date('Y-m-d'),
|
||||
'earlierThanInfoText' => $__t('The given date is earlier than today, are you sure?'),
|
||||
'additionalGroupCssClasses' => $additionalGroupCssClasses,
|
||||
|
@ -61,7 +61,7 @@
|
||||
for="is_freezer">{{ $__t('Is freezer') }}
|
||||
<i class="fas fa-question-circle text-muted"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('When moving products from/to a freezer location, the products best before date is automatically adjusted according to the product settings') }}"></i>
|
||||
title="{{ $__t('When moving products from/to a freezer location, the products due date is automatically adjusted according to the product settings') }}"></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -159,31 +159,72 @@
|
||||
for="cumulate_min_stock_amount_of_sub_products">{{ $__t('Accumulate sub products min. stock amount') }}
|
||||
<i class="fas fa-question-circle text-muted"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('If enabled, the min. stock amount of sub products will be accumulated into this product, means the sub product will never be "missing", only this product') }}"></i></span>
|
||||
title="{{ $__t('If enabled, the min. stock amount of sub products will be accumulated into this product, means the sub product will never be "missing", only this product') }}"></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
|
||||
<div class="form-group">
|
||||
<label class="d-block my-0"
|
||||
for="location_id">{{ $__t('Due date type') }}
|
||||
<i class="fas fa-question-circle text-muted"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('Based on the selected type, the highlighting on the stock overview page will be different') }}"></i>
|
||||
</label>
|
||||
<div class="custom-control custom-radio mt-n2">
|
||||
<input class="custom-control-input"
|
||||
type="radio"
|
||||
name="due_type"
|
||||
id="due-type-bestbefore"
|
||||
value="1"
|
||||
@if($mode=='edit'
|
||||
&&
|
||||
$product->due_type == 1) checked @else checked @endif>
|
||||
<label class="custom-control-label"
|
||||
for="due-type-bestbefore">{{ $__t('Best before date') }}
|
||||
<i class="fas fa-question-circle text-muted"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('Means that the product is maybe still safe to be consumed after its due date is reached') }}"></i>
|
||||
</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio mt-n2">
|
||||
<input class="custom-control-input"
|
||||
type="radio"
|
||||
name="due_type"
|
||||
id="due-type-expiration"
|
||||
value="2"
|
||||
@if($mode=='edit'
|
||||
&&
|
||||
$product->due_type == 2) checked @endif>
|
||||
<label class="custom-control-label"
|
||||
for="due-type-expiration">{{ $__t('Expiration date') }}
|
||||
<i class="fas fa-question-circle text-muted"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('Means that the product is not safe to be consumed after its due date is reached') }}"></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@php if($mode == 'edit') { $value = $product->default_best_before_days; } else { $value = 0; } @endphp
|
||||
@include('components.numberpicker', array(
|
||||
'id' => 'default_best_before_days',
|
||||
'label' => 'Default best before days',
|
||||
'label' => 'Default due days',
|
||||
'min' => -1,
|
||||
'value' => $value,
|
||||
'invalidFeedback' => $__t('The amount cannot be lower than %s', '-1'),
|
||||
'hint' => $__t('For purchases this amount of days will be added to today for the best before date suggestion') . ' (' . $__t('-1 means that this product never expires') . ')'
|
||||
'hint' => $__t('For purchases this amount of days will be added to today for the due date suggestion') . ' (' . $__t('-1 means that this product wille be never overdue') . ')'
|
||||
))
|
||||
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
|
||||
@php if($mode == 'edit') { $value = $product->default_best_before_days_after_open; } else { $value = 0; } @endphp
|
||||
@include('components.numberpicker', array(
|
||||
'id' => 'default_best_before_days_after_open',
|
||||
'label' => 'Default best before days after opened',
|
||||
'label' => 'Default due days after opened',
|
||||
'min' => 0,
|
||||
'value' => $value,
|
||||
'invalidFeedback' => $__t('The amount cannot be lower than %s', '-1'),
|
||||
'hint' => $__t('When this product was marked as opened, the best before date will be replaced by today + this amount of days (a value of 0 disables this)')
|
||||
'hint' => $__t('When this product was marked as opened, the expiry date will be replaced by today + this amount of days (a value of 0 disables this)')
|
||||
))
|
||||
@else
|
||||
<input type="hidden"
|
||||
@ -196,6 +237,10 @@
|
||||
name="default_best_before_days"
|
||||
id="default_best_before_days"
|
||||
value="1">
|
||||
<input type="hidden"
|
||||
name="due_type"
|
||||
id="due_type"
|
||||
value="1">
|
||||
@endif
|
||||
|
||||
<div class="form-group">
|
||||
@ -344,21 +389,21 @@
|
||||
@php if($mode == 'edit') { $value = $product->default_best_before_days_after_freezing; } else { $value = 0; } @endphp
|
||||
@include('components.numberpicker', array(
|
||||
'id' => 'default_best_before_days_after_freezing',
|
||||
'label' => 'Default best before days after freezing',
|
||||
'label' => 'Default due days after freezing',
|
||||
'min' => -1,
|
||||
'value' => $value,
|
||||
'invalidFeedback' => $__t('The amount cannot be lower than %s', '0'),
|
||||
'hint' => $__t('On moving this product to a freezer location (so when freezing it), the best before date will be replaced by today + this amount of days')
|
||||
'hint' => $__t('On moving this product to a freezer location (so when freezing it), the expiry date will be replaced by today + this amount of days')
|
||||
))
|
||||
|
||||
@php if($mode == 'edit') { $value = $product->default_best_before_days_after_thawing; } else { $value = 0; } @endphp
|
||||
@include('components.numberpicker', array(
|
||||
'id' => 'default_best_before_days_after_thawing',
|
||||
'label' => 'Default best before days after thawing',
|
||||
'label' => 'Default due days after thawing',
|
||||
'min' => -1,
|
||||
'value' => $value,
|
||||
'invalidFeedback' => $__t('The amount cannot be lower than %s', '0'),
|
||||
'hint' => $__t('On moving this product from a freezer location (so when thawing it), the best before date will be replaced by today + this amount of days')
|
||||
'hint' => $__t('On moving this product from a freezer location (so when thawing it), the due date will be replaced by today + this amount of days')
|
||||
))
|
||||
@else
|
||||
<input type="hidden"
|
||||
|
@ -81,16 +81,16 @@
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
|
||||
@include('components.datetimepicker', array(
|
||||
'id' => 'best_before_date',
|
||||
'label' => 'Best before',
|
||||
'label' => 'Due date',
|
||||
'format' => 'YYYY-MM-DD',
|
||||
'initWithNow' => false,
|
||||
'limitEndToNow' => false,
|
||||
'limitStartToNow' => false,
|
||||
'invalidFeedback' => $__t('A best before date is required'),
|
||||
'invalidFeedback' => $__t('A due date is required'),
|
||||
'nextInputSelector' => '#price',
|
||||
'additionalCssClasses' => 'date-only-datetimepicker',
|
||||
'shortcutValue' => '2999-12-31',
|
||||
'shortcutLabel' => 'Never expires',
|
||||
'shortcutLabel' => 'Never overdue',
|
||||
'earlierThanInfoLimit' => date('Y-m-d'),
|
||||
'earlierThanInfoText' => $__t('The given date is earlier than today, are you sure?'),
|
||||
'activateNumberPad' => GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_FIELD_NUMBER_PAD
|
||||
@ -111,23 +111,23 @@
|
||||
'additionalCssClasses' => 'locale-number-input locale-number-currency'
|
||||
))
|
||||
|
||||
<div class="form-check form-check-inline mb-3">
|
||||
<input class="form-check-input"
|
||||
<div class="custom-control custom-radio custom-control-inline mt-n2 mb-3">
|
||||
<input class="custom-control-input"
|
||||
type="radio"
|
||||
name="price-type"
|
||||
id="price-type-unit-price"
|
||||
value="unit-price"
|
||||
checked>
|
||||
<label class="form-check-label"
|
||||
<label class="custom-control-label"
|
||||
for="price-type-unit-price">{{ $__t('Unit price') }}</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline mb-3">
|
||||
<input class="form-check-input"
|
||||
<div class="custom-control custom-radio custom-control-inline mt-n2 mb-3">
|
||||
<input class="custom-control-input"
|
||||
type="radio"
|
||||
name="price-type"
|
||||
id="price-type-total-price"
|
||||
value="total-price">
|
||||
<label class="form-check-label"
|
||||
<label class="custom-control-label"
|
||||
for="price-type-total-price">{{ $__t('Total price') }}</label>
|
||||
</div>
|
||||
@include('components.shoppinglocationpicker', array(
|
||||
|
@ -320,7 +320,7 @@
|
||||
<label>{{ $__t('Costs') }} </label>
|
||||
<i class="fas fa-question-circle text-muted"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('Based on the prices of the default consume rule which is "First expiring first, then first in first out"') }}"></i>
|
||||
title="{{ $__t('Based on the prices of the default consume rule which is "First due first, then first in first out"') }}"></i>
|
||||
<h3 class="locale-number locale-number-currency pt-0">{{ $costs }}</h3>
|
||||
</div>
|
||||
@endif
|
||||
|
@ -115,10 +115,10 @@
|
||||
href="#">
|
||||
{{ $__t('Add products that are below defined min. stock amount') }}
|
||||
</a>
|
||||
<a id="add-expired-products"
|
||||
<a id="add-overdue-products"
|
||||
class="btn btn-outline-primary btn-sm mb-1 responsive-button @if(!GROCY_FEATURE_FLAG_STOCK) d-none @endif"
|
||||
href="#">
|
||||
{{ $__t('Add expired products') }}
|
||||
{{ $__t('Add overdue products') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -38,7 +38,7 @@
|
||||
data-setting-key="shopping_list_to_stock_workflow_auto_submit_when_prefilled">
|
||||
<label class="form-check-label custom-control-label"
|
||||
for="shopping_list_to_stock_workflow_auto_submit_when_prefilled">
|
||||
{{ $__t('Automatically do the booking using the last price and the amount of the shopping list item, if the product has "Default best before days" set') }}
|
||||
{{ $__t('Automatically do the booking using the last price and the amount of the shopping list item, if the product has "Default due days" set') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -66,7 +66,7 @@
|
||||
<th>{{ $__t('Product') }}</th>
|
||||
<th>{{ $__t('Amount') }}</th>
|
||||
@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
|
||||
<th>{{ $__t('Best before date') }}</th>
|
||||
<th>{{ $__t('Due date') }}</th>
|
||||
@endif
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)<th>{{ $__t('Location') }}</th>@endif
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
|
||||
@ -83,7 +83,8 @@
|
||||
<tbody class="d-none">
|
||||
@foreach($stockEntries as $stockEntry)
|
||||
<tr id="stock-{{ $stockEntry->id }}-row"
|
||||
class="@if(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $stockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('-1 days')) && $stockEntry->amount > 0) table-danger @elseif(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $stockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('+' . $nextXDays . ' days'))
|
||||
data-due-type="{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->due_type }}"
|
||||
class="@if(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $stockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('-1 days')) && $stockEntry->amount > 0) @if(FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->due_type == 1) table-secondary @else table-danger @endif @elseif(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $stockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('+' . $nextXDays . ' days'))
|
||||
&&
|
||||
$stockEntry->amount > 0) table-warning @endif">
|
||||
<td class="fit-content border-right">
|
||||
@ -218,8 +219,8 @@
|
||||
</td>
|
||||
@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
|
||||
<td>
|
||||
<span id="stock-{{ $stockEntry->id }}-best-before-date">{{ $stockEntry->best_before_date }}</span>
|
||||
<time id="stock-{{ $stockEntry->id }}-best-before-date-timeago"
|
||||
<span id="stock-{{ $stockEntry->id }}-due-date">{{ $stockEntry->best_before_date }}</span>
|
||||
<time id="stock-{{ $stockEntry->id }}-due-date-timeago"
|
||||
class="timeago timeago-contextual"
|
||||
datetime="{{ $stockEntry->best_before_date }} 23:59:59"></time>
|
||||
</td>
|
||||
|
@ -36,16 +36,16 @@
|
||||
@include('components.datetimepicker', array(
|
||||
'id' => 'best_before_date',
|
||||
'initialValue' => $stockEntry->best_before_date,
|
||||
'label' => 'Best before',
|
||||
'label' => 'Due date',
|
||||
'format' => 'YYYY-MM-DD',
|
||||
'initWithNow' => false,
|
||||
'limitEndToNow' => false,
|
||||
'limitStartToNow' => false,
|
||||
'invalidFeedback' => $__t('A best before date is required'),
|
||||
'invalidFeedback' => $__t('A due date is required'),
|
||||
'nextInputSelector' => '#best_before_date',
|
||||
'additionalGroupCssClasses' => 'date-only-datetimepicker',
|
||||
'shortcutValue' => '2999-12-31',
|
||||
'shortcutLabel' => 'Never expires',
|
||||
'shortcutLabel' => 'Never overdue',
|
||||
'earlierThanInfoLimit' => date('Y-m-d'),
|
||||
'earlierThanInfoText' => $__t('The given date is earlier than today, are you sure?'),
|
||||
'additionalGroupCssClasses' => $additionalGroupCssClasses,
|
||||
|
@ -50,10 +50,13 @@
|
||||
</div>
|
||||
<div class="border-top border-bottom my-2 py-1">
|
||||
@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
|
||||
<div id="info-expiring-products"
|
||||
<div id="info-duesoon-products"
|
||||
data-next-x-days="{{ $nextXDays }}"
|
||||
data-status-filter="expiring"
|
||||
data-status-filter="duesoon"
|
||||
class="warning-message status-filter-message responsive-button mr-2"></div>
|
||||
<div id="info-overdue-products"
|
||||
data-status-filter="overdue"
|
||||
class="secondary-message status-filter-message responsive-button mr-2"></div>
|
||||
<div id="info-expired-products"
|
||||
data-status-filter="expired"
|
||||
class="error-message status-filter-message responsive-button mr-2"></div>
|
||||
@ -130,8 +133,9 @@
|
||||
<option class="bg-white"
|
||||
value="all">{{ $__t('All') }}</option>
|
||||
@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
|
||||
<option value="expiring">{{ $__t('Expiring soon') }}</option>
|
||||
<option value="expired">{{ $__t('Already expired') }}</option>
|
||||
<option value="duesoon">{{ $__t('Due soon') }}</option>
|
||||
<option value="overdue">{{ $__t('Overdue') }}</option>
|
||||
<option value="expired">{{ $__t('Expired') }}</option>
|
||||
@endif
|
||||
<option value="belowminstockamount">{{ $__t('Below min. stock amount') }}</option>
|
||||
</select>
|
||||
@ -156,7 +160,7 @@
|
||||
<th>{{ $__t('Product group') }}</th>
|
||||
<th>{{ $__t('Amount') }}</th>
|
||||
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">{{ $__t('Value') }}</th>
|
||||
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) d-none @endif">{{ $__t('Next best before date') }}</th>
|
||||
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) d-none @endif">{{ $__t('Next due date') }}</th>
|
||||
<th class="d-none">Hidden location</th>
|
||||
<th class="d-none">Hidden status</th>
|
||||
<th class="d-none">Hidden product group</th>
|
||||
@ -172,7 +176,7 @@
|
||||
<tbody class="d-none">
|
||||
@foreach($currentStock as $currentStockEntry)
|
||||
<tr id="product-{{ $currentStockEntry->product_id }}-row"
|
||||
class="@if(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('-1 days')) && $currentStockEntry->amount > 0) table-danger @elseif(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('+' . $nextXDays . ' days')) && $currentStockEntry->amount > 0) table-warning @elseif ($currentStockEntry->product_missing) table-info @endif">
|
||||
class="@if(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('-1 days')) && $currentStockEntry->amount > 0) @if($currentStockEntry->due_type == 1) table-secondary @else table-danger @endif @elseif(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('+' . $nextXDays . ' days')) && $currentStockEntry->amount > 0) table-warning @elseif ($currentStockEntry->product_missing) table-info @endif">
|
||||
<td class="fit-content border-right">
|
||||
<a class="permission-STOCK_CONSUME btn btn-success btn-sm product-consume-button @if($currentStockEntry->amount < $currentStockEntry->quick_consume_amount || $currentStockEntry->enable_tare_weight_handling == 1) disabled @endif"
|
||||
href="#"
|
||||
@ -340,8 +344,8 @@
|
||||
class="locale-number locale-number-currency">{{ $currentStockEntry->value }}</span>
|
||||
</td>
|
||||
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) d-none @endif">
|
||||
<span id="product-{{ $currentStockEntry->product_id }}-next-best-before-date">{{ $currentStockEntry->best_before_date }}</span>
|
||||
<time id="product-{{ $currentStockEntry->product_id }}-next-best-before-date-timeago"
|
||||
<span id="product-{{ $currentStockEntry->product_id }}-next-due-date">{{ $currentStockEntry->best_before_date }}</span>
|
||||
<time id="product-{{ $currentStockEntry->product_id }}-next-due-date-timeago"
|
||||
class="timeago timeago-contextual"
|
||||
datetime="{{ $currentStockEntry->best_before_date }} 23:59:59"></time>
|
||||
</td>
|
||||
@ -358,7 +362,7 @@
|
||||
. ' days'
|
||||
))
|
||||
&&
|
||||
$currentStockEntry->amount > 0) expired @elseif($currentStockEntry->best_before_date < date('Y-m-d
|
||||
$currentStockEntry->amount > 0) @if($currentStockEntry->due_type == 1) overdue @else expired @endif @elseif($currentStockEntry->best_before_date < date('Y-m-d
|
||||
23:59:59',
|
||||
strtotime('+'
|
||||
.
|
||||
@ -366,7 +370,7 @@
|
||||
. ' days'
|
||||
))
|
||||
&&
|
||||
$currentStockEntry->amount > 0) expiring @elseif ($currentStockEntry->product_missing) belowminstockamount @endif"
|
||||
$currentStockEntry->amount > 0) duesoon @elseif ($currentStockEntry->product_missing) belowminstockamount @endif"
|
||||
</td>
|
||||
<td class="d-none">
|
||||
xx{{ $currentStockEntry->product_group_name }}xx
|
||||
|
@ -59,9 +59,9 @@
|
||||
|
||||
<h4 class="mt-2">{{ $__t('Stock overview') }}</h4>
|
||||
@include('components.numberpicker', array(
|
||||
'id' => 'stock_expiring_soon_days',
|
||||
'additionalAttributes' => 'data-setting-key="stock_expiring_soon_days"',
|
||||
'label' => 'Expiring soon days',
|
||||
'id' => 'stock_due_soon_days',
|
||||
'additionalAttributes' => 'data-setting-key="stock_due_soon_days"',
|
||||
'label' => 'Due soon days',
|
||||
'min' => 1,
|
||||
'invalidFeedback' => $__t('This cannot be lower than %s', '1'),
|
||||
'additionalCssClasses' => 'user-setting-control'
|
||||
@ -108,11 +108,11 @@
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox"
|
||||
class="form-check-input custom-control-input user-setting-control"
|
||||
id="show_warning_on_purchase_when_best_before_date_is_earlier_than_next"
|
||||
data-setting-key="show_warning_on_purchase_when_best_before_date_is_earlier_than_next">
|
||||
id="show_warning_on_purchase_when_due_date_is_earlier_than_next"
|
||||
data-setting-key="show_warning_on_purchase_when_due_date_is_earlier_than_next">
|
||||
<label class="form-check-label custom-control-label"
|
||||
for="show_warning_on_purchase_when_best_before_date_is_earlier_than_next">
|
||||
{{ $__t('Show a warning when the best before date of the purchased product is earlier than the next best before date in stock') }}
|
||||
for="show_warning_on_purchase_when_due_date_is_earlier_than_next">
|
||||
{{ $__t('Show a warning when the due date of the purchased product is earlier than the next due date in stock') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -19,7 +19,7 @@
|
||||
@include('components.numberpicker', array(
|
||||
'id' => 'tasks_due_soon_days',
|
||||
'additionalAttributes' => 'data-setting-key="tasks_due_soon_days"',
|
||||
'label' => 'Tasks due soon days',
|
||||
'label' => 'Due soon days',
|
||||
'min' => 1,
|
||||
'invalidFeedback' => $__t('This cannot be lower than %s', '1'),
|
||||
'additionalCssClasses' => 'user-setting-control'
|
||||
|
@ -73,7 +73,7 @@
|
||||
for="use_specific_stock_entry">{{ $__t('Use a specific stock item') }}
|
||||
<i class="fas fa-question-circle text-muted"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('The first item in this list would be picked by the default rule which is "First expiring first, then first in first out"') }}"></i>
|
||||
title="{{ $__t('The first item in this list would be picked by the default rule which is "First due first, then first in first out"') }}"></i>
|
||||
</label>
|
||||
</div>
|
||||
<select disabled
|
||||
|
Loading…
x
Reference in New Issue
Block a user