Started working on shopping list feature

This commit is contained in:
Bernd Bestel 2017-04-21 15:36:04 +02:00
parent 52e311d847
commit c6925ba4c3
10 changed files with 296 additions and 3 deletions

View File

@ -81,7 +81,7 @@ class GrocyDbMigrator
ON p.id = s.product_id ON p.id = s.product_id
WHERE p.min_stock_amount != 0 WHERE p.min_stock_amount != 0
GROUP BY p.id GROUP BY p.id
HAVING SUM(s.amount) < p.min_stock_amount;" HAVING IFNULL(SUM(s.amount), 0) < p.min_stock_amount;"
); );
self::ExecuteMigrationWhenNeeded($pdo, 8, " self::ExecuteMigrationWhenNeeded($pdo, 8, "
@ -92,6 +92,16 @@ class GrocyDbMigrator
GROUP BY product_id GROUP BY product_id
ORDER BY MIN(best_before_date) ASC;" ORDER BY MIN(best_before_date) ASC;"
); );
self::ExecuteMigrationWhenNeeded($pdo, 9, "
CREATE TABLE shopping_list (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
product_id INTEGER NOT NULL UNIQUE,
amount INTEGER NOT NULL DEFAULT 0,
amount_autoadded INTEGER NOT NULL DEFAULT 0,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)"
);
} }
private static function ExecuteMigrationWhenNeeded(PDO $pdo, int $migrationId, string $sql) private static function ExecuteMigrationWhenNeeded(PDO $pdo, int $migrationId, string $sql)

View File

@ -160,4 +160,32 @@ class GrocyLogicStock
return true; return true;
} }
public static function AddMissingProductsToShoppingList()
{
$db = Grocy::GetDbConnection();
$missingProducts = self::GetMissingProducts();
foreach ($missingProducts as $missingProduct)
{
$product = $db->products()->where('id', $missingProduct->id)->fetch();
$amount = ceil($missingProduct->amount_missing / $product->qu_factor_purchase_to_stock);
$alreadyExistingEntry = $db->shopping_list()->where('product_id', $missingProduct->id)->fetch();
if ($alreadyExistingEntry) //Update
{
$alreadyExistingEntry->update(array(
'amount_autoadded' => $amount
));
}
else //Insert
{
$shoppinglistRow = $db->shopping_list()->createRow(array(
'product_id' => $missingProduct->id,
'amount_autoadded' => $amount
));
$shoppinglistRow->save();
}
}
}
} }

View File

@ -25,6 +25,8 @@
<Compile Include="index.php" /> <Compile Include="index.php" />
<Compile Include="views\consumption.php" /> <Compile Include="views\consumption.php" />
<Compile Include="views\inventory.php" /> <Compile Include="views\inventory.php" />
<Compile Include="views\shoppinglistform.php" />
<Compile Include="views\shoppinglist.php" />
<Compile Include="views\purchase.php" /> <Compile Include="views\purchase.php" />
<Compile Include="views\quantityunitform.php" /> <Compile Include="views\quantityunitform.php" />
<Compile Include="views\locationform.php" /> <Compile Include="views\locationform.php" />
@ -51,6 +53,8 @@
<Content Include="views\consumption.js" /> <Content Include="views\consumption.js" />
<Content Include="views\dashboard.js" /> <Content Include="views\dashboard.js" />
<Content Include="views\inventory.js" /> <Content Include="views\inventory.js" />
<Content Include="views\shoppinglistform.js" />
<Content Include="views\shoppinglist.js" />
<Content Include="views\purchase.js" /> <Content Include="views\purchase.js" />
<Content Include="views\quantityunitform.js" /> <Content Include="views\quantityunitform.js" />
<Content Include="views\locationform.js" /> <Content Include="views\locationform.js" />

View File

