mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 09:39:57 +00:00
Started working on shopping list feature
This commit is contained in:
parent
52e311d847
commit
c6925ba4c3
@ -81,7 +81,7 @@ class GrocyDbMigrator
|
||||
ON p.id = s.product_id
|
||||
WHERE p.min_stock_amount != 0
|
||||
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, "
|
||||
@ -92,6 +92,16 @@ class GrocyDbMigrator
|
||||
GROUP BY product_id
|
||||
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)
|
||||
|
@ -160,4 +160,32 @@ class GrocyLogicStock
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@
|
||||
<Compile Include="index.php" />
|
||||
<Compile Include="views\consumption.php" />
|
||||
<Compile Include="views\inventory.php" />
|
||||
<Compile Include="views\shoppinglistform.php" />
|
||||
<Compile Include="views\shoppinglist.php" />
|
||||
<Compile Include="views\purchase.php" />
|
||||
<Compile Include="views\quantityunitform.php" />
|
||||
<Compile Include="views\locationform.php" />
|
||||
@ -51,6 +53,8 @@
|
||||
<Content Include="views\consumption.js" />
|
||||
<Content Include="views\dashboard.js" />
|
||||
<Content Include="views\inventory.js" />
|
||||
<Content Include="views\shoppinglistform.js" />
|
||||
<Content Include="views\shoppinglist.js" />
|
||||
<Content Include="views\purchase.js" />
|
||||
<Content Include="views\quantityunitform.js" />
|
||||
<Content Include="views\locationform.js" />
|
||||
|
41
index.php
41
index.php
@ -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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
$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());
|
||||
});
|
||||
|
||||
$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)
|
||||
{
|
||||
$response = $next($request, $response);
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label for="product_id">Product <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>
|
||||
<?php foreach ($products as $product) : ?>
|
||||
<option data-additional-searchdata="<?php echo $product->barcode; ?>" value="<?php echo $product->id; ?>"><?php echo $product->name; ?></option>
|
||||
|
@ -52,6 +52,9 @@
|
||||
<li data-nav-for-page="inventory.php">
|
||||
<a class="discrete-link" href="/inventory"><i class="fa fa-list fa-fw"></i> Inventory</a>
|
||||
</li>
|
||||
<li data-nav-for-page="shoppinglist.php">
|
||||
<a class="discrete-link" href="/shoppinglist"><i class="fa fa-shopping-bag fa-fw"></i> Shopping list</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
@ -88,6 +91,9 @@
|
||||
<li data-nav-for-page="inventory.php">
|
||||
<a class="discrete-link" href="/inventory"><i class="fa fa-list fa-fw"></i> Inventory</a>
|
||||
</li>
|
||||
<li data-nav-for-page="shoppinglist.php">
|
||||
<a class="discrete-link" href="/shoppinglist"><i class="fa fa-shopping-bag fa-fw"></i> Shopping list</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav nav-sidebar">
|
||||
|
38
views/shoppinglist.js
Normal file
38
views/shoppinglist.js
Normal 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
45
views/shoppinglist.php
Normal 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> Add
|
||||
</a>
|
||||
<a id="add-products-below-min-stock-amount" class="btn btn-info" href="#" role="button">
|
||||
<i class="fa fa-plus"></i> 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
87
views/shoppinglistform.js
Normal 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;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
34
views/shoppinglistform.php
Normal file
34
views/shoppinglistform.php
Normal 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 <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>
|
Loading…
x
Reference in New Issue
Block a user