Allow optional partial units of products in stock (closes #123)

This commit is contained in:
Bernd Bestel 2019-01-26 14:17:02 +01:00
parent 9e139e2b73
commit dfc05e0bec
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
10 changed files with 158 additions and 11 deletions

View File

@ -981,7 +981,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"amount": { "amount": {
"type": "integer" "type": "double"
}, },
"best_before_date": { "best_before_date": {
"type": "string", "type": "string",
@ -1056,7 +1056,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"amount": { "amount": {
"type": "integer", "type": "double",
"description": "The amount to remove" "description": "The amount to remove"
}, },
"transaction_type": { "transaction_type": {
@ -1191,7 +1191,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"amount": { "amount": {
"type": "integer", "type": "double",
"description": "The amount to mark as opened" "description": "The amount to mark as opened"
}, },
"stock_entry_id": { "stock_entry_id": {
@ -1870,6 +1870,9 @@
"picture_file_name": { "picture_file_name": {
"type": "string" "type": "string"
}, },
"allow_partial_units_in_stock": {
"type": "boolean"
},
"row_created_timestamp": { "row_created_timestamp": {
"type": "string", "type": "string",
"format": "date-time" "format": "date-time"
@ -1925,7 +1928,7 @@
"type": "integer" "type": "integer"
}, },
"amount": { "amount": {
"type": "integer" "type": "double"
}, },
"best_before_date": { "best_before_date": {
"type": "string", "type": "string",
@ -2176,7 +2179,7 @@
"type": "string" "type": "string"
}, },
"amount": { "amount": {
"type": "integer", "type": "double",
"minimum": 0, "minimum": 0,
"default": 0, "default": 0,
"description": "The manual entered amount" "description": "The manual entered amount"
@ -2289,7 +2292,7 @@
"type": "integer" "type": "integer"
}, },
"amount": { "amount": {
"type": "integer" "type": "double"
}, },
"best_before_date": { "best_before_date": {
"type": "string", "type": "string",
@ -2345,7 +2348,7 @@
"type": "integer" "type": "integer"
}, },
"amount": { "amount": {
"type": "integer" "type": "double"
}, },
"best_before_date": { "best_before_date": {
"type": "string", "type": "string",

69
migrations/0049.sql Normal file
View File

@ -0,0 +1,69 @@
ALTER TABLE products
ADD allow_partial_units_in_stock TINYINT NOT NULL DEFAULT 0;
PRAGMA legacy_alter_table = ON;
ALTER TABLE stock RENAME TO stock_old;
CREATE TABLE stock (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
product_id INTEGER NOT NULL,
amount DECIMAL(15, 2) NOT NULL,
best_before_date DATE,
purchased_date DATE DEFAULT (datetime('now', 'localtime')),
stock_id TEXT NOT NULL,
price DECIMAL(15, 2),
open TINYINT NOT NULL DEFAULT 0 CHECK(open IN (0, 1)),
opened_date DATETIME,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);
INSERT INTO stock
(product_id, amount, best_before_date, purchased_date, stock_id, price, open, opened_date, row_created_timestamp)
SELECT product_id, amount, best_before_date, purchased_date, stock_id, price, open, opened_date, row_created_timestamp
FROM stock_old;
DROP TABLE stock_old;
ALTER TABLE stock_log RENAME TO stock_log_old;
CREATE TABLE stock_log (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
product_id INTEGER NOT NULL,
amount DECIMAL(15, 2) NOT NULL,
best_before_date DATE,
purchased_date DATE,
used_date DATE,
spoiled INTEGER NOT NULL DEFAULT 0,
stock_id TEXT NOT NULL,
transaction_type TEXT NOT NULL,
price DECIMAL(15, 2),
undone TINYINT NOT NULL DEFAULT 0 CHECK(undone IN (0, 1)),
undone_timestamp DATETIME,
opened_date DATETIME,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);
INSERT INTO stock_log
(product_id, amount, best_before_date, purchased_date, used_date, spoiled, stock_id, transaction_type, price, undone, undone_timestamp, opened_date, row_created_timestamp)
SELECT product_id, amount, best_before_date, purchased_date, used_date, spoiled, stock_id, transaction_type, price, undone, undone_timestamp, opened_date, row_created_timestamp
FROM stock_log_old;
DROP TABLE stock_log_old;
ALTER TABLE shopping_list RENAME TO shopping_list_old;
CREATE TABLE shopping_list (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
product_id INTEGER,
note TEXT,
amount DECIMAL(15, 2) NOT NULL DEFAULT 0,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);
INSERT INTO shopping_list
(product_id, amount, note, row_created_timestamp)
SELECT product_id, amount, note, row_created_timestamp
FROM shopping_list_old;
DROP TABLE shopping_list_old;

View File

@ -133,6 +133,19 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
$('#amount').attr('max', productDetails.stock_amount); $('#amount').attr('max', productDetails.stock_amount);
$('#amount_qu_unit').text(productDetails.quantity_unit_stock.name); $('#amount_qu_unit').text(productDetails.quantity_unit_stock.name);
if (productDetails.product.allow_partial_units_in_stock == 1)
{
$("#amount").attr("min", "0.01");
$("#amount").attr("step", "0.01");
$("#amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', 0.01.toLocaleString()));
}
else
{
$("#amount").attr("min", "1");
$("#amount").attr("step", "1");
$("#amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', '1'));
}
if ((productDetails.stock_amount || 0) === 0) if ((productDetails.stock_amount || 0) === 0)
{ {
Grocy.Components.ProductPicker.SetValue(''); Grocy.Components.ProductPicker.SetValue('');

View File

@ -83,6 +83,19 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
$('#new_amount').attr('not-equal', productDetails.stock_amount); $('#new_amount').attr('not-equal', productDetails.stock_amount);
$('#new_amount_qu_unit').text(productDetails.quantity_unit_stock.name); $('#new_amount_qu_unit').text(productDetails.quantity_unit_stock.name);
if (productDetails.product.allow_partial_units_in_stock == 1)
{
$("#new_amount").attr("min", "0.01");
$("#new_amount").attr("step", "0.01");
$("#new_amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', 0.01.toLocaleString()));
}
else
{
$("#new_amount").attr("min", "1");
$("#new_amount").attr("step", "1");
$("#new_amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', '1'));
}
$('#new_amount').focus(); $('#new_amount').focus();
}, },
function(xhr) function(xhr)

View File

@ -9,7 +9,7 @@
redirectDestination = returnTo + '?createdproduct=' + encodeURIComponent($('#name').val()); redirectDestination = returnTo + '?createdproduct=' + encodeURIComponent($('#name').val());
} }
var jsonData = $('#product-form').serializeJSON(); var jsonData = $('#product-form').serializeJSON({ checkboxUncheckedValue: "0" });
Grocy.FrontendHelpers.BeginUiBusy("product-form"); Grocy.FrontendHelpers.BeginUiBusy("product-form");
if ($("#product-picture")[0].files.length > 0) if ($("#product-picture")[0].files.length > 0)

View File

@ -100,6 +100,19 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name); $('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name);
$('#price').val(productDetails.last_price); $('#price').val(productDetails.last_price);
if (productDetails.product.allow_partial_units_in_stock == 1)
{
$("#amount").attr("min", "0.01");
$("#amount").attr("step", "0.01");
$("#amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', 0.01.toLocaleString()));
}
else
{
$("#amount").attr("min", "1");
$("#amount").attr("step", "1");
$("#amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', '1'));
}
if (productDetails.product.default_best_before_days.toString() !== '0') if (productDetails.product.default_best_before_days.toString() !== '0')
{ {
if (productDetails.product.default_best_before_days == -1) if (productDetails.product.default_best_before_days == -1)

View File

@ -52,6 +52,20 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{ {
$("#qu_id").val(productDetails.quantity_unit_stock.id); $("#qu_id").val(productDetails.quantity_unit_stock.id);
} }
if (productDetails.product.allow_partial_units_in_stock == 1)
{
$("#amount").attr("min", "0.01");
$("#amount").attr("step", "0.01");
$("#amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', 0.01.toLocaleString()));
}
else
{
$("#amount").attr("min", "1");
$("#amount").attr("step", "1");
$("#amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', '1'));
}
$('#amount').focus(); $('#amount').focus();
Grocy.FrontendHelpers.ValidateForm('recipe-pos-form'); Grocy.FrontendHelpers.ValidateForm('recipe-pos-form');
}, },

View File

@ -47,6 +47,20 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
function (productDetails) function (productDetails)
{ {
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name); $('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name);
if (productDetails.product.allow_partial_units_in_stock == 1)
{
$("#amount").attr("min", "0.01");
$("#amount").attr("step", "0.01");
$("#amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', 0.01.toLocaleString()));
}
else
{
$("#amount").attr("min", "1");
$("#amount").attr("step", "1");
$("#amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', '1'));
}
$('#amount').focus(); $('#amount').focus();
Grocy.FrontendHelpers.ValidateForm('shoppinglist-form'); Grocy.FrontendHelpers.ValidateForm('shoppinglist-form');
}, },

View File

@ -109,7 +109,7 @@ class StockService extends BaseService
} }
} }
public function AddProduct(int $productId, int $amount, string $bestBeforeDate, $transactionType, $purchasedDate, $price) public function AddProduct(int $productId, float $amount, string $bestBeforeDate, $transactionType, $purchasedDate, $price)
{ {
if (!$this->ProductExists($productId)) if (!$this->ProductExists($productId))
{ {
@ -151,7 +151,7 @@ class StockService extends BaseService
} }
} }
public function ConsumeProduct(int $productId, int $amount, bool $spoiled, $transactionType, $specificStockEntryId = 'default') public function ConsumeProduct(int $productId, float $amount, bool $spoiled, $transactionType, $specificStockEntryId = 'default')
{ {
if (!$this->ProductExists($productId)) if (!$this->ProductExists($productId))
{ {
@ -258,7 +258,7 @@ class StockService extends BaseService
return $this->Database->lastInsertId(); return $this->Database->lastInsertId();
} }
public function OpenProduct(int $productId, int $amount, $specificStockEntryId = 'default') public function OpenProduct(int $productId, float $amount, $specificStockEntryId = 'default')
{ {
if (!$this->ProductExists($productId)) if (!$this->ProductExists($productId))
{ {

View File

@ -126,6 +126,14 @@
'additionalHtmlElements' => '<p id="qu-conversion-info" class="form-text text-muted small d-none"></p>' 'additionalHtmlElements' => '<p id="qu-conversion-info" class="form-text text-muted small d-none"></p>'
)) ))
<div class="form-group">
<div class="form-check">
<input type="hidden" name="allow_partial_units_in_stock" value="0">
<input @if($mode == 'edit' && $product->allow_partial_units_in_stock == 1) checked @endif class="form-check-input" type="checkbox" id="allow_partial_units_in_stock" name="allow_partial_units_in_stock" value="1">
<label class="form-check-label" for="allow_partial_units_in_stock">{{ $L('Allow partial units in stock') }}</label>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="product-picture">{{ $L('Product picture') }}</label> <label for="product-picture">{{ $L('Product picture') }}</label>
<div class="custom-file"> <div class="custom-file">