mirror of
https://github.com/grocy/grocy.git
synced 2026-04-07 21:26:16 +02:00
Feature: Stock Purchase Metrics
This commit is contained in:
parent
91700e7dae
commit
24b178fe75
|
|
@ -515,6 +515,53 @@ class StockController extends BaseController
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function StockMetricsPurchases(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||||
|
{
|
||||||
|
if (isset($request->getQueryParams()['start_date']) AND isset($request->getQueryParams()['end_date']))
|
||||||
|
{
|
||||||
|
$start_date = $request->getQueryParams()['start_date'];
|
||||||
|
$end_date = $request->getQueryParams()['end_date'];
|
||||||
|
$where = "purchased_date >= '$start_date' AND purchased_date <= '$end_date'";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Default this month
|
||||||
|
$where = "purchased_date >= DATE(DATE('now', 'localtime'), 'start of month')";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (isset($request->getQueryParams()['byGroup']))
|
||||||
|
{
|
||||||
|
$sql = "
|
||||||
|
SELECT product_group_id as id, product_group as name, sum(quantity * price) as total
|
||||||
|
FROM product_purchase_history
|
||||||
|
where $where
|
||||||
|
GROUP BY product_group
|
||||||
|
ORDER BY product_group
|
||||||
|
";
|
||||||
|
} else {
|
||||||
|
if (isset($request->getQueryParams()['product_group']) AND $request->getQueryParams()['product_group'] != 'all')
|
||||||
|
{
|
||||||
|
$where = $where . ' AND product_group_id = ' . $request->getQueryParams()['product_group'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT product_id as id, product_name as name, product_group_id as group_id, product_group as group_name, sum(quantity * price) as total
|
||||||
|
FROM product_purchase_history
|
||||||
|
WHERE $where
|
||||||
|
GROUP BY product_name
|
||||||
|
ORDER BY product_name
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->renderPage($response, 'stockmetricspurchases', [
|
||||||
|
'metrics' => $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ),
|
||||||
|
'productGroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'),
|
||||||
|
'selectedGroup' => isset($request->getQueryParams()['product_group']) ? $request->getQueryParams()['product_group'] : null,
|
||||||
|
'byGroup' => isset($request->getQueryParams()['byGroup']) ? $request->getQueryParams()['byGroup'] : null
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function Transfer(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
public function Transfer(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||||
{
|
{
|
||||||
return $this->renderPage($response, 'transfer', [
|
return $this->renderPage($response, 'transfer', [
|
||||||
|
|
|
||||||
|
|
@ -2371,3 +2371,18 @@ msgstr ""
|
||||||
|
|
||||||
msgid "Quick open amount"
|
msgid "Quick open amount"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Metrics"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Stock Metrics: Purchases"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "by Product"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "by Group"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Total"
|
||||||
|
msgstr ""
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
"bootstrap": "^4.5.2",
|
"bootstrap": "^4.5.2",
|
||||||
"bootstrap-select": "^1.13.18",
|
"bootstrap-select": "^1.13.18",
|
||||||
"bwip-js": "^3.0.1",
|
"bwip-js": "^3.0.1",
|
||||||
|
"canvasjs": "^1.8.3",
|
||||||
"chart.js": "^2.8.0",
|
"chart.js": "^2.8.0",
|
||||||
"datatables.net": "^1.10.22",
|
"datatables.net": "^1.10.22",
|
||||||
"datatables.net-bs4": "^1.10.22",
|
"datatables.net-bs4": "^1.10.22",
|
||||||
|
|
@ -21,6 +22,7 @@
|
||||||
"datatables.net-rowgroup-bs4": "^1.1.2",
|
"datatables.net-rowgroup-bs4": "^1.1.2",
|
||||||
"datatables.net-select": "^1.3.1",
|
"datatables.net-select": "^1.3.1",
|
||||||
"datatables.net-select-bs4": "^1.3.1",
|
"datatables.net-select-bs4": "^1.3.1",
|
||||||
|
"daterangepicker": "dangrossman/daterangepicker",
|
||||||
"fullcalendar": "^3.10.1",
|
"fullcalendar": "^3.10.1",
|
||||||
"gettext-translator": "2.1.0",
|
"gettext-translator": "2.1.0",
|
||||||
"jquery": "^3.6.0",
|
"jquery": "^3.6.0",
|
||||||
|
|
|
||||||
96
public/viewjs/metrics.js
Normal file
96
public/viewjs/metrics.js
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Metrics Javascript
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Charting */
|
||||||
|
var dataPoints = [];
|
||||||
|
$("#metrics-table tbody tr").each(function () {
|
||||||
|
var self = $(this);
|
||||||
|
var label = self.find("td:eq(0)").attr('data-chart-label');
|
||||||
|
var value = Number(self.find("td:eq(1)").attr('data-chart-value'));
|
||||||
|
var dataPoint = { label: label, y: parseFloat((Math.round(value * 100) / 100).toFixed(2))};
|
||||||
|
dataPoints.push(dataPoint);
|
||||||
|
});
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
exportEnabled: true,
|
||||||
|
legend:{
|
||||||
|
horizontalAlign: "center",
|
||||||
|
verticalAlign: "bottom"
|
||||||
|
},
|
||||||
|
data: [{
|
||||||
|
type: "pie",
|
||||||
|
showInLegend: true,
|
||||||
|
toolTipContent: "<b>{label}</b>: ${y} (#percent%)",
|
||||||
|
indexLabel: "{label}",
|
||||||
|
legendText: "{label} (#percent%)",
|
||||||
|
indexLabelPlacement: "outside",
|
||||||
|
valueFormatSTringt: "#,##0.##",
|
||||||
|
dataPoints: dataPoints
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
// needed for recursionCount error
|
||||||
|
recursionCount=0;
|
||||||
|
$("#metrics-chart").CanvasJSChart(options);
|
||||||
|
|
||||||
|
/* DataTables */
|
||||||
|
var metricsTable = $('#metrics-table').DataTable({
|
||||||
|
"columnDefs": [
|
||||||
|
{ "type": "num", "targets": 1 }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
$('#metrics-table tbody').removeClass("d-none");
|
||||||
|
metricsTable.columns.adjust().draw();
|
||||||
|
|
||||||
|
/* DateRangePicker */
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
var start_date = moment().startOf("month").format('MM/DD/YYYY');
|
||||||
|
var end_date = moment().endOf("month").format('MM/DD/YYYY');
|
||||||
|
|
||||||
|
if (urlParams.get('start_date')) start_date = moment(urlParams.get('start_date')) ;
|
||||||
|
if (urlParams.get('end_date')) end_date = moment(urlParams.get('end_date'));
|
||||||
|
|
||||||
|
$('#daterange-filter').daterangepicker({
|
||||||
|
showDropdowns: true,
|
||||||
|
startDate: start_date,
|
||||||
|
endDate: end_date,
|
||||||
|
locale: {
|
||||||
|
"format": 'MM/DD/YYYY',
|
||||||
|
"firstDay": 1
|
||||||
|
},
|
||||||
|
ranges: {
|
||||||
|
'Today': [moment(), moment()],
|
||||||
|
'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
|
||||||
|
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
|
||||||
|
'Last 14 Days': [moment().subtract(13, 'days'), moment()],
|
||||||
|
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
|
||||||
|
'This Month': [moment().startOf('month'), moment().endOf('month')],
|
||||||
|
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')],
|
||||||
|
'This Year': [moment().startOf('year'), moment().endOf('year')],
|
||||||
|
'Last Year': [moment().subtract(1, 'year').startOf('year'), moment().subtract(1, 'year').endOf('year')]
|
||||||
|
}}, function(start, end, label) {
|
||||||
|
UpdateUriParam("start_date", start.format('YYYY-MM-DD'));
|
||||||
|
UpdateUriParam("end_date", end.format('YYYY-MM-DD'))
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#daterange-filter').on('cancel.daterangepicker', function(ev, picker)
|
||||||
|
{
|
||||||
|
$(this).val(start_date + ' - ' + end_date);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#clear-filter-button").on("click", function()
|
||||||
|
{
|
||||||
|
RemoveUriParam("start_date");
|
||||||
|
RemoveUriParam("end_date");
|
||||||
|
RemoveUriParam("product_group");
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#product-group-filter").on("change", function()
|
||||||
|
{
|
||||||
|
UpdateUriParam("product_group", $(this).val());
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
|
@ -56,6 +56,7 @@ $app->group('', function (RouteCollectorProxy $group) {
|
||||||
$group->get('/locations', '\Grocy\Controllers\StockController:LocationsList');
|
$group->get('/locations', '\Grocy\Controllers\StockController:LocationsList');
|
||||||
$group->get('/location/{locationId}', '\Grocy\Controllers\StockController:LocationEditForm');
|
$group->get('/location/{locationId}', '\Grocy\Controllers\StockController:LocationEditForm');
|
||||||
$group->get('/stockjournal', '\Grocy\Controllers\StockController:Journal');
|
$group->get('/stockjournal', '\Grocy\Controllers\StockController:Journal');
|
||||||
|
$group->get('/stockmetricspurchases', '\Grocy\Controllers\StockController:StockMetricsPurchases');
|
||||||
$group->get('/locationcontentsheet', '\Grocy\Controllers\StockController:LocationContentSheet');
|
$group->get('/locationcontentsheet', '\Grocy\Controllers\StockController:LocationContentSheet');
|
||||||
$group->get('/quantityunitpluraltesting', '\Grocy\Controllers\StockController:QuantityUnitPluralFormTesting');
|
$group->get('/quantityunitpluraltesting', '\Grocy\Controllers\StockController:QuantityUnitPluralFormTesting');
|
||||||
$group->get('/stockjournal/summary', '\Grocy\Controllers\StockController:JournalSummary');
|
$group->get('/stockjournal/summary', '\Grocy\Controllers\StockController:JournalSummary');
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,8 @@ class DemoDataGeneratorService extends BaseService
|
||||||
INSERT INTO quantity_units (id, name, name_plural) VALUES (12, '{$this->__n_sql(1, 'Slice', 'Slices')}', '{$this->__n_sql(2, 'Slice', 'Slices')}'); --12
|
INSERT INTO quantity_units (id, name, name_plural) VALUES (12, '{$this->__n_sql(1, 'Slice', 'Slices')}', '{$this->__n_sql(2, 'Slice', 'Slices')}'); --12
|
||||||
DELETE FROM quantity_units WHERE name = '{$this->__t_sql('Kilogram')}';
|
DELETE FROM quantity_units WHERE name = '{$this->__t_sql('Kilogram')}';
|
||||||
INSERT INTO quantity_units (id, name, name_plural) VALUES (13, '{$this->__n_sql(1, 'Kilogram', 'Kilograms')}', '{$this->__n_sql(2, 'Kilogram', 'Kilograms')}'); --13
|
INSERT INTO quantity_units (id, name, name_plural) VALUES (13, '{$this->__n_sql(1, 'Kilogram', 'Kilograms')}', '{$this->__n_sql(2, 'Kilogram', 'Kilograms')}'); --13
|
||||||
|
DELETE FROM quantity_units WHERE name = '{$this->__t_sql('pint')}';
|
||||||
|
INSERT INTO quantity_units (id, name, name_plural) VALUES (14, '{$this->__n_sql(1, 'Pint', 'Pints')}', '{$this->__n_sql(2, 'Pint', 'Pint')}'); --14
|
||||||
|
|
||||||
INSERT INTO product_groups(name) VALUES ('01 {$this->__t_sql('Sweets')}'); --1
|
INSERT INTO product_groups(name) VALUES ('01 {$this->__t_sql('Sweets')}'); --1
|
||||||
INSERT INTO product_groups(name) VALUES ('02 {$this->__t_sql('Bakery products')}'); --2
|
INSERT INTO product_groups(name) VALUES ('02 {$this->__t_sql('Bakery products')}'); --2
|
||||||
|
|
@ -73,13 +75,14 @@ class DemoDataGeneratorService extends BaseService
|
||||||
INSERT INTO product_groups(name) VALUES ('04 {$this->__t_sql('Butchery products')}'); --4
|
INSERT INTO product_groups(name) VALUES ('04 {$this->__t_sql('Butchery products')}'); --4
|
||||||
INSERT INTO product_groups(name) VALUES ('05 {$this->__t_sql('Vegetables/Fruits')}'); --5
|
INSERT INTO product_groups(name) VALUES ('05 {$this->__t_sql('Vegetables/Fruits')}'); --5
|
||||||
INSERT INTO product_groups(name) VALUES ('06 {$this->__t_sql('Refrigerated products')}'); --6
|
INSERT INTO product_groups(name) VALUES ('06 {$this->__t_sql('Refrigerated products')}'); --6
|
||||||
|
INSERT INTO product_groups(name) VALUES ('07 {$this->__t_sql('Beverages')}'); --7'
|
||||||
|
|
||||||
DELETE FROM sqlite_sequence WHERE name = 'products'; --Just to keep IDs in order as mentioned here...
|
DELETE FROM sqlite_sequence WHERE name = 'products'; --Just to keep IDs in order as mentioned here...
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, min_stock_amount, product_group_id, picture_file_name) VALUES ('{$this->__t_sql('Cookies')}', 4, 3, 3, 8, 1, 'cookies.jpg'); --1
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, min_stock_amount, product_group_id, picture_file_name) VALUES ('{$this->__t_sql('Cookies')}', 4, 3, 3, 8, 1, 'cookies.jpg'); --1
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, min_stock_amount, product_group_id, cumulate_min_stock_amount_of_sub_products) VALUES ('{$this->__t_sql('Chocolate')}', 4, 3, 3, 8, 1, 1); --2
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, min_stock_amount, product_group_id, cumulate_min_stock_amount_of_sub_products) VALUES ('{$this->__t_sql('Chocolate')}', 4, 3, 3, 8, 1, 1); --2
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, min_stock_amount, product_group_id, picture_file_name) VALUES ('{$this->__t_sql('Gummy bears')}', 4, 3, 3, 8, 1, 'gummybears.jpg'); --3
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, min_stock_amount, product_group_id, picture_file_name) VALUES ('{$this->__t_sql('Gummy bears')}', 4, 3, 3, 8, 1, 'gummybears.jpg'); --3
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, min_stock_amount, product_group_id) VALUES ('{$this->__t_sql('Crisps')}', 4, 3, 3, 10, 1); --4
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, min_stock_amount, product_group_id) VALUES ('{$this->__t_sql('Crisps')}', 4, 3, 3, 10, 1); --4
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id) VALUES ('{$this->__t_sql('Eggs')}', 2, 3, 2, 5); --5
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id) VALUES ('{$this->__t_sql('Eggs')}', 2, 3, 2, 6); --5
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id) VALUES ('{$this->__t_sql('Noodles')}', 3, 3, 3, 6); --6
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id) VALUES ('{$this->__t_sql('Noodles')}', 3, 3, 3, 6); --6
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id) VALUES ('{$this->__t_sql('Pickles')}', 5, 4, 4, 3); --7
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id) VALUES ('{$this->__t_sql('Pickles')}', 5, 4, 4, 3); --7
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id) VALUES ('{$this->__t_sql('Gulash soup')}', 5, 5, 5, 3); --8
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id) VALUES ('{$this->__t_sql('Gulash soup')}', 5, 5, 5, 3); --8
|
||||||
|
|
@ -101,6 +104,10 @@ class DemoDataGeneratorService extends BaseService
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id, parent_product_id) VALUES ('{$this->__t_sql('Milk Chocolate')}', 4, 3, 3, 1, 2); --24
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id, parent_product_id) VALUES ('{$this->__t_sql('Milk Chocolate')}', 4, 3, 3, 1, 2); --24
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id, parent_product_id) VALUES ('{$this->__t_sql('Dark Chocolate')}', 4, 3, 3, 1, 2); --25
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id, parent_product_id) VALUES ('{$this->__t_sql('Dark Chocolate')}', 4, 3, 3, 1, 2); --25
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id) VALUES ('{$this->__t_sql('Waffle rolls')}', 4, 3, 3, 1); --26
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id) VALUES ('{$this->__t_sql('Waffle rolls')}', 4, 3, 3, 1); --26
|
||||||
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id) VALUES ('{$this->__t_sql('Ice Cream')}', 6, 14, 14, 1); --27
|
||||||
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id) VALUES ('{$this->__t_sql('Soda')}', 2, 6, 6, 7); --28
|
||||||
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, product_group_id) VALUES ('{$this->__t_sql('Beer')}', 2, 6, 6, 7); --29
|
||||||
|
|
||||||
UPDATE products SET calories = 123 WHERE IFNULL(calories, 0) = 0;
|
UPDATE products SET calories = 123 WHERE IFNULL(calories, 0) = 0;
|
||||||
|
|
||||||
INSERT INTO product_barcodes (product_id, barcode) VALUES (8, '22111968');
|
INSERT INTO product_barcodes (product_id, barcode) VALUES (8, '22111968');
|
||||||
|
|
@ -289,6 +296,16 @@ class DemoDataGeneratorService extends BaseService
|
||||||
$stockService->AddProduct(24, 2, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
$stockService->AddProduct(24, 2, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||||
$stockService->AddProduct(25, 2, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
$stockService->AddProduct(25, 2, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||||
$stockService->AddProduct(2, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
$stockService->AddProduct(2, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||||
|
$stockService->AddProduct(27, 1, date('Y-m-d', strtotime('+30 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('now')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||||
|
$stockService->AddProduct(23, 1, date('Y-m-d', strtotime('+60 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('now')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||||
|
$stockService->AddProduct(27, 1, date('Y-m-d', strtotime('+30 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-2 weeks')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||||
|
$stockService->AddProduct(27, 1, date('Y-m-d', strtotime('+30 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-3 weeks')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||||
|
$stockService->AddProduct(28, 12, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-1 weeks')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||||
|
$stockService->AddProduct(29, 12, date('Y-m-d', strtotime('+365 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-2 weeks')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||||
|
$stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+1 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-1 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||||
|
$stockService->AddProduct(1, 12, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-1 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||||
|
$stockService->AddProduct(2, 12, date('Y-m-d', strtotime('+365 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-1 days')), $this->RandomPrice(), null, $this->NextSupermarketId(), $stockTransactionId);
|
||||||
|
|
||||||
$stockService->AddMissingProductsToShoppingList();
|
$stockService->AddMissingProductsToShoppingList();
|
||||||
$stockService->OpenProduct(3, 1);
|
$stockService->OpenProduct(3, 1);
|
||||||
$stockService->OpenProduct(6, 1);
|
$stockService->OpenProduct(6, 1);
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,8 @@
|
||||||
rel="stylesheet">
|
rel="stylesheet">
|
||||||
<link href="{{ $U('/node_modules/bootstrap-select/dist/css/bootstrap-select.min.css?v=', true) }}{{ $version }}"
|
<link href="{{ $U('/node_modules/bootstrap-select/dist/css/bootstrap-select.min.css?v=', true) }}{{ $version }}"
|
||||||
rel="stylesheet">
|
rel="stylesheet">
|
||||||
|
<link href="{{ $U('/node_modules/daterangepicker/daterangepicker.css?v=', true) }}{{ $version }}"
|
||||||
|
rel="stylesheet">
|
||||||
<link href="{{ $U('/node_modules/@fontsource/noto-sans/latin.css?v=', true) }}{{ $version }}"
|
<link href="{{ $U('/node_modules/@fontsource/noto-sans/latin.css?v=', true) }}{{ $version }}"
|
||||||
rel="stylesheet">
|
rel="stylesheet">
|
||||||
<link href="{{ $U('/css/grocy.css?v=', true) }}{{ $version }}"
|
<link href="{{ $U('/css/grocy.css?v=', true) }}{{ $version }}"
|
||||||
|
|
@ -734,6 +736,8 @@
|
||||||
@if(!empty($__t('bootstrap-select_locale') && $__t('bootstrap-select_locale') != 'x'))<script src="{{ $U('/node_modules', true) }}/bootstrap-select/dist/js/i18n/defaults-{{ $__t('bootstrap-select_locale') }}.js?v={{ $version }}"></script>@endif
|
@if(!empty($__t('bootstrap-select_locale') && $__t('bootstrap-select_locale') != 'x'))<script src="{{ $U('/node_modules', true) }}/bootstrap-select/dist/js/i18n/defaults-{{ $__t('bootstrap-select_locale') }}.js?v={{ $version }}"></script>@endif
|
||||||
<script src="{{ $U('/node_modules/jquery-lazy/jquery.lazy.min.js?v=', true) }}{{ $version }}"></script>
|
<script src="{{ $U('/node_modules/jquery-lazy/jquery.lazy.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
<script src="{{ $U('/node_modules/nosleep.js/dist/NoSleep.min.js?v=', true) }}{{ $version }}"></script>
|
<script src="{{ $U('/node_modules/nosleep.js/dist/NoSleep.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/node_modules/daterangepicker/daterangepicker.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/node_modules/canvasjs/dist/jquery.canvasjs.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
|
||||||
<script src="{{ $U('/js/extensions.js?v=', true) }}{{ $version }}"></script>
|
<script src="{{ $U('/js/extensions.js?v=', true) }}{{ $version }}"></script>
|
||||||
<script src="{{ $U('/js/grocy.js?v=', true) }}{{ $version }}"></script>
|
<script src="{{ $U('/js/grocy.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
|
|
||||||
121
views/stockmetricspurchases.blade.php
Normal file
121
views/stockmetricspurchases.blade.php
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
@extends('layout.default')
|
||||||
|
|
||||||
|
@section('title', $__t('Stock Metrics: Purchases'))
|
||||||
|
@section('activeNav', 'stockmetricspurchases')
|
||||||
|
@section('viewJsName', 'metrics')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="title-related-links">
|
||||||
|
<h2 class="title mr-2 order-0">
|
||||||
|
@yield('title')
|
||||||
|
</h2>
|
||||||
|
<h2 class="mb-0 mr-auto order-3 order-md-1 width-xs-sm-100">
|
||||||
|
<span id="info-current-stock"
|
||||||
|
class="text-muted small"></span>
|
||||||
|
</h2>
|
||||||
|
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3"
|
||||||
|
type="button"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-target="#related-links">
|
||||||
|
<i class="fa-solid fa-ellipsis-v"></i>
|
||||||
|
</button>
|
||||||
|
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
|
||||||
|
id="related-links">
|
||||||
|
<a class="btn btn-outline-dark responsive-button @if(!$byGroup) active @endif m-1 mt-md-0 mb-md-0 float-right"
|
||||||
|
href="{{ $U('/stockmetricspurchases') }}">
|
||||||
|
{{ $__t('by Product') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
|
||||||
|
id="related-links">
|
||||||
|
<a class="btn btn-outline-dark responsive-button @if($byGroup) active @endif m-1 mt-md-0 mb-md-0 float-right"
|
||||||
|
href="{{ $U('/stockmetricspurchases?byGroup=true') }}">
|
||||||
|
{{ $__t('by Group') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-2">
|
||||||
|
|
||||||
|
<div class="row collapse d-md-flex"
|
||||||
|
id="table-filter-row">
|
||||||
|
<div class="col-sm-12 col-md-6 col-xl-3">
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa-solid fa-clock"></i> {{ $__t('Date range') }}</span>
|
||||||
|
<input type="text" name="date-filter" id="daterange-filter" class="custom-control custom-select" value="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if(!$byGroup)
|
||||||
|
<div class="col-sm-12 col-md-6 col-xl-3">
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa-solid fa-filter"></i> {{ $__t('Product group') }}</span>
|
||||||
|
</div>
|
||||||
|
<select class="custom-control custom-select"
|
||||||
|
id="product-group-filter">
|
||||||
|
<option value="all">{{ $__t('All') }}</option>
|
||||||
|
@foreach($productGroups as $productGroup)
|
||||||
|
<option @if($selectedGroup == $productGroup->id)
|
||||||
|
selected="selected"
|
||||||
|
@endif
|
||||||
|
value="{{ $productGroup->id }}">{{ $productGroup->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="col">
|
||||||
|
<div class="float-right mt-1">
|
||||||
|
<button id="clear-filter-button"
|
||||||
|
class="btn btn-sm btn-outline-info"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
title="{{ $__t('Clear filter') }}">
|
||||||
|
<i class="fa-solid fa-filter-circle-xmark"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col-sm-12 col-md-12 col-xl-12">
|
||||||
|
<div id="metrics-chart" style="height: 400px;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12 col-md-12 col-xl-12">
|
||||||
|
<table id="metrics-table"
|
||||||
|
class="table table-sm table-striped nowrap w-100">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ $__t('Name') }}</th>
|
||||||
|
<th>{{ $__t('Total') }}</th>
|
||||||
|
@if(!$byGroup)
|
||||||
|
<th>{{ $__t('Product group') }}</th>
|
||||||
|
@endif
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="d-none">
|
||||||
|
@foreach($metrics as $metric)
|
||||||
|
<tr>
|
||||||
|
<td data-chart-label="{{ $metric->name }}">
|
||||||
|
{{ $metric->name }}
|
||||||
|
</td>
|
||||||
|
<td data-chart-value="{{ $metric->total }}" data-order="{{ $metric->total }}">
|
||||||
|
<span class="locale-number locale-number-currency">{{ $metric->total }}</span>
|
||||||
|
</td>
|
||||||
|
@if(!$byGroup)
|
||||||
|
<td>
|
||||||
|
{{ $metric->group_name }}
|
||||||
|
</td>
|
||||||
|
@endif
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@stop
|
||||||
|
|
@ -40,6 +40,10 @@
|
||||||
href="{{ $U('/stockentries') }}">
|
href="{{ $U('/stockentries') }}">
|
||||||
{{ $__t('Stock entries') }}
|
{{ $__t('Stock entries') }}
|
||||||
</a>
|
</a>
|
||||||
|
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
|
||||||
|
href="{{ $U('/stockmetricspurchases') }}">
|
||||||
|
{{ $__t('Metrics') }}
|
||||||
|
</a>
|
||||||
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
|
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
|
||||||
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
|
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
|
||||||
href="{{ $U('/locationcontentsheet') }}">
|
href="{{ $U('/locationcontentsheet') }}">
|
||||||
|
|
|
||||||
19
yarn.lock
19
yarn.lock
|
|
@ -112,6 +112,11 @@ bwip-js@^3.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/bwip-js/-/bwip-js-3.2.2.tgz#bef1b5b566519754acd251dbe323a2fe1dc06c9a"
|
resolved "https://registry.yarnpkg.com/bwip-js/-/bwip-js-3.2.2.tgz#bef1b5b566519754acd251dbe323a2fe1dc06c9a"
|
||||||
integrity sha512-70aY2FSRVd1u6q8iXY+HDQDm6598lQt/toSNLrKeQhbmzw75y40Hmg85MTDtVv1NElbRPNtsX9aPuOQVsFuOzA==
|
integrity sha512-70aY2FSRVd1u6q8iXY+HDQDm6598lQt/toSNLrKeQhbmzw75y40Hmg85MTDtVv1NElbRPNtsX9aPuOQVsFuOzA==
|
||||||
|
|
||||||
|
canvasjs@^1.8.3:
|
||||||
|
version "1.8.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/canvasjs/-/canvasjs-1.8.3.tgz#181a526bbce09c1909431d9471f808494fe970cf"
|
||||||
|
integrity sha512-60eUT0VjqRgYqdIQcOkXg0Zptfbl4HefA/O51YEf1m/P0uXvE3icI/1KPrXpY9aVxn8gG/BB8DzVoTGCcyBnYg==
|
||||||
|
|
||||||
caseless@~0.12.0:
|
caseless@~0.12.0:
|
||||||
version "0.12.0"
|
version "0.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||||
|
|
@ -307,6 +312,13 @@ datatables.net@>=1.12.1, datatables.net@^1.10.22:
|
||||||
dependencies:
|
dependencies:
|
||||||
jquery ">=1.7"
|
jquery ">=1.7"
|
||||||
|
|
||||||
|
daterangepicker@dangrossman/daterangepicker:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://codeload.github.com/dangrossman/daterangepicker/tar.gz/8495717c4007a03fd5dee422f161811fd6140c0e"
|
||||||
|
dependencies:
|
||||||
|
jquery ">=1.10"
|
||||||
|
moment "^2.9.0"
|
||||||
|
|
||||||
delayed-stream@~1.0.0:
|
delayed-stream@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||||
|
|
@ -479,6 +491,11 @@ jquery@3.3.1:
|
||||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
|
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
|
||||||
integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==
|
integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==
|
||||||
|
|
||||||
|
jquery@>=1.10:
|
||||||
|
version "3.6.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.3.tgz#23ed2ffed8a19e048814f13391a19afcdba160e6"
|
||||||
|
integrity sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==
|
||||||
|
|
||||||
jquery@>=1.12.0, jquery@>=1.7, jquery@>=1.7.2, jquery@^3.6.0:
|
jquery@>=1.12.0, jquery@>=1.7, jquery@>=1.7.2, jquery@^3.6.0:
|
||||||
version "3.6.3"
|
version "3.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.3.tgz#23ed2ffed8a19e048814f13391a19afcdba160e6"
|
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.3.tgz#23ed2ffed8a19e048814f13391a19afcdba160e6"
|
||||||
|
|
@ -538,7 +555,7 @@ moment-timezone@^0.5.34:
|
||||||
dependencies:
|
dependencies:
|
||||||
moment ">= 2.9.0"
|
moment ">= 2.9.0"
|
||||||
|
|
||||||
"moment@>= 2.9.0", moment@^2.10.2, moment@^2.27.0, moment@^2.29.2:
|
"moment@>= 2.9.0", moment@^2.10.2, moment@^2.27.0, moment@^2.29.2, moment@^2.9.0:
|
||||||
version "2.29.4"
|
version "2.29.4"
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
|
||||||
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
|
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user