From 8a36b094854a3047010ee348871a762f39201a26 Mon Sep 17 00:00:00 2001 From: Katharina Bogad Date: Sun, 6 Jun 2021 22:05:29 +0200 Subject: [PATCH] Grocycode: Productpicker, StockService --- .vscode/settings.json | 18 ++- helpers/Grocycode.php | 142 ++++++++++++++++++++++ public/viewjs/components/productpicker.js | 18 ++- public/viewjs/consume.js | 13 ++ services/StockService.php | 24 ++-- 5 files changed, 205 insertions(+), 10 deletions(-) create mode 100644 helpers/Grocycode.php diff --git a/.vscode/settings.json b/.vscode/settings.json index 85356897..1a8875b2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,4 +14,20 @@ "php-cs-fixer.formatHtml": true, "php-cs-fixer.autoFixBySemicolon": true, "php-cs-fixer.onsave": true, -} + "phpfmt.passes": [ + "PSR2KeywordsLowerCase", + "PSR2LnAfterNamespace", + "PSR2CurlyOpenNextLine", + "PSR2ModifierVisibilityStaticOrder", + "PSR2SingleEmptyLineAndStripClosingTag", + "ReindentSwitchBlocks", + "AllmanStyleBraces", + "StripExtraCommaInArray" + ], + "phpfmt.exclude": [ + "ReindentComments", + "StripNewlineWithinClassBody" + ], + "phpfmt.psr2": false, + "phpfmt.indent_with_space": false, +} \ No newline at end of file diff --git a/helpers/Grocycode.php b/helpers/Grocycode.php new file mode 100644 index 00000000..beb6a143 --- /dev/null +++ b/helpers/Grocycode.php @@ -0,0 +1,142 @@ + + */ +class Grocycode +{ + + public const PRODUCT = "p"; + public const BATTERY = "b"; + public const CHORE = "c"; + + public const MAGIC = "grcy"; + + /** + * An array that registers all valid grocycode types. Register yours here by appending to this array. + */ + public static $Items = [self::PRODUCT, self::BATTERY, self::CHORE]; + + private $type, $id, $extra_data = []; + + /** + * Validates a grocycode. + * + * Returns true, if a supplied $code is a valid grocycode, false otherwise. + * + * @return bool + */ + public static function Validate(string $code) + { + try + { + $gc = new self($code); + return true; + } + catch (Exception $e) + { + return false; + } + } + + /** + * Constructs a new instance of the Grocycode class. + * + * Because php doesn't support overloading, this is a proxy + * to either setFromCode($code) or setFromData($type, $id, $extra_data = []). + */ + public function __construct(...$args) + { + $argc = count($args); + if ($argc == 1) + { + $this->setFromCode($args[0]); + return; + } + else if ($argc == 2 || $argc == 3) + { + if ($argc == 2) + { + $args[] = []; + } + $this->setFromData($args[0], $args[1], $args[2]); + return; + } + + throw new \Exception("No suitable overload found."); + } + + /** + * Parses a grocycode. + */ + private function setFromCode($code) + { + $parts = array_reverse(explode(":", $barcode)); + if (array_pop($parts) != self::MAGIC) + { + throw new \Exception("Not a grocycode"); + } + + if (!in_array($this->type = array_pop($parts), self::$Items)) + { + throw new \Exception("Unknown grocycode type"); + } + + $this->id = array_pop($parts); + $this->extra_data = array_reverse($parse); + + } + + /** + * Constructs a grocycode from data. + */ + private function setFromData($type, $id, $extra_data = []) + { + if (!is_array($extra_data)) + { + throw new \Exception("Extra data must be array of string"); + } + if (!in_array($type, self::$Items)) + { + throw new \Exception("Unknown grocycode type"); + } + + $this->type = $type; + $this->id = $id; + $this->extra_data = $extra_data; + } + + public function GetId() + { + return $this->id; + } + + public function GetExtraData() + { + return $this->extra_data; + } + + public function GetType() + { + return $this->type; + } + + public function __toString(): string + { + $arr = array_merge([self::MAGIC, $this->type, $this->id], $this->extra_data); + + return implode(":", $arr); + } +} diff --git a/public/viewjs/components/productpicker.js b/public/viewjs/components/productpicker.js index 42aa8126..6345acb5 100644 --- a/public/viewjs/components/productpicker.js +++ b/public/viewjs/components/productpicker.js @@ -147,7 +147,23 @@ $('#product_id_text_input').on('blur', function(e) $('#product_id').attr("barcode", "null"); var input = $('#product_id_text_input').val().toString(); - var possibleOptionElement = $("#product_id option[data-additional-searchdata*=\"" + input + ",\"]").first(); + var possibleOptionElement = []; + + // did we enter a grocycode? + if (input.startsWith("grcy")) + { + var gc = input.split(":"); + if (gc[1] == "p") + { + // find product id + possibleOptionElement = $("#product_id option[value=\"" + gc[2] + "\"]").first(); + $("#product_id").data("grocycode", true); + } + } + else // process barcode as usual + { + possibleOptionElement = $("#product_id option[data-additional-searchdata*=\"" + input + ",\"]").first(); + } if (GetUriParam('flow') === undefined && input.length > 0 && possibleOptionElement.length > 0) { diff --git a/public/viewjs/consume.js b/public/viewjs/consume.js index 5a3ea2f4..a94c55e1 100644 --- a/public/viewjs/consume.js +++ b/public/viewjs/consume.js @@ -223,6 +223,18 @@ $("#location_id").on('change', function(e) { stockId = GetUriParam('stockId'); } + else + { + // try to get stock id from grocycode + if ($("#product_id").data("grocycode")) + { + var gc = $("#product_id").attr("barcode").split(":"); + if (gc.length == 4) + { + stockId = gc[3]; + } + } + } if (locationId) { @@ -249,6 +261,7 @@ $("#location_id").on('change', function(e) if (stockEntry.stock_id == stockId) { + $("#use_specific_stock_entry").click(); $("#specific_stock_entry").val(stockId); } } diff --git a/services/StockService.php b/services/StockService.php index 8aa04bf0..d7efedce 100644 --- a/services/StockService.php +++ b/services/StockService.php @@ -1,7 +1,8 @@ amount < $amountToAdd) - { + { $alreadyExistingEntry->update([ 'amount' => $amountToAdd, 'shopping_list_id' => $listId @@ -173,7 +174,7 @@ class StockService extends BaseService 'stock_id' => $stockId, 'price' => $price, 'location_id' => $locationId, - 'shopping_location_id' => $shoppingLocationId, + 'shopping_location_id' => $shoppingLocationId ]); $stockRow->save(); @@ -240,7 +241,7 @@ class StockService extends BaseService throw new \Exception('Location does not exist'); } - $productDetails = (object)$this->GetProductDetails($productId); + $productDetails = (object) $this->GetProductDetails($productId); // Tare weight handling // The given amount is the new total amount including the container weight (gross) @@ -280,7 +281,7 @@ class StockService extends BaseService // TODO: This check doesn't really check against products only at the given location // (as GetProductDetails returns the stock_amount_aggregated of all locations) // However, $potentialStockEntries are filtered accordingly, so this currently isn't really a problem at the end - $productStockAmount = ((object)$this->GetProductDetails($productId))->stock_amount_aggregated; + $productStockAmount = ((object) $this->GetProductDetails($productId))->stock_amount_aggregated; if ($amount > $productStockAmount) { throw new \Exception('Amount to be consumed cannot be > current stock amount (if supplied, at the desired location)'); @@ -451,7 +452,7 @@ class StockService extends BaseService if ($pluginOutput !== null) { // Lookup was successful if ($addFoundProduct === true) - { + { // Add product to database and include new product id in output $newRow = $this->getDatabase()->products()->createRow($pluginOutput); $newRow->save(); @@ -640,6 +641,13 @@ class StockService extends BaseService public function GetProductIdFromBarcode(string $barcode) { + // first, try to parse this as a product grocycode + if (Grocycode::Validate($barcode)) + { + $gc = new Grocycode($barcode); + return intval($gc->GetId()); + } + $potentialProduct = $this->getDatabase()->product_barcodes()->where('barcode = :1', $barcode)->fetch(); if ($potentialProduct === null) @@ -728,7 +736,7 @@ class StockService extends BaseService throw new \Exception('Product does not exist or is inactive'); } - $productDetails = (object)$this->GetProductDetails($productId); + $productDetails = (object) $this->GetProductDetails($productId); if ($price === null) { @@ -787,7 +795,7 @@ class StockService extends BaseService throw new \Exception('Product does not exist or is inactive'); } - $productDetails = (object)$this->GetProductDetails($productId); + $productDetails = (object) $this->GetProductDetails($productId); $productStockAmountUnopened = floatval($productDetails->stock_amount_aggregated) - floatval($productDetails->stock_amount_opened_aggregated); $potentialStockEntries = $this->GetProductStockEntries($productId, true, $allowSubproductSubstitution); $product = $this->getDatabase()->products($productId);