mirror of
https://github.com/grocy/grocy.git
synced 2026-04-05 12:26:15 +02:00
Grocycode: Productpicker, StockService
This commit is contained in:
parent
4a4d9c451f
commit
8a36b09485
18
.vscode/settings.json
vendored
18
.vscode/settings.json
vendored
|
|
@ -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,
|
||||
}
|
||||
142
helpers/Grocycode.php
Normal file
142
helpers/Grocycode.php
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
namespace Grocy\Helpers;
|
||||
|
||||
/**
|
||||
* A class that abstracts grocycode.
|
||||
*
|
||||
* grocycode is a simple, easily serializable format to reference
|
||||
* stuff within grocy. It consists of n (n ≥ 3) double-colon seperated parts:
|
||||
*
|
||||
* 1. The magic `grcy`
|
||||
* 2. A type identifer, must match `[a-z]+` (i.e. only lowercase ascii, minimum length 1 character)
|
||||
* 3. An object id
|
||||
* 4. Any number of further data fields, double-colon seperated.
|
||||
*
|
||||
* @author Katharina Bogad <katharina@hacked.xyz>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Grocy\Services;
|
||||
|
||||
use Grocy\Helpers\Grocycode;
|
||||
|
||||
class StockService extends BaseService
|
||||
{
|
||||
const TRANSACTION_TYPE_CONSUME = 'consume';
|
||||
|
|
@ -33,7 +34,7 @@ class StockService extends BaseService
|
|||
if ($alreadyExistingEntry)
|
||||
{ // Update
|
||||
if ($alreadyExistingEntry->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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user