diff --git a/controllers/StockApiController.php b/controllers/StockApiController.php
index 5db2038b..3eb360a5 100644
--- a/controllers/StockApiController.php
+++ b/controllers/StockApiController.php
@@ -23,6 +23,18 @@ class StockApiController extends BaseApiController
}
}
+ public function ProductBarcodeDetails(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
+ {
+ try
+ {
+ return $this->ApiResponse($response, $this->getDatabase()->product_barcodes()->where('barcode = :1', $args['barcode'])->fetch());
+ }
+ catch (\Exception $ex)
+ {
+ return $this->GenericErrorResponse($response, $ex->getMessage());
+ }
+ }
+
public function ProductDetailsByBarcode(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
try
@@ -88,13 +100,19 @@ class StockApiController extends BaseApiController
$shoppingLocationId = $requestBody['shopping_location_id'];
}
+ $quFactorPurchaseToStock = null;
+ if (array_key_exists('qu_factor_purchase_to_stock', $requestBody) && is_numeric($requestBody['qu_factor_purchase_to_stock']))
+ {
+ $quFactorPurchaseToStock = $requestBody['qu_factor_purchase_to_stock'];
+ }
+
$transactionType = StockService::TRANSACTION_TYPE_PURCHASE;
if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype']))
{
$transactionType = $requestBody['transactiontype'];
}
- $bookingId = $this->getStockService()->AddProduct($args['productId'], $requestBody['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price, $locationId, $shoppingLocationId);
+ $bookingId = $this->getStockService()->AddProduct($args['productId'], $requestBody['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price, $quFactorPurchaseToStock, $locationId, $shoppingLocationId);
return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId));
}
catch (\Exception $ex)
@@ -156,7 +174,7 @@ class StockApiController extends BaseApiController
$shoppingLocationId = $requestBody['shopping_location_id'];
}
- $bookingId = $this->getStockService()->EditStockEntry($args['entryId'], $requestBody['amount'], $bestBeforeDate, $locationId, $shoppingLocationId, $price, $requestBody['open'], $requestBody['purchased_date']);
+ $bookingId = $this->getStockService()->EditStockEntry($args['entryId'], $requestBody['amount'], $bestBeforeDate, $locationId, $shoppingLocationId, $price, $requestBody['open'], $requestBody['purchased_date'], $requestBody['qu_factor_purchase_to_stock']);
return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId));
}
catch (\Exception $ex)
diff --git a/controllers/StockController.php b/controllers/StockController.php
index 693bd016..f704c84e 100644
--- a/controllers/StockController.php
+++ b/controllers/StockController.php
@@ -16,7 +16,7 @@ class StockController extends BaseController
$nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['stock_expring_soon_days'];
return $this->renderPage($response, 'stockoverview', [
- 'products' => $this->getDatabase()->products()->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'),
'locations' => $this->getDatabase()->locations()->orderBy('name'),
'currentStock' => $this->getStockService()->GetCurrentStock(true),
@@ -36,7 +36,7 @@ class StockController extends BaseController
$nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['stock_expring_soon_days'];
return $this->renderPage($response, 'stockentries', [
- 'products' => $this->getDatabase()->products()->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'),
'locations' => $this->getDatabase()->locations()->orderBy('name'),
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'),
@@ -50,8 +50,12 @@ class StockController extends BaseController
public function Purchase(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
+ $sql = 'select group_concat(barcode) barcodes, product_id from product_barcodes group by product_id';
+ $productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
+
return $this->renderPage($response, 'purchase', [
- 'products' => $this->getDatabase()->products()->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
+ 'barcodes' => $productBarcodes,
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'),
'locations' => $this->getDatabase()->locations()->orderBy('name')
]);
@@ -59,8 +63,12 @@ class StockController extends BaseController
public function Consume(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
+ $sql = 'select group_concat(barcode) barcodes, product_id from product_barcodes group by product_id';
+ $productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
+
return $this->renderPage($response, 'consume', [
- 'products' => $this->getDatabase()->products()->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
+ 'barcodes' => $productBarcodes,
'recipes' => $this->getDatabase()->recipes()->orderBy('name'),
'locations' => $this->getDatabase()->locations()->orderBy('name')
]);
@@ -68,8 +76,12 @@ class StockController extends BaseController
public function Transfer(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
+ $sql = 'select group_concat(barcode) barcodes, product_id from product_barcodes group by product_id';
+ $productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
+
return $this->renderPage($response, 'transfer', [
- 'products' => $this->getDatabase()->products()->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
+ 'barcodes' => $productBarcodes,
'recipes' => $this->getDatabase()->recipes()->orderBy('name'),
'locations' => $this->getDatabase()->locations()->orderBy('name')
]);
@@ -77,8 +89,12 @@ class StockController extends BaseController
public function Inventory(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
+ $sql = 'select group_concat(barcode) barcodes, product_id from product_barcodes group by product_id';
+ $productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
+
return $this->renderPage($response, 'inventory', [
- 'products' => $this->getDatabase()->products()->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
+ 'barcodes' => $productBarcodes,
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'),
'locations' => $this->getDatabase()->locations()->orderBy('name')
]);
@@ -88,7 +104,7 @@ class StockController extends BaseController
{
return $this->renderPage($response, 'stockentryform', [
'stockEntry' => $this->getDatabase()->stock()->where('id', $args['entryId'])->fetch(),
- 'products' => $this->getDatabase()->products()->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'),
'locations' => $this->getDatabase()->locations()->orderBy('name')
]);
@@ -104,7 +120,7 @@ class StockController extends BaseController
return $this->renderPage($response, 'shoppinglist', [
'listItems' => $this->getDatabase()->shopping_list()->where('shopping_list_id = :1', $listId),
- 'products' => $this->getDatabase()->products()->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'),
'missingProducts' => $this->getStockService()->GetMissingProducts(),
'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'),
@@ -158,7 +174,7 @@ class StockController extends BaseController
{
return $this->renderPage($response, 'productgroups', [
'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'),
- 'products' => $this->getDatabase()->products()->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
'userfields' => $this->getUserfieldsService()->GetFields('product_groups'),
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('product_groups')
]);
@@ -179,11 +195,12 @@ class StockController extends BaseController
{
return $this->renderPage($response, 'productform', [
'locations' => $this->getDatabase()->locations()->orderBy('name'),
+ 'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'),
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'),
'productgroups' => $this->getDatabase()->product_groups()->orderBy('name'),
'userfields' => $this->getUserfieldsService()->GetFields('products'),
- 'products' => $this->getDatabase()->products()->where('parent_product_id IS NULL')->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('parent_product_id IS NULL and active = 1')->orderBy('name'),
'isSubProductOfOthers' => false,
'mode' => 'create'
]);
@@ -195,11 +212,12 @@ class StockController extends BaseController
return $this->renderPage($response, 'productform', [
'product' => $product,
'locations' => $this->getDatabase()->locations()->orderBy('name'),
+ 'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'),
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'),
'productgroups' => $this->getDatabase()->product_groups()->orderBy('name'),
'userfields' => $this->getUserfieldsService()->GetFields('products'),
- 'products' => $this->getDatabase()->products()->where('id != :1 AND parent_product_id IS NULL', $product->id)->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('id != :1 AND parent_product_id IS NULL and active = 1', $product->id)->orderBy('name'),
'isSubProductOfOthers' => $this->getDatabase()->products()->where('parent_product_id = :1', $product->id)->count() !== 0,
'mode' => 'edit',
'quConversions' => $this->getDatabase()->quantity_unit_conversions()
@@ -296,7 +314,7 @@ class StockController extends BaseController
if ($args['itemId'] == 'new')
{
return $this->renderPage($response, 'shoppinglistitemform', [
- 'products' => $this->getDatabase()->products()->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'),
'mode' => 'create'
]);
@@ -305,7 +323,7 @@ class StockController extends BaseController
{
return $this->renderPage($response, 'shoppinglistitemform', [
'listItem' => $this->getDatabase()->shopping_list($args['itemId']),
- 'products' => $this->getDatabase()->products()->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'),
'mode' => 'edit'
]);
@@ -339,7 +357,7 @@ class StockController extends BaseController
return $this->renderPage($response, 'stockjournal', [
'stockLog' => $this->getDatabase()->stock_log()->orderBy('row_created_timestamp', 'DESC'),
'locations' => $this->getDatabase()->locations()->orderBy('name'),
- 'products' => $this->getDatabase()->products()->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name')
]);
}
@@ -347,13 +365,41 @@ class StockController extends BaseController
public function LocationContentSheet(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->renderPage($response, 'locationcontentsheet', [
- 'products' => $this->getDatabase()->products()->orderBy('name'),
+ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'),
'locations' => $this->getDatabase()->locations()->orderBy('name'),
'currentStockLocationContent' => $this->getStockService()->GetCurrentStockLocationContent()
]);
}
+ public function ProductBarcodesEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
+ {
+ $product = null;
+ if (isset($request->getQueryParams()['product']))
+ {
+ $product = $this->getDatabase()->products($request->getQueryParams()['product']);
+ }
+
+ if ($args['productBarcodeId'] == 'new')
+ {
+ return $this->renderPage($response, 'productbarcodesform', [
+ 'mode' => 'create',
+ 'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'),
+ 'product' => $product,
+ 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name')
+ ]);
+ }
+ else
+ {
+ return $this->renderPage($response, 'productbarcodesform', [
+ 'mode' => 'edit',
+ 'barcode' => $this->getDatabase()->product_barcodes($args['productBarcodeId']),
+ 'product' => $product,
+ 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name')
+ ]);
+ }
+ }
+
public function QuantityUnitConversionEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
$product = null;
diff --git a/grocy.openapi.json b/grocy.openapi.json
index c19a0c6b..20af6b9f 100644
--- a/grocy.openapi.json
+++ b/grocy.openapi.json
@@ -2703,6 +2703,57 @@
}
}
},
+ "/productbarcodedetails/{barcode}": {
+ "get": {
+ "summary": "Executes a product barcode details lookoup via the configured plugin with the given barcode",
+ "tags": [
+ "Product"
+ ],
+ "parameters": [
+ {
+ "in": "path",
+ "name": "barcode",
+ "required": true,
+ "description": "The barcode to lookup up",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "in": "query",
+ "name": "add",
+ "required": false,
+ "description": "When true, the product is added to the database on a successful lookup and the new product id is in included in the response",
+ "schema": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "An ProductBarcodeDetails object or null, when nothing was found for the given barcode",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ProductBarcodeDetailsResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The operation was not successful (possible errors are: Plugin error)",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GenericErrorResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/recipes/{recipeId}/add-not-fulfilled-products-to-shoppinglist": {
"post": {
"summary": "Adds all missing products for the given recipe to the shopping list",
@@ -3374,6 +3425,7 @@
"enum": [
"products",
"chores",
+ "product_barcodes",
"batteries",
"locations",
"quantity_units",
@@ -3399,6 +3451,7 @@
"type": "string",
"enum": [
"products",
+ "product_barcodes",
"chores",
"batteries",
"locations",
@@ -3748,6 +3801,9 @@
"product": {
"$ref": "#/components/schemas/Product"
},
+ "product_barcodes": {
+ "$ref": "#/components/schemas/ProductBarcodeDetailsResponse"
+ },
"quantity_unit_purchase": {
"$ref": "#/components/schemas/QuantityUnit"
},
@@ -3776,6 +3832,14 @@
"type": "number",
"format": "number"
},
+ "avg_price": {
+ "type": "number",
+ "format": "number"
+ },
+ "oldest_price": {
+ "type": "number",
+ "format": "number"
+ },
"last_shopping_location_id": {
"type": "integer"
},
@@ -3813,6 +3877,15 @@
"not_check_stock_fulfillment_for_recipes": "0",
"last_shopping_location_id": null
},
+ "product_barcodes": [
+ {
+ "id": "1",
+ "product_id": "13",
+ "barcode": "01321230213",
+ "qu_factor_purchase_to_stock": "10.0",
+ "shopping_location_id": "2"
+ }
+ ],
"last_purchased": null,
"last_used": null,
"stock_amount": "2",
@@ -3834,6 +3907,8 @@
"plural_forms": null
},
"last_price": null,
+ "avg_price": null,
+ "oldest_price": null,
"last_shopping_location_id": null,
"next_best_before_date": "2019-07-07",
"location": {
@@ -3862,6 +3937,28 @@
}
}
},
+ "ProductBarcodeDetailsResponse": {
+ "type": "object",
+ "properties": {
+ "product_id": {
+ "type": "integer"
+ },
+ "barcode": {
+ "type": "string"
+ },
+ "qu_factor_purchase_to_stock": {
+ "type": "number",
+ "format": "number"
+ },
+ "barcode": {
+ "type": "string",
+ "description": "Can contain multiple barcodes separated by comma"
+ },
+ "shopping_location_id": {
+ "type": "integer"
+ }
+ }
+ },
"ExternalBarcodeLookupResponse": {
"type": "object",
"properties": {
diff --git a/localization/strings.pot b/localization/strings.pot
index aced7907..3951c972 100644
--- a/localization/strings.pot
+++ b/localization/strings.pot
@@ -1810,3 +1810,33 @@ msgstr ""
msgid "Save & return to recipes"
msgstr ""
+
+msgid "Stock value"
+msgstr ""
+
+msgid "Average price"
+msgstr ""
+
+msgid "Active"
+msgstr ""
+
+msgid "Barcodes"
+msgstr ""
+
+msgid "Barcode"
+msgstr ""
+
+msgid "Create Barcode"
+msgstr ""
+
+msgid "Barcode for product"
+msgstr ""
+
+msgid "Edit Barcode"
+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\""
+msgstr ""
diff --git a/migrations/0103.sql b/migrations/0103.sql
new file mode 100644
index 00000000..862d01f5
--- /dev/null
+++ b/migrations/0103.sql
@@ -0,0 +1,171 @@
+ALTER TABLE stock_log
+ADD qu_factor_purchase_to_stock REAL NOT NULL DEFAULT 1.0;
+
+ALTER TABLE stock
+ADD qu_factor_purchase_to_stock REAL NOT NULL DEFAULT 1.0;
+
+UPDATE stock
+SET qu_factor_purchase_to_stock = (SELECT qu_factor_purchase_to_stock FROM products WHERE product_id = id);
+
+UPDATE stock_log
+SET qu_factor_purchase_to_stock = (SELECT qu_factor_purchase_to_stock FROM products WHERE product_id = id);
+
+--Price is now going forward to be saved as 1 QU Stock
+UPDATE stock
+SET price = ROUND(price / qu_factor_purchase_to_stock, 2);
+
+UPDATE stock_log
+SET price = ROUND(price / qu_factor_purchase_to_stock, 2);
+
+CREATE TABLE product_barcodes (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
+ product_id INT NOT NULL,
+ barcode TEXT NOT NULL UNIQUE,
+ qu_factor_purchase_to_stock REAL NOT NULL DEFAULT 1,
+ shopping_location_id INTEGER,
+ row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
+);
+
+-- Convert product table to new product_barcodes table
+INSERT INTO product_barcodes
+ (product_id, barcode, qu_factor_purchase_to_stock, shopping_location_id)
+WITH barcodes_splitted(id, barcode, str, qu_factor_purchase_to_stock, shopping_location_id) AS (
+ SELECT id as product_id, '', barcode || ',', qu_factor_purchase_to_stock, shopping_location_id
+ FROM products
+
+ UNION ALL
+ SELECT
+ id as product_id,
+ SUBSTR(str, 0, instr(str, ',')),
+ SUBSTR(str, instr(str, ',') + 1),
+ qu_factor_purchase_to_stock,
+ shopping_location_id
+ FROM barcodes_splitted
+ WHERE str != ''
+)
+SELECT id as product_id, barcode, qu_factor_purchase_to_stock, shopping_location_id
+FROM barcodes_splitted
+WHERE barcode != '';
+
+PRAGMA legacy_alter_table = ON;
+ALTER TABLE products RENAME TO products_old;
+
+-- Remove barcode column
+-- Reorder columns
+CREATE TABLE products (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
+ name TEXT NOT NULL UNIQUE,
+ description TEXT,
+ product_group_id INTEGER,
+ active TINYINT NOT NULL DEFAULT 1,
+ location_id INTEGER NOT NULL,
+ shopping_location_id INTEGER,
+ qu_id_purchase INTEGER NOT NULL,
+ qu_id_stock INTEGER NOT NULL,
+ qu_factor_purchase_to_stock REAL NOT NULL,
+ min_stock_amount INTEGER NOT NULL DEFAULT 0,
+ default_best_before_days INTEGER NOT NULL DEFAULT 0,
+ default_best_before_days_after_open INTEGER NOT NULL DEFAULT 0,
+ default_best_before_days_after_freezing INTEGER NOT NULL DEFAULT 0,
+ default_best_before_days_after_thawing INTEGER NOT NULL DEFAULT 0,
+ picture_file_name TEXT,
+ allow_partial_units_in_stock TINYINT NOT NULL DEFAULT 0,
+ enable_tare_weight_handling TINYINT NOT NULL DEFAULT 0,
+ tare_weight REAL NOT NULL DEFAULT 0,
+ not_check_stock_fulfillment_for_recipes TINYINT DEFAULT 0,
+ parent_product_id INT,
+ calories INTEGER,
+ cumulate_min_stock_amount_of_sub_products TINYINT DEFAULT 0,
+ row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
+);
+
+INSERT INTO products
+ (id, name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, min_stock_amount, default_best_before_days, row_created_timestamp, product_group_id, picture_file_name, default_best_before_days_after_open, allow_partial_units_in_stock, enable_tare_weight_handling, tare_weight, not_check_stock_fulfillment_for_recipes, parent_product_id, calories, cumulate_min_stock_amount_of_sub_products, default_best_before_days_after_freezing, default_best_before_days_after_thawing, shopping_location_id)
+SELECT id, name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, min_stock_amount,default_best_before_days, row_created_timestamp, product_group_id, picture_file_name, default_best_before_days_after_open, allow_partial_units_in_stock, enable_tare_weight_handling, tare_weight, not_check_stock_fulfillment_for_recipes, parent_product_id, calories, cumulate_min_stock_amount_of_sub_products, default_best_before_days_after_freezing, default_best_before_days_after_thawing, shopping_location_id
+FROM products_old;
+
+DROP TABLE products_old;
+
+DROP VIEW stock_current_location_content;
+CREATE VIEW stock_current_location_content
+AS
+SELECT
+ IFNULL(s.location_id, p.location_id) AS location_id,
+ s.product_id,
+ SUM(s.amount) AS amount,
+ ROUND(SUM(s.amount / s.qu_factor_purchase_to_stock),2) as factor_purchase_amount,
+ ROUND(SUM(IFNULL(s.price, 0) * s.amount), 2) AS value,
+ MIN(s.best_before_date) AS best_before_date,
+ IFNULL((SELECT SUM(amount) FROM stock WHERE product_id = s.product_id AND location_id = s.location_id AND open = 1), 0) AS amount_opened
+FROM stock s
+JOIN products p
+ ON s.product_id = p.id
+ AND p.active = 1
+GROUP BY IFNULL(s.location_id, p.location_id), s.product_id;
+
+DROP VIEW stock_current;
+CREATE VIEW stock_current
+AS
+SELECT
+ pr.parent_product_id AS product_id,
+ IFNULL((SELECT SUM(amount) FROM stock WHERE product_id = pr.parent_product_id), 0) AS amount,
+ IFNULL(ROUND((SELECT SUM(amount / qu_factor_purchase_to_stock) FROM stock WHERE product_id = pr.parent_product_id), 2), 0) as factor_purchase_amount,
+ SUM(s.amount) * IFNULL(qucr.factor, 1) AS amount_aggregated,
+ IFNULL(ROUND((SELECT SUM(IFNULL(price,0) * amount) FROM stock WHERE product_id = pr.parent_product_id), 2), 0) AS value,
+ 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 p_sub.parent_product_id IS NOT NULL THEN 1 ELSE 0 END AS is_aggregated_amount
+FROM products_resolved pr
+JOIN stock s
+ ON pr.sub_product_id = s.product_id
+JOIN products p_parent
+ ON pr.parent_product_id = p_parent.id
+ AND p_parent.active = 1
+JOIN products p_sub
+ ON pr.sub_product_id = p_sub.id
+ AND p_sub.active = 1
+LEFT JOIN quantity_unit_conversions_resolved qucr
+ ON pr.sub_product_id = qucr.product_id
+ AND p_sub.qu_id_stock = qucr.from_qu_id
+ AND p_parent.qu_id_stock = qucr.to_qu_id
+GROUP BY pr.parent_product_id
+HAVING SUM(s.amount) > 0
+
+UNION
+
+-- This is the same as above but sub products not rolled up (no QU conversion and column is_aggregated_amount = 0 here)
+SELECT
+ pr.sub_product_id AS product_id,
+ SUM(s.amount) AS amount,
+ ROUND(SUM(s.amount / s.qu_factor_purchase_to_stock), 2) as factor_purchase_amount,
+ SUM(s.amount) AS amount_aggregated,
+ ROUND(SUM(IFNULL(s.price, 0) * s.amount), 2) AS value,
+ 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
+FROM products_resolved pr
+JOIN stock s
+ ON pr.sub_product_id = s.product_id
+WHERE pr.parent_product_id != pr.sub_product_id
+GROUP BY pr.sub_product_id
+HAVING SUM(s.amount) > 0;
+
+DROP VIEW products_resolved;
+CREATE VIEW products_resolved AS
+SELECT
+ p.parent_product_id parent_product_id,
+ p.id as sub_product_id
+FROM products p
+ WHERE p.parent_product_id IS NOT NULL
+ AND p.active = 1
+
+UNION
+
+SELECT
+ p.id parent_product_id,
+ p.id as sub_product_id
+FROM products p
+ WHERE p.parent_product_id IS NULL
+ AND p.active = 1;
diff --git a/migrations/0104.sql b/migrations/0104.sql
new file mode 100644
index 00000000..edb883eb
--- /dev/null
+++ b/migrations/0104.sql
@@ -0,0 +1,185 @@
+-- Deprecate unused view to instead use products_last_purchased
+DROP VIEW products_current_price;
+
+CREATE VIEW products_last_purchased
+AS
+select
+ 1 AS id, -- Dummy, LessQL needs an id column
+ sl.product_id,
+ sl.amount,
+ sl.best_before_date,
+ sl.purchased_date,
+ sl.price,
+ sl.qu_factor_purchase_to_stock,
+ sl.location_id,
+ sl.shopping_location_id
+ from stock_log sl
+ JOIN (
+ SELECT
+ s1.product_id,
+ MAX(s1.id) max_stock_id
+ FROM stock_log s1
+ JOIN (
+ SELECT
+ s.product_id,
+ MAX(s.purchased_date) max_purchased_date
+ FROM stock_log s
+ WHERE undone = 0
+ AND transaction_type in ('purchase', 'stock-edit-new', 'inventory-correction')
+ GROUP BY s.product_id) sp2
+ ON s1.product_id = sp2.product_id
+ AND s1.purchased_date = sp2.max_purchased_date
+ WHERE undone = 0
+ AND transaction_type in ('purchase', 'stock-edit-new', 'inventory-correction')
+ GROUP BY s1.product_id) sp3
+ ON sl.product_id = sp3.product_id
+ AND sl.id = sp3.max_stock_id;
+
+CREATE VIEW products_average_price
+AS
+SELECT
+ 1 AS id, -- Dummy, LessQL needs an id column
+ s.product_id,
+ round(sum(s.amount * s.price) / sum(s.amount), 2) as price
+FROM stock s
+GROUP BY s.product_id;
+
+CREATE VIEW products_oldest_stock_unit_price
+AS
+-- Find oldest best_before_date then oldest purchased_date then make sure to return one stock row using max
+SELECT
+ 1 AS id, -- Dummy, LessQL needs an id column
+ sw.product_id,
+ sw.amount,
+ sw.best_before_date,
+ sw.purchased_date,
+ sw.price, sw.qu_factor_purchase_to_stock,
+ sw.location_id,
+ sw.shopping_location_id
+ FROM stock sw
+ JOIN (
+ SELECT
+ s1.product_id,
+ MIN(s1.id) min_stock_id
+ FROM stock s1
+ JOIN (
+ SELECT
+ s.product_id,
+ sp.oldest_date,
+ MIN(s.purchased_date) min_purchased_date
+ FROM stock s
+ JOIN (
+ SELECT
+ product_id,
+ MIN(best_before_date) as oldest_date
+ FROM stock
+ GROUP BY product_id) sp
+ ON s.product_id = sp.product_id
+ AND s.best_before_date = sp.oldest_date
+ GROUP BY s.product_id, sp.oldest_date) sp2
+ ON s1.product_id = sp2.product_id
+ AND s1.best_before_date = sp2.oldest_date
+ AND s1.purchased_date = sp2.min_purchased_date
+ GROUP BY s1.product_id) sp3
+ON sw.product_id = sp3.product_id
+AND sw.id = sp3.min_stock_id;
+
+DROP VIEW recipes_pos_resolved;
+CREATE VIEW recipes_pos_resolved
+AS
+
+-- Multiplication by 1.0 to force conversion to float (REAL)
+
+SELECT
+ r.id AS recipe_id,
+ rp.id AS recipe_pos_id,
+ rp.product_id AS product_id,
+ rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) AS recipe_amount,
+ IFNULL(sc.amount_aggregated, 0) AS stock_amount,
+ CASE WHEN IFNULL(sc.amount_aggregated, 0) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END THEN 1 ELSE 0 END AS need_fulfilled,
+ CASE WHEN IFNULL(sc.amount_aggregated, 0) - CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END < 0 THEN ABS(IFNULL(sc.amount_aggregated, 0) - (CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END)) ELSE 0 END AS missing_amount,
+ IFNULL(sl.amount, 0) * p.qu_factor_purchase_to_stock AS amount_on_shopping_list,
+ CASE WHEN IFNULL(sc.amount_aggregated, 0) + (CASE WHEN r.not_check_shoppinglist = 1 THEN 0 ELSE IFNULL(sl.amount, 0) END * p.qu_factor_purchase_to_stock) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END THEN 1 ELSE 0 END AS need_fulfilled_with_shopping_list,
+ rp.qu_id,
+ (CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END) * rp.amount * pop.price * rp.price_factor AS costs,
+ CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN 0 ELSE 1 END AS is_nested_recipe_pos,
+ rp.ingredient_group,
+ pg.name as product_group,
+ rp.id, -- Just a dummy id column
+ r.type as recipe_type,
+ rnr.includes_recipe_id as child_recipe_id,
+ rp.note,
+ rp.variable_amount AS recipe_variable_amount,
+ rp.only_check_single_unit_in_stock,
+ rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) * IFNULL(p.calories, 0) AS calories,
+ p.active AS product_active
+FROM recipes r
+JOIN recipes_nestings_resolved rnr
+ ON r.id = rnr.recipe_id
+JOIN recipes rnrr
+ ON rnr.includes_recipe_id = rnrr.id
+JOIN recipes_pos rp
+ ON rnr.includes_recipe_id = rp.recipe_id
+JOIN products p
+ ON rp.product_id = p.id
+LEFT JOIN product_groups pg
+ ON p.product_group_id = pg.id
+LEFT JOIN (
+ SELECT product_id, SUM(amount) AS amount
+ FROM shopping_list
+ GROUP BY product_id) sl
+ ON rp.product_id = sl.product_id
+LEFT JOIN stock_current sc
+ ON rp.product_id = sc.product_id
+LEFT JOIN products_oldest_stock_unit_price pop
+ ON rp.product_id = pop.product_id
+WHERE rp.not_check_stock_fulfillment = 0
+
+UNION
+
+-- Just add all recipe positions which should not be checked against stock with fulfilled need
+
+SELECT
+ r.id AS recipe_id,
+ rp.id AS recipe_pos_id,
+ rp.product_id AS product_id,
+ rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) AS recipe_amount,
+ IFNULL(sc.amount_aggregated, 0) AS stock_amount,
+ 1 AS need_fulfilled,
+ 0 AS missing_amount,
+ IFNULL(sl.amount, 0) * p.qu_factor_purchase_to_stock AS amount_on_shopping_list,
+ 1 AS need_fulfilled_with_shopping_list,
+ rp.qu_id,
+ (CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END) * rp.amount * IFNULL(pop.price, 0) * rp.price_factor AS costs,
+ CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN 0 ELSE 1 END AS is_nested_recipe_pos,
+ rp.ingredient_group,
+ pg.name as product_group,
+ rp.id, -- Just a dummy id column
+ r.type as recipe_type,
+ rnr.includes_recipe_id as child_recipe_id,
+ rp.note,
+ rp.variable_amount AS recipe_variable_amount,
+ rp.only_check_single_unit_in_stock,
+ rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) * IFNULL(p.calories, 0) AS calories,
+ p.active AS product_active
+FROM recipes r
+JOIN recipes_nestings_resolved rnr
+ ON r.id = rnr.recipe_id
+JOIN recipes rnrr
+ ON rnr.includes_recipe_id = rnrr.id
+JOIN recipes_pos rp
+ ON rnr.includes_recipe_id = rp.recipe_id
+JOIN products p
+ ON rp.product_id = p.id
+LEFT JOIN product_groups pg
+ ON p.product_group_id = pg.id
+LEFT JOIN (
+ SELECT product_id, SUM(amount) AS amount
+ FROM shopping_list
+ GROUP BY product_id) sl
+ ON rp.product_id = sl.product_id
+LEFT JOIN stock_current sc
+ ON rp.product_id = sc.product_id
+LEFT JOIN products_oldest_stock_unit_price pop
+ ON rp.product_id = pop.product_id
+WHERE rp.not_check_stock_fulfillment = 1;
diff --git a/public/viewjs/components/productcard.js b/public/viewjs/components/productcard.js
index 9b6c3ba4..2fc953aa 100644
--- a/public/viewjs/components/productcard.js
+++ b/public/viewjs/components/productcard.js
@@ -6,11 +6,24 @@ Grocy.Components.ProductCard.Refresh = function(productId)
function(productDetails)
{
var stockAmount = productDetails.stock_amount || '0';
+ var stockFactorPurchaseAmount = productDetails.stock_factor_purchase_amount || '0';
+ var stockValue = productDetails.stock_value || '0';
var stockAmountOpened = productDetails.stock_amount_opened || '0';
$('#productcard-product-name').text(productDetails.product.name);
$('#productcard-product-description').html(productDetails.product.description);
$('#productcard-product-stock-amount').text(stockAmount);
$('#productcard-product-stock-qu-name').text(__n(stockAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural));
+ if (productDetails.last_qu_factor_purchase_to_stock > 1)
+ {
+ $('#productcard-product-stock-factor-purchase-amount').text('(' + stockFactorPurchaseAmount);
+ $('#productcard-product-stock-factor-purchase-qu-name').text(__n(stockFactorPurchaseAmount, productDetails.quantity_unit_purchase.name, productDetails.quantity_unit_purchase.name_plural) + ')');
+ }
+ else
+ {
+ $('#productcard-product-stock-factor-purchase-amount').text('');
+ $('#productcard-product-stock-factor-purchase-qu-name').text('');
+ }
+ $('#productcard-product-stock-value').text(stockValue + ' ' + Grocy.Currency);
$('#productcard-product-last-purchased').text((productDetails.last_purchased || '2999-12-31').substring(0, 10));
$('#productcard-product-last-purchased-timeago').attr("datetime", productDetails.last_purchased || '2999-12-31');
$('#productcard-product-last-used').text((productDetails.last_used || '2999-12-31').substring(0, 10));
@@ -80,13 +93,29 @@ Grocy.Components.ProductCard.Refresh = function(productId)
if (productDetails.last_price !== null)
{
- $('#productcard-product-last-price').text(Number.parseFloat(productDetails.last_price).toLocaleString() + ' ' + Grocy.Currency + ' per ' + productDetails.quantity_unit_purchase.name);
+ if (productDetails.last_qu_factor_purchase_to_stock > 1)
+ {
+ $('#productcard-product-last-price').text(Number.parseFloat(productDetails.last_price).toLocaleString() + ' ' + Grocy.Currency + ' per 1 ' + productDetails.quantity_unit_purchase.name + ' of ' + productDetails.last_qu_factor_purchase_to_stock + ' ' + productDetails.quantity_unit_stock.name_plural);
+ }
+ else
+ {
+ $('#productcard-product-last-price').text(Number.parseFloat(productDetails.last_price).toLocaleString() + ' ' + Grocy.Currency + ' per ' + productDetails.quantity_unit_purchase.name);
+ }
}
else
{
$('#productcard-product-last-price').text(__t('Unknown'));
}
+ if (productDetails.avg_price !== null)
+ {
+ $('#productcard-product-average-price').text(Number.parseFloat(productDetails.avg_price).toLocaleString() + ' ' + Grocy.Currency);
+ }
+ else
+ {
+ $('#productcard-product-average-price').text(__t('Unknown'));
+ }
+
if (productDetails.product.picture_file_name !== null && !productDetails.product.picture_file_name.isEmpty())
{
$("#productcard-product-picture").removeClass("d-none");
diff --git a/public/viewjs/components/productpicker.js b/public/viewjs/components/productpicker.js
index 15649c85..abbd5589 100644
--- a/public/viewjs/components/productpicker.js
+++ b/public/viewjs/components/productpicker.js
@@ -132,6 +132,7 @@ $('#product_id_text_input').on('blur', function(e)
{
return;
}
+ $('#product_id').attr("barcode", "null");
var input = $('#product_id_text_input').val().toString();
var possibleOptionElement = $("#product_id option[data-additional-searchdata*=\"" + input + ",\"]").first();
@@ -139,6 +140,7 @@ $('#product_id_text_input').on('blur', function(e)
if (GetUriParam('addbarcodetoselection') === undefined && input.length > 0 && possibleOptionElement.length > 0)
{
$('#product_id').val(possibleOptionElement.val());
+ $('#product_id').attr("barcode", input);
$('#product_id').data('combobox').refresh();
$('#product_id').trigger('change');
}
diff --git a/public/viewjs/consume.js b/public/viewjs/consume.js
index 1661b955..eb6572cf 100644
--- a/public/viewjs/consume.js
+++ b/public/viewjs/consume.js
@@ -44,17 +44,12 @@
var addBarcode = GetUriParam('addbarcodetoselection');
if (addBarcode !== undefined)
{
- var existingBarcodes = productDetails.product.barcode || '';
- if (existingBarcodes.length === 0)
- {
- productDetails.product.barcode = addBarcode;
- }
- else
- {
- productDetails.product.barcode += ',' + addBarcode;
- }
+ var jsonDataBarcode = {};
+ jsonDataBarcode.barcode = addBarcode;
+ jsonDataBarcode.product_id = jsonForm.product_id;
+ jsonDataBarcode.qu_factor_purchase_to_stock = productDetails.product.qu_factor_purchase_to_stock;
- Grocy.Api.Put('objects/products/' + productDetails.product.id, productDetails.product,
+ Grocy.Api.Post('objects/product_barcodes', jsonDataBarcode,
function(result)
{
$("#flow-info-addbarcodetoselection").addClass("d-none");
@@ -63,7 +58,8 @@
},
function(xhr)
{
- console.error(xhr);
+ Grocy.FrontendHelpers.EndUiBusy("consume-form");
+ Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
}
);
}
diff --git a/public/viewjs/inventory.js b/public/viewjs/inventory.js
index df4727a5..9f173f5d 100644
--- a/public/viewjs/inventory.js
+++ b/public/viewjs/inventory.js
@@ -37,17 +37,13 @@
var addBarcode = GetUriParam('addbarcodetoselection');
if (addBarcode !== undefined)
{
- var existingBarcodes = productDetails.product.barcode || '';
- if (existingBarcodes.length === 0)
- {
- productDetails.product.barcode = addBarcode;
- }
- else
- {
- productDetails.product.barcode += ',' + addBarcode;
- }
+ var jsonDataBarcode = {};
+ jsonDataBarcode.barcode = addBarcode;
+ jsonDataBarcode.product_id = jsonForm.product_id;
+ jsonDataBarcode.qu_factor_purchase_to_stock = productDetails.product.qu_factor_purchase_to_stock;
+ jsonDataBarcode.shopping_location_id = jsonForm.shopping_location_id;
- Grocy.Api.Put('objects/products/' + productDetails.product.id, productDetails.product,
+ Grocy.Api.Post('objects/product_barcodes', jsonDataBarcode,
function(result)
{
$("#flow-info-addbarcodetoselection").addClass("d-none");
@@ -56,7 +52,8 @@
},
function(xhr)
{
- console.error(xhr);
+ Grocy.FrontendHelpers.EndUiBusy("inventory-form");
+ Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
}
);
}
diff --git a/public/viewjs/mealplan.js b/public/viewjs/mealplan.js
index b1dedbc7..9570cda5 100644
--- a/public/viewjs/mealplan.js
+++ b/public/viewjs/mealplan.js
@@ -180,6 +180,11 @@ var calendar = $("#calendar").fullCalendar({
productDetails.last_price = 0;
}
+ if (productDetails.last_qu_factor_purchase_to_stock === null)
+ {
+ productDetails.last_qu_factor_purchase_to_stock = 1;
+ }
+
element.attr("data-product-details", event.productDetails);
var productOrderMissingButtonDisabledClasses = "disabled";
@@ -205,7 +210,7 @@ var calendar = $("#calendar").fullCalendar({
var costsAndCaloriesPerServing = ""
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{
- costsAndCaloriesPerServing = '
' + productDetails.last_price / productDetails.product.qu_factor_purchase_to_stock * mealPlanEntry.product_amount + ' / ' + productDetails.product.calories * mealPlanEntry.product_amount + ' kcal ' + '';
+ costsAndCaloriesPerServing = '' + productDetails.last_price / productDetails.last_qu_factor_purchase_to_stock * mealPlanEntry.product_amount + ' / ' + productDetails.product.calories * mealPlanEntry.product_amount + ' kcal ' + '';
}
else
{
diff --git a/public/viewjs/productbarcodesform.js b/public/viewjs/productbarcodesform.js
new file mode 100644
index 00000000..29b57822
--- /dev/null
+++ b/public/viewjs/productbarcodesform.js
@@ -0,0 +1,65 @@
+$('#save-barcode-button').on('click', function(e)
+{
+ e.preventDefault();
+
+ var jsonData = $('#barcode-form').serializeJSON();
+ Grocy.FrontendHelpers.BeginUiBusy("barcode-form");
+
+ if (Grocy.EditMode === 'create')
+ {
+ Grocy.Api.Post('objects/product_barcodes', jsonData,
+ function(result)
+ {
+ },
+ function(xhr)
+ {
+ Grocy.FrontendHelpers.EndUiBusy("barcode-form");
+ Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
+ }
+ );
+ }
+ else
+ {
+ Grocy.Api.Put('objects/product_barcodes/' + Grocy.EditObjectId, jsonData,
+ function(result)
+ {
+ },
+ function(xhr)
+ {
+ Grocy.FrontendHelpers.EndUiBusy("barcode-form");
+ Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
+ }
+ );
+ }
+
+ window.parent.postMessage(WindowMessageBag("ProductBarcodesChanged"), U("/product/" + GetUriParam("product")));
+ window.parent.postMessage(WindowMessageBag("CloseAllModals"), U("/product/" + GetUriParam("product")));
+});
+
+$('#barcode').on('change', function(e)
+{
+ Grocy.FrontendHelpers.ValidateForm('barcode-form');
+});
+
+$('#qu_factor_purchase_to_stock').on('change', function(e)
+{
+ Grocy.FrontendHelpers.ValidateForm('barcode-form');
+});
+
+$('#barcode-form input').keydown(function(event)
+{
+ if (event.keyCode === 13) //Enter
+ {
+ event.preventDefault();
+
+ if (document.getElementById('barcode-form').checkValidity() === false) //There is at least one validation error
+ {
+ return false;
+ }
+ else
+ {
+ $('#save-barcode-button').click();
+ }
+ }
+});
+Grocy.FrontendHelpers.ValidateForm('barcode-form');
diff --git a/public/viewjs/productform.js b/public/viewjs/productform.js
index 4e4edaa6..3761b2f5 100644
--- a/public/viewjs/productform.js
+++ b/public/viewjs/productform.js
@@ -42,6 +42,26 @@
function(result)
{
Grocy.EditObjectId = result.created_object_id;
+
+ if (prefillBarcode !== undefined)
+ {
+ var jsonDataBarcode = {};
+ jsonDataBarcode.barcode = prefillBarcode;
+ jsonDataBarcode.product_id = result.created_object_id;
+ jsonDataBarcode.qu_factor_purchase_to_stock = jsonData.qu_factor_purchase_to_stock;
+ jsonDataBarcode.shopping_location_id = jsonData.shopping_location_id;
+
+ Grocy.Api.Post('objects/product_barcodes', jsonDataBarcode,
+ function(result)
+ {
+ },
+ function(xhr)
+ {
+ Grocy.FrontendHelpers.EndUiBusy("barcode-form");
+ Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
+ }
+ );
+ }
Grocy.Components.UserfieldsForm.Save(function()
{
if (jsonData.hasOwnProperty("picture_file_name") && !Grocy.DeleteProductPictureOnSave)
@@ -166,32 +186,19 @@
}
});
-$('#barcode-taginput').tagsManager({
- 'hiddenTagListName': 'barcode',
- 'tagsContainer': '#barcode-taginput-container',
- 'tagClass': 'badge badge-secondary',
- 'delimiters': [13, 44]
-});
-
-if (Grocy.EditMode === 'edit')
-{
- Grocy.Api.Get('objects/products/' + Grocy.EditObjectId,
- function (product)
+Grocy.Api.Get('stock/products/' + Grocy.EditObjectId,
+ function(productDetails)
+ {
+ if (productDetails.last_purchased == null)
{
- if (product.barcode !== null && product.barcode.length > 0)
- {
- product.barcode.split(',').forEach(function(item)
- {
- $('#barcode-taginput').tagsManager('pushTag', item);
- });
- }
- },
- function(xhr)
- {
- console.error(xhr);
+ $('#qu_id_stock').removeAttr("disabled");
}
- );
-}
+ },
+ function(xhr)
+ {
+ console.error(xhr);
+ }
+);
var prefillName = GetUriParam('prefillname');
if (prefillName !== undefined)
@@ -201,16 +208,6 @@ if (prefillName !== undefined)
}
var prefillBarcode = GetUriParam('prefillbarcode');
-if (prefillBarcode !== undefined)
-{
- $('#barcode-taginput').tagsManager('pushTag', prefillBarcode);
- $('#name').focus();
-}
-
-$("#barcode-taginput").on("blur", function(e)
-{
- $("#barcode-taginput").tagsManager("pushTag", $("#barcode-taginput").val());
-});
$('.input-group-qu').on('change', function(e)
{
@@ -249,12 +246,34 @@ $('#product-form input').keyup(function(event)
{
$("#qu-conversion-add-button").removeClass("disabled");
}
+ if (document.getElementById('product-form').checkValidity() === false) //There is at least one validation error
+ {
+ $("#barcode-add-button").addClass("disabled");
+ }
+ else
+ {
+ if (prefillBarcode === undefined)
+ {
+ $("#barcode-add-button").removeClass("disabled");
+ }
+ }
});
$('#product-form select').change(function(event)
{
Grocy.FrontendHelpers.ValidateForm('product-form');
+ if (document.getElementById('product-form').checkValidity() === false) //There is at least one validation error
+ {
+ $("#barcode-add-button").addClass("disabled");
+ }
+ else
+ {
+ if (prefillBarcode === undefined)
+ {
+ $("#barcode-add-button").removeClass("disabled");
+ }
+ }
if (document.getElementById('product-form').checkValidity() === false) //There is at least one validation error
{
$("#qu-conversion-add-button").addClass("disabled");
@@ -365,6 +384,17 @@ var quConversionsTable = $('#qu-conversions-table').DataTable({
$('#qu-conversions-table tbody').removeClass("d-none");
quConversionsTable.columns.adjust().draw();
+var barcodeTable = $('#barcode-table').DataTable({
+ 'order': [[1, 'asc']],
+ "orderFixed": [[1, 'asc']],
+ 'columnDefs': [
+ { 'orderable': false, 'targets': 0 },
+ { 'searchable': false, "targets": 0 }
+ ]
+});
+$('#barcode-table tbody').removeClass("d-none");
+barcodeTable.columns.adjust().draw();
+
Grocy.Components.UserfieldsForm.Load();
$("#name").trigger("keyup");
$('#name').focus();
@@ -412,17 +442,44 @@ $(document).on('click', '.qu-conversion-delete-button', function(e)
});
});
-$(document).on('click', '.qu-conversion-edit-button', function (e)
+$(document).on('click', '.barcode-delete-button', function(e)
{
- var id = $(e.currentTarget).attr('data-qu-conversion-id');
- Grocy.ProductEditFormRedirectUri = U("/quantityunitconversion/" + id.toString() + "?product=editobjectid");
- $('#save-product-button').click();
-});
+ var objectId = $(e.currentTarget).attr('data-barcode-id');
+ var productId = $(e.currentTarget).attr('data-product-id');
+ var barcode = $(e.currentTarget).attr('data-barcode');
+ var productBarcode = $(e.currentTarget).attr('data-product-barcode');
-$("#qu-conversion-add-button").on("click", function(e)
-{
- Grocy.ProductEditFormRedirectUri = U("/quantityunitconversion/new?product=editobjectid");
- $('#save-product-button').click();
+ bootbox.confirm({
+ message: __t('Are you sure to remove this barcode?'),
+ closeButton: false,
+ buttons: {
+ confirm: {
+ label: __t('Yes'),
+ className: 'btn-success'
+ },
+ cancel: {
+ label: __t('No'),
+ className: 'btn-danger'
+ }
+ },
+ callback: function(result)
+ {
+ if (result === true)
+ {
+ Grocy.Api.Delete('objects/product_barcodes/' + objectId, { },
+ function(result)
+ {
+ Grocy.ProductEditFormRedirectUri = "reload";
+ $('#save-product-button').click();
+ },
+ function(xhr)
+ {
+ console.error(xhr);
+ }
+ );
+ }
+ }
+ });
});
$('#qu_id_purchase').blur(function(e)
@@ -436,12 +493,12 @@ $('#qu_id_purchase').blur(function(e)
}
});
-$(document).on("Grocy.BarcodeScanned", function(e, barcode, target)
+$(window).on("message", function(e)
{
- if (target != "#barcode-taginput")
+ var data = e.originalEvent.data;
+
+ if (data.Message === "ProductBarcodesChanged" || data.Message === "ProductQUConversionChanged")
{
- return;
+ window.location.reload();
}
-
- $("#barcode-taginput").tagsManager("pushTag", barcode);
});
diff --git a/public/viewjs/productgroupform.js b/public/viewjs/productgroupform.js
index d4095fa4..86d5b33d 100644
--- a/public/viewjs/productgroupform.js
+++ b/public/viewjs/productgroupform.js
@@ -13,7 +13,7 @@
Grocy.EditObjectId = result.created_object_id;
Grocy.Components.UserfieldsForm.Save(function()
{
- window.location.href = U('/productgroups');
+ window.parent.postMessage(WindowMessageBag("CloseAllModals"), U("/productgroups"));
});
},
function(xhr)
@@ -30,7 +30,7 @@
{
Grocy.Components.UserfieldsForm.Save(function()
{
- window.location.href = U('/productgroups');
+ window.parent.postMessage(WindowMessageBag("CloseAllModals"), U("/productgroups"));
});
},
function(xhr)
diff --git a/public/viewjs/productgroups.js b/public/viewjs/productgroups.js
index d935977b..af64c63d 100644
--- a/public/viewjs/productgroups.js
+++ b/public/viewjs/productgroups.js
@@ -55,3 +55,12 @@ $(document).on('click', '.product-group-delete-button', function(e)
}
});
});
+$(window).on("message", function(e)
+{
+ var data = e.originalEvent.data;
+
+ if (data.Message === "CloseAllModals")
+ {
+ window.location.reload();
+ }
+});
diff --git a/public/viewjs/products.js b/public/viewjs/products.js
index 2a040a7d..ace04d3d 100644
--- a/public/viewjs/products.js
+++ b/public/viewjs/products.js
@@ -49,7 +49,7 @@ $(document).on('click', '.product-delete-button', function (e)
if (stockAmount.toString() == "0")
{
bootbox.confirm({
- message: __t('Are you sure to delete product "%s"?', objectName),
+ message: __t('Are you sure you want to deactivate this product "%s"?', objectName),
closeButton: false,
buttons: {
confirm: {
@@ -65,7 +65,9 @@ $(document).on('click', '.product-delete-button', function (e)
{
if (result === true)
{
- Grocy.Api.Delete('objects/products/' + objectId, {},
+ jsonData = {};
+ jsonData.active = 0;
+ Grocy.Api.Put('objects/products/' + objectId, jsonData,
function (result)
{
window.location.href = U('/products');
@@ -82,8 +84,8 @@ $(document).on('click', '.product-delete-button', function (e)
else
{
bootbox.alert({
- title: __t('Delete not possible'),
- message: __t('This product cannot be deleted because it is in stock, please remove the stock amount first.') + '
' + __t('Stock amount') + ': ' + stockAmount + ' ' + __n(stockAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural),
+ title: __t('Deactivation not possible'),
+ message: __t('This product cannot be deactivated because it is in stock, please remove the stock amount first.') + '
' + __t('Stock amount') + ': ' + stockAmount + ' ' + __n(stockAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural),
closeButton: false
});
}
diff --git a/public/viewjs/purchase.js b/public/viewjs/purchase.js
index 2f5ebad7..7fe1d843 100644
--- a/public/viewjs/purchase.js
+++ b/public/viewjs/purchase.js
@@ -8,12 +8,13 @@
Grocy.Api.Get('stock/products/' + jsonForm.product_id,
function(productDetails)
{
- var amount = jsonForm.amount * productDetails.product.qu_factor_purchase_to_stock;
+ var amount = jsonForm.amount * jsonForm.qu_factor_purchase_to_stock;
var price = "";
if (!jsonForm.price.toString().isEmpty())
{
- price = parseFloat(jsonForm.price).toFixed(2);
+ // price is saved as 1 QU to stock
+ price = parseFloat(jsonForm.price / amount).toFixed(2);
if ($("input[name='price-type']:checked").val() == "total-price")
{
@@ -37,6 +38,7 @@
{
jsonData.location_id = Grocy.Components.LocationPicker.GetValue();
}
+ jsonData.qu_factor_purchase_to_stock = jsonForm.qu_factor_purchase_to_stock;
Grocy.Api.Post('stock/products/' + jsonForm.product_id + '/add', jsonData,
function(result)
@@ -49,17 +51,13 @@
var addBarcode = GetUriParam('addbarcodetoselection');
if (addBarcode !== undefined)
{
- var existingBarcodes = productDetails.product.barcode || '';
- if (existingBarcodes.length === 0)
- {
- productDetails.product.barcode = addBarcode;
- }
- else
- {
- productDetails.product.barcode += ',' + addBarcode;
- }
+ var jsonDataBarcode = {};
+ jsonDataBarcode.barcode = addBarcode;
+ jsonDataBarcode.product_id = jsonForm.product_id;
+ jsonDataBarcode.qu_factor_purchase_to_stock = jsonForm.qu_factor_purchase_to_stock;
+ jsonDataBarcode.shopping_location_id = jsonForm.shopping_location_id;
- Grocy.Api.Put('objects/products/' + productDetails.product.id, productDetails.product,
+ Grocy.Api.Post('objects/product_barcodes', jsonDataBarcode,
function(result)
{
$("#flow-info-addbarcodetoselection").addClass("d-none");
@@ -69,7 +67,7 @@
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy("purchase-form");
- console.error(xhr);
+ Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
}
);
}
@@ -140,13 +138,66 @@ if (Grocy.Components.ProductPicker !== undefined)
{
Grocy.Components.ProductCard.Refresh(productId);
+ if (document.getElementById("product_id").getAttribute("barcode") != "null")
+ {
+ Grocy.Api.Get('productbarcodedetails/' + document.getElementById("product_id").getAttribute("barcode"),
+ function(resultBarcode)
+ {
+ if (resultBarcode != null)
+ {
+ $('#product_id').attr("barcode-qu-factor-purchase-to-stock", resultBarcode.qu_factor_purchase_to_stock);
+ $('#product_id').attr("barcode-shopping-location-id", resultBarcode.shopping_location_id);
+ }
+ else
+ {
+ $('#product_id').attr("barcode-qu-factor-purchase-to-stock", "null");
+ $('#product_id').attr("barcode-shopping-location-id", "null");
+ }
+ },
+ function(xhr)
+ {
+ console.error(xhr);
+ }
+ );
+ }
+ else
+ {
+ $('#product_id').attr("barcode-qu-factor-purchase-to-stock", "null");
+ $('#product_id').attr("barcode-shopping-location-id", "null");
+ }
+
Grocy.Api.Get('stock/products/' + productId,
function(productDetails)
{
- $('#price').val(parseFloat(productDetails.last_price).toLocaleString({ minimumFractionDigits: 2, maximumFractionDigits: 2 }));
+ $('#price').val(parseFloat(productDetails.last_price).toLocaleString({ minimumFractionDigits: 2, maximumFractionDigits: 2 }));
+
+ var qu_factor_purchase_to_stock = null;
+ var barcode_shopping_location_id = null;
+
+ if (document.getElementById("product_id").getAttribute("barcode") != "null" && document.getElementById("product_id").getAttribute("barcode-qu-factor-purchase-to-stock") != "null")
+ {
+ qu_factor_purchase_to_stock = document.getElementById("product_id").getAttribute("barcode-qu-factor-purchase-to-stock");
+ barcode_shopping_location_id = document.getElementById("product_id").getAttribute("barcode-shopping-location-id");
+ }
+ else
+ {
+ if (productDetails.last_qu_factor_purchase_to_stock != null)
+ {
+ qu_factor_purchase_to_stock = productDetails.last_qu_factor_purchase_to_stock;
+ }
+ else
+ {
+ qu_factor_purchase_to_stock = productDetails.product.qu_factor_purchase_to_stock;
+ }
+ }
+
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) {
- if (productDetails.last_shopping_location_id != null)
+ if (barcode_shopping_location_id != null)
+ {
+ Grocy.Components.ShoppingLocationPicker.SetId(barcode_shopping_location_id);
+ }
+ else if (productDetails.last_shopping_location_id != null)
{
Grocy.Components.ShoppingLocationPicker.SetId(productDetails.last_shopping_location_id);
}
@@ -161,15 +212,22 @@ if (Grocy.Components.ProductPicker !== undefined)
Grocy.Components.LocationPicker.SetId(productDetails.location.id);
}
- $('#amount_qu_unit').attr("qu-factor-purchase-to-stock", productDetails.product.qu_factor_purchase_to_stock);
+ $('#amount_qu_unit').attr("qu-factor-purchase-to-stock", qu_factor_purchase_to_stock);
+ $('#amount_qu_unit').attr("quantity-unit-purchase-name", productDetails.quantity_unit_purchase.name);
$('#amount_qu_unit').attr("quantity-unit-stock-name", productDetails.quantity_unit_stock.name);
- if (productDetails.product.qu_id_purchase === productDetails.product.qu_id_stock)
+ $('#amount_qu_unit').attr("quantity-unit-stock-name-plural", productDetails.quantity_unit_stock.name_plural);
+ $('#qu_factor_purchase_to_stock').val(qu_factor_purchase_to_stock);
+
+
+ if (qu_factor_purchase_to_stock == 1)
{
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name);
+ $('#group-qu_factor_purchase_to_stock').addClass('d-none');
}
else
{
- $('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name + " (" + __t("will be multiplied by a factor of %1$s to get %2$s", parseFloat(productDetails.product.qu_factor_purchase_to_stock).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: 2 }), __n(2, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural)) + ")");
+ $('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name + " (" + __t("will be multiplied by a factor of %1$s to get %2$s", parseFloat(qu_factor_purchase_to_stock).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: 2 }), __n(2, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural)) + ")");
+ $('#group-qu_factor_purchase_to_stock').removeClass('d-none');
}
var priceTypeUnitPrice = $("#price-type-unit-price");
@@ -193,7 +251,7 @@ if (Grocy.Components.ProductPicker !== undefined)
if (productDetails.product.enable_tare_weight_handling == 1)
{
- var minAmount = parseFloat(productDetails.product.tare_weight) / productDetails.product.qu_factor_purchase_to_stock + parseFloat(productDetails.stock_amount);
+ var minAmount = parseFloat(productDetails.product.tare_weight) / qu_factor_purchase_to_stock + parseFloat(productDetails.stock_amount);
$("#amount").attr("min", minAmount);
$("#amount").attr("step", "0.0001");
$("#amount").parent().find(".invalid-feedback").text(__t('The amount cannot be lower than %s', minAmount.toLocaleString()));
@@ -339,6 +397,15 @@ $('#amount').on('change', function(e)
Grocy.FrontendHelpers.ValidateForm('purchase-form');
});
+$('#qu_factor_purchase_to_stock').on('change', function(e)
+{
+ var value = $(e.target).val();
+ $('#amount_qu_unit').attr("qu-factor-purchase-to-stock", value);
+ $('#amount_qu_unit').text(document.getElementById("amount_qu_unit").getAttribute("quantity-unit-purchase-name") + " (" + __t("will be multiplied by a factor of %1$s to get %2$s", parseFloat(value).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: 2 }), __n(2, document.getElementById("amount_qu_unit").getAttribute("quantity-unit-stock-name"), document.getElementById("amount_qu_unit").getAttribute("quantity-unit-stock-name-plural")) + ")"));
+ refreshPriceHint();
+ Grocy.FrontendHelpers.ValidateForm('purchase-form');
+});
+
if (GetUriParam("flow") === "shoppinglistitemtostock")
{
$('#amount').val(parseFloat(GetUriParam("amount")).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: 4 }));
diff --git a/public/viewjs/quantityunitconversionform.js b/public/viewjs/quantityunitconversionform.js
index a21e74ed..5137fc7e 100644
--- a/public/viewjs/quantityunitconversionform.js
+++ b/public/viewjs/quantityunitconversionform.js
@@ -25,7 +25,8 @@
}
else
{
- window.location.href = U("/product/" + GetUriParam("product"));
+ window.parent.postMessage(WindowMessageBag("ProductQUConversionChanged"), U("/product/" + GetUriParam("product")));
+ window.parent.postMessage(WindowMessageBag("CloseAllModals"), U("/product/" + GetUriParam("product")));
}
});
},
@@ -54,7 +55,8 @@
}
else
{
- window.location.href = U("/product/" + GetUriParam("product"));
+ window.parent.postMessage(WindowMessageBag("ProductQUConversionChanged"), U("/product/" + GetUriParam("product")));
+ window.parent.postMessage(WindowMessageBag("CloseAllModals"), U("/product/" + GetUriParam("product")));
}
});
},
@@ -79,7 +81,8 @@
}
else
{
- window.location.href = U("/product/" + GetUriParam("product"));
+ window.parent.postMessage(WindowMessageBag("ProductQUConversionChanged"), U("/product/" + GetUriParam("product")));
+ window.parent.postMessage(WindowMessageBag("CloseAllModals"), U("/product/" + GetUriParam("product")));
}
});
},
diff --git a/public/viewjs/stockentries.js b/public/viewjs/stockentries.js
index 8f173a05..20c9a8ec 100644
--- a/public/viewjs/stockentries.js
+++ b/public/viewjs/stockentries.js
@@ -177,6 +177,7 @@ function RefreshStockEntryRow(stockRowId)
);
$('#stock-' + stockRowId + '-price').text(result.price);
+ $('#stock-' + stockRowId + '-qu-factor-purchase-to-stock').text(result.qu_factor_purchase_to_stock);
$('#stock-' + stockRowId + '-purchased-date').text(result.purchased_date);
$('#stock-' + stockRowId + '-purchased-date-timeago').attr('datetime', result.purchased_date + ' 23:59:59');
diff --git a/public/viewjs/stockentryform.js b/public/viewjs/stockentryform.js
index 19817541..d0b41f81 100644
--- a/public/viewjs/stockentryform.js
+++ b/public/viewjs/stockentryform.js
@@ -26,6 +26,9 @@
jsonData.location_id = 1;
}
jsonData.price = price;
+ jsonData.qu_factor_purchase_to_stock = jsonForm.qu_factor_purchase_to_stock;
+ console.log(jsonForm);
+ console.log(jsonData);
jsonData.open = $("#open").is(":checked");
diff --git a/public/viewjs/stockoverview.js b/public/viewjs/stockoverview.js
index 4cb6e205..191ec9a7 100644
--- a/public/viewjs/stockoverview.js
+++ b/public/viewjs/stockoverview.js
@@ -1,9 +1,10 @@
var stockOverviewTable = $('#stock-overview-table').DataTable({
- 'order': [[3, 'asc']],
+ 'order': [[4, 'asc']],
+ 'colReorder': false,
'columnDefs': [
{ 'orderable': false, 'targets': 0 },
{ 'searchable': false, "targets": 0 },
- { 'visible': false, 'targets': 4 },
+ { 'searchable': false, "targets": 0 },
{ 'visible': false, 'targets': 5 },
{ 'visible': false, 'targets': 6 }
],
@@ -19,7 +20,7 @@ $("#location-filter").on("change", function()
value = "";
}
- stockOverviewTable.column(4).search(value).draw();
+ stockOverviewTable.column(5).search(value).draw();
});
$("#product-group-filter").on("change", function()
@@ -30,7 +31,7 @@ $("#product-group-filter").on("change", function()
value = "";
}
- stockOverviewTable.column(6).search(value).draw();
+ stockOverviewTable.column(2).search(value).draw();
});
$("#status-filter").on("change", function()
@@ -44,7 +45,7 @@ $("#status-filter").on("change", function()
// Transfer CSS classes of selected element to dropdown element (for background)
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
- stockOverviewTable.column(5).search(value).draw();
+ stockOverviewTable.column(6).search(value).draw();
});
$(".status-filter-message").on("click", function()
@@ -252,7 +253,7 @@ function RefreshProductRow(productId)
$('#product-' + productId + '-qu-name').text(__n(result.stock_amount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural));
$('#product-' + productId + '-amount').text(result.stock_amount);
$('#product-' + productId + '-consume-all-button').attr('data-consume-amount', result.stock_amount);
-
+ $('#product-' + productId + '-factor-purchase-amount').text(__t('( %s', result.stock_factor_purchase_amount));
$('#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);
diff --git a/public/viewjs/transfer.js b/public/viewjs/transfer.js
index c803b303..bc3dc3bf 100644
--- a/public/viewjs/transfer.js
+++ b/public/viewjs/transfer.js
@@ -30,17 +30,12 @@
if (addBarcode !== undefined)
{
- var existingBarcodes = productDetails.product.barcode || '';
- if (existingBarcodes.length === 0)
- {
- productDetails.product.barcode = addBarcode;
- }
- else
- {
- productDetails.product.barcode += ',' + addBarcode;
- }
+ var jsonDataBarcode = {};
+ jsonDataBarcode.barcode = addBarcode;
+ jsonDataBarcode.product_id = jsonForm.product_id;
+ jsonDataBarcode.qu_factor_purchase_to_stock = productDetails.product.qu_factor_purchase_to_stock;
- Grocy.Api.Put('objects/products/' + productDetails.product.id, productDetails.product,
+ Grocy.Api.Post('objects/product_barcodes', jsonDataBarcode,
function(result)
{
$("#flow-info-addbarcodetoselection").addClass("d-none");
@@ -49,7 +44,8 @@
},
function(xhr)
{
- console.error(xhr);
+ Grocy.FrontendHelpers.EndUiBusy("transfer-form");
+ Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
}
);
}
diff --git a/routes.php b/routes.php
index c432ecee..4da065c0 100644
--- a/routes.php
+++ b/routes.php
@@ -45,6 +45,7 @@ $app->group('', function(RouteCollectorProxy $group)
$group->get('/stockentry/{entryId}', '\Grocy\Controllers\StockController:StockEntryEditForm');
$group->get('/products', '\Grocy\Controllers\StockController:ProductsList');
$group->get('/product/{productId}', '\Grocy\Controllers\StockController:ProductEditForm');
+ $group->get('/productbarcodes/{productBarcodeId}', '\Grocy\Controllers\StockController:ProductBarcodesEditForm');
$group->get('/stocksettings', '\Grocy\Controllers\StockController:StockSettings');
$group->get('/locations', '\Grocy\Controllers\StockController:LocationsList');
$group->get('/location/{locationId}', '\Grocy\Controllers\StockController:LocationEditForm');
@@ -199,6 +200,7 @@ $app->group('/api', function(RouteCollectorProxy $group)
$group->get('/stock/transactions/{transactionId}', '\Grocy\Controllers\StockApiController:StockTransactions');
$group->post('/stock/transactions/{transactionId}/undo', '\Grocy\Controllers\StockApiController:UndoTransaction');
$group->get('/stock/barcodes/external-lookup/{barcode}', '\Grocy\Controllers\StockApiController:ExternalBarcodeLookup');
+ $group->get('/productbarcodedetails/{barcode}', '\Grocy\Controllers\StockApiController:ProductBarcodeDetails');
}
// Shopping list
diff --git a/services/DemoDataGeneratorService.php b/services/DemoDataGeneratorService.php
index 29d426a7..bea6c23f 100644
--- a/services/DemoDataGeneratorService.php
+++ b/services/DemoDataGeneratorService.php
@@ -96,7 +96,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, calories) VALUES ('{$this->__t_sql('Milk')}', 2, 10, 10, 1, 6, 1); --23
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, parent_product_id) VALUES ('{$this->__t_sql('Milk Chocolate')}', 4, 3, 3, 1, 1, 2); --24
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, parent_product_id) VALUES ('{$this->__t_sql('Dark Chocolate')}', 4, 3, 3, 1, 1, 2); --25
- INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, barcode) VALUES ('{$this->__t_sql('Waffle rolls')}', 4, 3, 3, 1, 1, '22111289'); --26
+ INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$this->__t_sql('Waffle rolls')}', 4, 3, 3, 1, 1); --26
UPDATE products SET calories = 123 WHERE IFNULL(calories, 0) = 0;
/* Prevent invalid quantity unit assignments */
@@ -189,80 +189,80 @@ class DemoDataGeneratorService extends BaseService
$this->getDatabaseService()->ExecuteDbStatement($sql);
$stockService = new StockService();
- $stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $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());
- $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());
- $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());
- $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());
- $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());
- $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());
- $stockService->AddProduct(22, 1, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(23, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(23, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(24, 2, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(25, 2, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
- $stockService->AddProduct(2, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId());
+ $stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 10, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 10, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), 10, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), 10, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), 10, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(21, 1500, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(21, 2500, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(22, 1, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(22, 1, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(23, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(23, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(24, 2, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(25, 2, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
+ $stockService->AddProduct(2, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), 1.0, null, null, $this->NextSupermarketId());
$stockService->AddMissingProductsToShoppingList();
$stockService->OpenProduct(3, 1);
$stockService->OpenProduct(6, 1);
diff --git a/services/StockService.php b/services/StockService.php
index 273b790c..2abfae88 100644
--- a/services/StockService.php
+++ b/services/StockService.php
@@ -25,7 +25,7 @@ class StockService extends BaseService
$missingProductsView = 'stock_missing_products';
}
- $sql = 'SELECT * FROM stock_current WHERE best_before_date IS NOT NULL UNION SELECT id, 0, 0, null, 0, 0, 0 FROM ' . $missingProductsView . ' WHERE id NOT IN (SELECT product_id FROM stock_current)';
+ $sql = 'SELECT * FROM stock_current WHERE best_before_date IS NOT NULL UNION SELECT id, 0, 0, 0, 0, null, 0, 0, 0 FROM ' . $missingProductsView . ' WHERE id NOT IN (SELECT product_id FROM stock_current)';
}
$currentStockMapped = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_GROUP|\PDO::FETCH_OBJ);
@@ -51,12 +51,6 @@ class StockService extends BaseService
return $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
}
- public function GetCurrentProductPrices()
- {
- $sql = 'SELECT * FROM products_current_price';
- return $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
- }
-
public function GetMissingProducts()
{
$sql = 'SELECT * FROM stock_missing_products_including_opened';
@@ -75,7 +69,7 @@ class StockService extends BaseService
public function GetProductIdFromBarcode(string $barcode)
{
- $potentialProduct = $this->getDatabase()->products()->where("',' || barcode || ',' LIKE '%,' || :1 || ',%' AND IFNULL(barcode, '') != ''", $barcode)->limit(1)->fetch();
+ $potentialProduct = $this->getDatabase()->product_barcodes()->where("barcode = :1", $barcode)->fetch();
if ($potentialProduct === null)
{
@@ -102,7 +96,7 @@ class StockService extends BaseService
{
if (!$this->ProductExists($productId))
{
- throw new \Exception('Product does not exist');
+ throw new \Exception('Product does not exist or is inactive');
}
$stockCurrentRow = FindObjectinArrayByPropertyValue($this->GetCurrentStock(), 'product_id', $productId);
@@ -111,6 +105,8 @@ class StockService extends BaseService
{
$stockCurrentRow = new \stdClass();
$stockCurrentRow->amount = 0;
+ $stockCurrentRow->factor_purchase_amount = 0;
+ $stockCurrentRow->value = 0;
$stockCurrentRow->amount_opened = 0;
$stockCurrentRow->amount_aggregated = 0;
$stockCurrentRow->amount_opened_aggregated = 0;
@@ -118,23 +114,20 @@ class StockService extends BaseService
}
$product = $this->getDatabase()->products($productId);
- $productLastPurchased = $this->getDatabase()->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_PURCHASE)->where('undone', 0)->max('purchased_date');
+ $productBarcodes = $this->getDatabase()->product_barcodes()->where('product_id', $productId)->fetchAll();
+ $productLastPurchased = $this->getDatabase()->products_last_purchased()->where('product_id', $productId)->fetch();
$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');
$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);
$averageShelfLifeDays = intval($this->getDatabase()->stock_average_product_shelf_life()->where('id', $productId)->fetch()->average_shelf_life_days);
-
- $lastPrice = null;
+ $lastPrice = $productLastPurchased->price;
+ $lastQuFactorPurchaseToStock = $productLastPurchased->qu_factor_purchase_to_stock;
+ $avgPrice = $this->getDatabase()->products_average_price()->where('product_id', $productId)->fetch();
+ $oldestPrice = $this->getDatabase()->products_oldest_stock_unit_price()->where('product_id', $productId)->fetch();
$defaultShoppingLocation = null;
- $lastShoppingLocation = null;
- $lastLogRow = $this->getDatabase()->stock_log()->where('product_id = :1 AND transaction_type IN (:2, :3) AND undone = 0', $productId, self::TRANSACTION_TYPE_PURCHASE, self::TRANSACTION_TYPE_INVENTORY_CORRECTION)->orderBy('row_created_timestamp', 'DESC')->limit(1)->fetch();
- if ($lastLogRow !== null && !empty($lastLogRow))
- {
- $lastPrice = $lastLogRow->price;
- $lastShoppingLocation = $lastLogRow->shopping_location_id;
- }
+ $lastShoppingLocation = $productLastPurchased->shopping_location_id;
$consumeCount = $this->getDatabase()->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->where('undone = 0 AND spoiled = 0')->sum('amount') * -1;
$consumeCountSpoiled = $this->getDatabase()->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->where('undone = 0 AND spoiled = 1')->sum('amount') * -1;
@@ -146,15 +139,21 @@ class StockService extends BaseService
return array(
'product' => $product,
- 'last_purchased' => $productLastPurchased,
+ 'product_barcodes' => $productBarcodes,
+ 'last_purchased' => $productLastPurchased->purchased_date,
'last_used' => $productLastUsed,
'stock_amount' => $stockCurrentRow->amount,
+ 'stock_factor_purchase_amount' => $stockCurrentRow->factor_purchase_amount,
+ 'stock_value' => $stockCurrentRow->value,
'stock_amount_opened' => $stockCurrentRow->amount_opened,
'stock_amount_aggregated' => $stockCurrentRow->amount_aggregated,
'stock_amount_opened_aggregated' => $stockCurrentRow->amount_opened_aggregated,
'quantity_unit_purchase' => $quPurchase,
'quantity_unit_stock' => $quStock,
'last_price' => $lastPrice,
+ 'last_qu_factor_purchase_to_stock' => $lastQuFactorPurchaseToStock,
+ 'avg_price' => $avgPrice->price,
+ 'oldest_price' => $oldestPrice->price,
'last_shopping_location_id' => $lastShoppingLocation,
'default_shopping_location_id' => $product->shopping_location_id,
'next_best_before_date' => $nextBestBeforeDate,
@@ -169,7 +168,7 @@ class StockService extends BaseService
{
if (!$this->ProductExists($productId))
{
- throw new \Exception('Product does not exist');
+ throw new \Exception('Product does not exist or is inactive');
}
$returnData = array();
@@ -217,11 +216,11 @@ class StockService extends BaseService
return FindAllObjectsInArrayByPropertyValue($stockEntries, 'location_id', $locationId);
}
- public function AddProduct(int $productId, float $amount, $bestBeforeDate, $transactionType, $purchasedDate, $price, $locationId = null, $shoppingLocationId = null, &$transactionId = null)
+ public function AddProduct(int $productId, float $amount, $bestBeforeDate, $transactionType, $purchasedDate, $price, $quFactorPurchaseToStock, $locationId = null, $shoppingLocationId = null, &$transactionId = null)
{
if (!$this->ProductExists($productId))
{
- throw new \Exception('Product does not exist');
+ throw new \Exception('Product does not exist or is inactive');
}
// Tare weight handling
@@ -275,6 +274,7 @@ class StockService extends BaseService
'location_id' => $locationId,
'transaction_id' => $transactionId,
'shopping_location_id' => $shoppingLocationId,
+ 'qu_factor_purchase_to_stock' => $quFactorPurchaseToStock,
));
$logRow->save();
@@ -289,6 +289,7 @@ class StockService extends BaseService
'price' => $price,
'location_id' => $locationId,
'shopping_location_id' => $shoppingLocationId,
+ 'qu_factor_purchase_to_stock' => $quFactorPurchaseToStock,
));
$stockRow->save();
@@ -304,7 +305,7 @@ class StockService extends BaseService
{
if (!$this->ProductExists($productId))
{
- throw new \Exception('Product does not exist');
+ throw new \Exception('Product does not exist or is inactive');
}
if ($locationId !== null && !$this->LocationExists($locationId))
@@ -423,7 +424,7 @@ class StockService extends BaseService
{
if (!$this->ProductExists($productId))
{
- throw new \Exception('Product does not exist');
+ throw new \Exception('Product does not exist or is inactive');
}
if (!$this->LocationExists($locationIdFrom))
@@ -509,6 +510,7 @@ class StockService extends BaseService
'stock_id' => $stockEntry->stock_id,
'transaction_type' => self::TRANSACTION_TYPE_TRANSFER_FROM,
'price' => $stockEntry->price,
+ 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock,
'opened_date' => $stockEntry->opened_date,
'location_id' => $stockEntry->location_id,
'correlation_id' => $correlationId,
@@ -524,6 +526,7 @@ class StockService extends BaseService
'stock_id' => $stockEntry->stock_id,
'transaction_type' => self::TRANSACTION_TYPE_TRANSFER_TO,
'price' => $stockEntry->price,
+ 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock,
'opened_date' => $stockEntry->opened_date,
'location_id' => $locationIdTo,
'correlation_id' => $correlationId,
@@ -550,6 +553,7 @@ class StockService extends BaseService
'stock_id' => $stockEntry->stock_id,
'transaction_type' => self::TRANSACTION_TYPE_TRANSFER_FROM,
'price' => $stockEntry->price,
+ 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock,
'opened_date' => $stockEntry->opened_date,
'location_id' => $stockEntry->location_id,
'correlation_id' => $correlationId,
@@ -565,6 +569,7 @@ class StockService extends BaseService
'stock_id' => $stockEntry->stock_id,
'transaction_type' => self::TRANSACTION_TYPE_TRANSFER_TO,
'price' => $stockEntry->price,
+ 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock,
'opened_date' => $stockEntry->opened_date,
'location_id' => $locationIdTo,
'correlation_id' => $correlationId,
@@ -584,6 +589,7 @@ class StockService extends BaseService
'best_before_date' => $newBestBeforeDate,
'purchased_date' => $stockEntry->purchased_date,
'stock_id' => $stockEntry->stock_id,
+ 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock,
'price' => $stockEntry->price,
'location_id' => $locationIdTo,
'open' => $stockEntry->open,
@@ -598,7 +604,7 @@ class StockService extends BaseService
return $this->getDatabase()->lastInsertId();
}
- public function EditStockEntry(int $stockRowId, float $amount, $bestBeforeDate, $locationId, $shoppingLocationId, $price, $open, $purchasedDate)
+ public function EditStockEntry(int $stockRowId, float $amount, $bestBeforeDate, $locationId, $shoppingLocationId, $price, $open, $purchasedDate, $quFactorPurchaseToStock)
{
$stockRow = $this->getDatabase()->stock()->where('id = :1', $stockRowId)->fetch();
@@ -621,6 +627,7 @@ class StockService extends BaseService
'opened_date' => $stockRow->opened_date,
'location_id' => $stockRow->location_id,
'shopping_location_id' => $stockRow->shopping_location_id,
+ 'qu_factor_purchase_to_stock' => $stockRow->qu_factor_purchase_to_stock,
'correlation_id' => $correlationId,
'transaction_id' => $transactionId,
'stock_row_id' => $stockRow->id
@@ -645,6 +652,7 @@ class StockService extends BaseService
'shopping_location_id' => $shoppingLocationId,
'opened_date' => $openedDate,
'open' => $open,
+ 'qu_factor_purchase_to_stock' => $quFactorPurchaseToStock,
'purchased_date' => $purchasedDate
));
@@ -659,6 +667,7 @@ class StockService extends BaseService
'opened_date' => $stockRow->opened_date,
'location_id' => $locationId,
'shopping_location_id' => $shoppingLocationId,
+ 'qu_factor_purchase_to_stock' => $stockRow->qu_factor_purchase_to_stock,
'correlation_id' => $correlationId,
'transaction_id' => $transactionId,
'stock_row_id' => $stockRow->id
@@ -672,7 +681,7 @@ class StockService extends BaseService
{
if (!$this->ProductExists($productId))
{
- throw new \Exception('Product does not exist');
+ throw new \Exception('Product does not exist or is inactive');
}
$productDetails = (object)$this->GetProductDetails($productId);
@@ -728,7 +737,7 @@ class StockService extends BaseService
{
if (!$this->ProductExists($productId))
{
- throw new \Exception('Product does not exist');
+ throw new \Exception('Product does not exist or is inactive');
}
$productStockAmountUnopened = $this->getDatabase()->stock()->where('product_id = :1 AND open = 0', $productId)->sum('amount');
@@ -908,7 +917,7 @@ class StockService extends BaseService
if (!$this->ProductExists($productId))
{
- throw new \Exception('Product does not exist');
+ throw new \Exception('Product does not exist or is inactive');
}
$alreadyExistingEntry = $this->getDatabase()->shopping_list()->where('product_id = :1 AND shopping_list_id = :2', $productId, $listId)->fetch();
@@ -934,7 +943,7 @@ class StockService extends BaseService
private function ProductExists($productId)
{
- $productRow = $this->getDatabase()->products()->where('id = :1', $productId)->fetch();
+ $productRow = $this->getDatabase()->products()->where('id = :1 and active = 1', $productId)->fetch();
return $productRow !== null;
}
@@ -1145,6 +1154,7 @@ class StockService extends BaseService
'best_before_date' => $logRow->best_before_date,
'purchased_date' => $logRow->purchased_date,
'price' => $logRow->price,
+ 'qu_factor_purchase_to_stock' => $logRow->qu_factor_purchase_to_stock,
'location_id' => $logRow->location_id,
'open' => $open,
'opened_date' => $openedDate
diff --git a/views/components/numberpicker.blade.php b/views/components/numberpicker.blade.php
index 623e0103..3ffa3cb6 100644
--- a/views/components/numberpicker.blade.php
+++ b/views/components/numberpicker.blade.php
@@ -16,7 +16,7 @@
@php if(!isset($isRequired)) { $isRequired = true; } @endphp
@php if(!isset($noNameAttribute)) { $noNameAttribute = false; } @endphp
-
+
-
{{ $__t('Stock amount') . ' / ' . $__t('Quantity unit') }}:
+
{{ $__t('Stock amount') }}:
+
+
+
{{ $__t('Stock value') }}:
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{{ $__t('Default location') }}: @endif
{{ $__t('Last purchased') }}:
{{ $__t('Last used') }}:
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{{ $__t('Last price') }}: @endif
+ @if (GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{{ $__t('Average price') }}: @endif
@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{{ $__t('Average shelf life') }}: @endif
{{ $__t('Spoil rate') }}:
diff --git a/views/components/productpicker.blade.php b/views/components/productpicker.blade.php
index 99998514..2d2cf033 100644
--- a/views/components/productpicker.blade.php
+++ b/views/components/productpicker.blade.php
@@ -21,7 +21,7 @@
{{ $__t('You have to select a product') }}
diff --git a/views/consume.blade.php b/views/consume.blade.php
index c5234717..a0471b05 100644
--- a/views/consume.blade.php
+++ b/views/consume.blade.php
@@ -30,6 +30,7 @@
@include('components.productpicker', array(
'products' => $products,
+ 'barcodes' => $barcodes,
'nextInputSelector' => '#amount',
'disallowAddProductWorkflows' => true
))
diff --git a/views/inventory.blade.php b/views/inventory.blade.php
index ea1f885e..95b8e717 100644
--- a/views/inventory.blade.php
+++ b/views/inventory.blade.php
@@ -13,6 +13,7 @@
@include('components.productpicker', array(
'products' => $products,
+ 'barcodes' => $barcodes,
'nextInputSelector' => '#new_amount'
))
diff --git a/views/productbarcodesform.blade.php b/views/productbarcodesform.blade.php
new file mode 100644
index 00000000..7f7b3228
--- /dev/null
+++ b/views/productbarcodesform.blade.php
@@ -0,0 +1,71 @@
+@extends('layout.default')
+
+@if($mode == 'edit')
+ @section('title', $__t('Edit Barcode'))
+@else
+ @section('title', $__t('Create Barcode'))
+@endif
+
+@section('viewJsName', 'productbarcodesform')
+
+@section('content')
+
+
+
@yield('title')
+
+
+
+
+
+
+
{{ $__t('Barcode for product') }} {{ $product->name }}
+
+
+
+ @if($mode == 'edit')
+
+ @endif
+
+
+
+
+@stop
diff --git a/views/productform.blade.php b/views/productform.blade.php
index 446ad7d7..5c566300 100644
--- a/views/productform.blade.php
+++ b/views/productform.blade.php
@@ -46,6 +46,14 @@
{{ $__t('A name is required') }}
+
+
@php $prefillById = ''; if($mode=='edit') { $prefillById = $product->parent_product_id; } @endphp
@php
$hint = '';
@@ -56,7 +64,6 @@
@endphp
@include('components.productpicker', array(
'products' => $products,
- 'nextInputSelector' => '#barcode-taginput',
'prefillById' => $prefillById,
'disallowAllProductWorkflows' => true,
'isRequired' => false,
@@ -70,14 +77,6 @@
-
-
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)