@ -74,6 +74,18 @@ $app->get('/inventory', function(Request $request, Response $response) use($db)
]); ]);
}); });
$app->get('/shoppinglist', function(Request $request, Response $response) use($db)
{
return $this->renderer->render($response, '/layout.php', [
'title' => 'Shopping list',
'contentPage' => 'shoppinglist.php',
'listItems' => $db->shopping_list(),
'products' => $db->products(),
'quantityunits' => $db->quantity_units(),
'missingProducts' => GrocyLogicStock::GetMissingProducts()
]);
});
$app->get('/products', function(Request $request, Response $response) use($db) $app->get('/products', function(Request $request, Response $response) use($db)
{ {
return $this->renderer->render($response, '/layout.php', [ return $this->renderer->render($response, '/layout.php', [
@ -170,6 +182,29 @@ $app->get('/quantityunit/{quantityunitId}', function(Request $request, Response
} }
}); });
$app->get('/shoppinglist/{itemId}', function(Request $request, Response $response, $args) use($db)
{
if ($args['itemId'] == 'new')
{
return $this->renderer->render($response, '/layout.php', [
'title' => 'Add shopping list item',
'contentPage' => 'shoppinglistform.php',
'products' => $db->products(),
'mode' => 'create'
]);
}
else
{
return $this->renderer->render($response, '/layout.php', [
'title' => 'Edit shopping list item',
'contentPage' => 'shoppinglistform.php',
'listItem' => $db->shopping_list($args['itemId']),
'products' => $db->products(),
'mode' => 'edit'
]);
}
});
$app->group('/api', function() use($db) $app->group('/api', function() use($db)
{ {
$this->get('/get-objects/{entity}', function(Request $request, Response $response, $args) use($db) $this->get('/get-objects/{entity}', function(Request $request, Response $response, $args) use($db)
@ -260,6 +295,12 @@ $app->group('/api', function() use($db)
{ {
echo json_encode(GrocyLogicStock::GetCurrentStock()); echo json_encode(GrocyLogicStock::GetCurrentStock());
}); });
$this->get('/stock/add-missing-products-to-shoppinglist', function(Request $request, Response $response)
{
GrocyLogicStock::AddMissingProductsToShoppingList();
echo json_encode(array('success' => true));
});
})->add(function($request, $response, $next) })->add(function($request, $response, $next)
{ {
$response = $next($request, $response); $response = $next($request, $response);

View File

@ -6,7 +6,7 @@
<div class="form-group"> <div class="form-group">
<label for="product_id">Product&nbsp;&nbsp;<i class="fa fa-barcode"></i></label> <label for="product_id">Product&nbsp;&nbsp;<i class="fa fa-barcode"></i></label>
<select data-instockproduct="instockproduct" class="form-control combobox" id="product_id" name="product_id" required> <select class="form-control combobox" id="product_id" name="product_id" required>
<option value=""></option> <option value=""></option>
<?php foreach ($products as $product) : ?> <?php foreach ($products as $product) : ?>
<option data-additional-searchdata="<?php echo $product->barcode; ?>" value="<?php echo $product->id; ?>"><?php echo $product->name; ?></option> <option data-additional-searchdata="<?php echo $product->barcode; ?>" value="<?php echo $product->id; ?>"><?php echo $product->name; ?></option>

View File

@ -52,6 +52,9 @@
<li data-nav-for-page="inventory.php"> <li data-nav-for-page="inventory.php">
<a class="discrete-link" href="/inventory"><i class="fa fa-list fa-fw"></i>&nbsp;Inventory</a> <a class="discrete-link" href="/inventory"><i class="fa fa-list fa-fw"></i>&nbsp;Inventory</a>
</li> </li>
<li data-nav-for-page="shoppinglist.php">
<a class="discrete-link" href="/shoppinglist"><i class="fa fa-shopping-bag fa-fw"></i>&nbsp;Shopping list</a>
</li>
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
@ -88,6 +91,9 @@
<li data-nav-for-page="inventory.php"> <li data-nav-for-page="inventory.php">
<a class="discrete-link" href="/inventory"><i class="fa fa-list fa-fw"></i>&nbsp;Inventory</a> <a class="discrete-link" href="/inventory"><i class="fa fa-list fa-fw"></i>&nbsp;Inventory</a>
</li> </li>
<li data-nav-for-page="shoppinglist.php">
<a class="discrete-link" href="/shoppinglist"><i class="fa fa-shopping-bag fa-fw"></i>&nbsp;Shopping list</a>
</li>
</ul> </ul>
<ul class="nav nav-sidebar"> <ul class="nav nav-sidebar">

38
views/shoppinglist.js Normal file
View File

@ -0,0 +1,38 @@
$(document).on('click', '.shoppinglist-delete-button', function(e)
{
Grocy.FetchJson('/api/delete-object/shopping_list/' + $(e.target).attr('data-shoppinglist-id'),
function(result)
{
window.location.href = '/shoppinglist';
},
function(xhr)
{
console.error(xhr);
}
);
});
$(document).on('click', '#add-products-below-min-stock-amount', function(e)
{
Grocy.FetchJson('/api/stock/add-missing-products-to-shoppinglist',
function(result)
{
window.location.href = '/shoppinglist';
},
function(xhr)
{
console.error(xhr);
}
);
});
$(function()
{
$('#shoppinglist-table').DataTable({
'pageLength': 50,
'order': [[1, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 }
]
});
});

45
views/shoppinglist.php Normal file
View File

@ -0,0 +1,45 @@
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header">
Shopping List
<a class="btn btn-default" href="/shoppinglist/new" role="button">
<i class="fa fa-plus"></i>&nbsp;Add
</a>
<a id="add-products-below-min-stock-amount" class="btn btn-info" href="#" role="button">
<i class="fa fa-plus"></i>&nbsp;Add products that are below defined min. stock amount
</a>
</h1>
<div class="table-responsive">
<table id="shoppinglist-table" class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Product</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<?php foreach ($listItems as $listItem) : ?>
<tr>
<td class="fit-content">
<a class="btn btn-info" href="/shoppinglist/<?php echo $listItem->id; ?>" role="button">
<i class="fa fa-pencil"></i>
</a>
<a class="btn btn-danger shoppinglist-delete-button" href="#" role="button" data-shoppinglist-id="<?php echo $listItem->id; ?>">
<i class="fa fa-trash"></i>
</a>
</td>
<td>
<?php echo GrocyPhpHelper::FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->name; ?>
</td>
<td>
<?php echo $listItem->amount + $listItem->amount_autoadded . ' ' . GrocyPhpHelper::FindObjectInArrayByPropertyValue($quantityunits, 'id', GrocyPhpHelper::FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->qu_id_purchase)->name; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>

87
views/shoppinglistform.js Normal file
View File

@ -0,0 +1,87 @@
$('#save-shoppinglist-button').on('click', function(e)
{
e.preventDefault();
if (Grocy.EditMode === 'create')
{
Grocy.PostJson('/api/add-object/shopping_list', $('#shoppinglist-form').serializeJSON(),
function(result)
{
window.location.href = '/shoppinglist';
},
function(xhr)
{
console.error(xhr);
}
);
}
else
{
Grocy.PostJson('/api/edit-object/shopping_list/' + Grocy.EditObjectId, $('#shoppinglist-form').serializeJSON(),
function(result)
{
window.location.href = '/shoppinglist';
},
function(xhr)
{
console.error(xhr);
}
);
}
});
$(function()
{
$('.combobox').combobox({
appendId: '_text_input'
});
$('#product_id_text_input').on('change', function(e)
{
var input = $('#product_id_text_input').val().toString();
var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + input + "']").first();
if (possibleOptionElement.length > 0)
{
$('#product_id').val(possibleOptionElement.val());
$('#product_id').data('combobox').refresh();
$('#product_id').trigger('change');
}
});
if (Grocy.EditMode === 'create')
{
$('#product_id').val('');
$('#product_id_text_input').focus();
$('#product_id_text_input').val('');
$('#product_id_text_input').trigger('change');
}
else
{
$('#product_id').data('combobox').refresh();
$('#product_id').trigger('change');
}
$('#shoppinglist-form').validator();
$('#shoppinglist-form').validator('validate');
$('#amount').on('focus', function(e)
{
if ($('#product_id_text_input').val().length === 0)
{
$('#product_id_text_input').focus();
}
});
$('#shoppinglist-form input').keydown(function(event)
{
if (event.keyCode === 13) //Enter
{
if ($('#shoppinglist-form').validator('validate').has('.has-error').length !== 0) //There is at least one validation error
{
event.preventDefault();
return false;
}
}
});
});

View File

@ -0,0 +1,34 @@
<div class="col-sm-3 col-sm-offset-3 col-md-4 col-md-offset-2 main">
<h1 class="page-header"><?php echo $title; ?></h1>
<script>Grocy.EditMode = '<?php echo $mode; ?>';</script>
<?php if ($mode == 'edit') : ?>
<script>Grocy.EditObjectId = <?php echo $listItem->id; ?>;</script>
<?php endif; ?>
<form id="shoppinglist-form">
<div class="form-group">
<label for="product_id">Product&nbsp;&nbsp;<i class="fa fa-barcode"></i></label>
<select class="form-control combobox" id="product_id" name="product_id" value="<?php if ($mode == 'edit') echo $listItem->product_id; ?>" required>
<option value=""></option>
<?php foreach ($products as $product) : ?>
<option data-additional-searchdata="<?php echo $product->barcode; ?>" value="<?php echo $product->id; ?>"><?php echo $product->name; ?></option>
<?php endforeach; ?>
</select>
<div id="product-error" class="help-block with-errors"></div>
</div>
<div class="form-group">
<label for="amount">Amount</label>
<input type="number" class="form-control" id="amount" name="amount" value="<?php if ($mode == 'edit') echo $listItem->amount; else echo '1'; ?>" min="1" required>
<div class="help-block with-errors"></div>
</div>
<button id="save-shoppinglist-button" type="submit" class="btn btn-default">Save</button>
</form>
</div>