mirror of
https://github.com/grocy/grocy.git
synced 2026-04-04 03:46: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)
|
||||
{
|
||||
return $this->renderPage($response, 'transfer', [
|
||||
|
|
|
|||
|
|
@ -2371,3 +2371,18 @@ msgstr ""
|
|||
|
||||
msgid "Quick open amount"
|
||||
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-select": "^1.13.18",
|
||||
"bwip-js": "^3.0.1",
|
||||
"canvasjs": "^1.8.3",
|
||||
"chart.js": "^2.8.0",
|
||||
"datatables.net": "^1.10.22",
|
||||
"datatables.net-bs4": "^1.10.22",
|
||||
|
|
@ -21,6 +22,7 @@
|
|||
"datatables.net-rowgroup-bs4": "^1.1.2",
|
||||
"datatables.net-select": "^1.3.1",
|
||||
"datatables.net-select-bs4": "^1.3.1",
|
||||
"daterangepicker": "dangrossman/daterangepicker",
|
||||
"fullcalendar": "^3.10.1",
|
||||
"gettext-translator": "2.1.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('/location/{locationId}', '\Grocy\Controllers\StockController:LocationEditForm');
|
||||
$group->get('/stockjournal', '\Grocy\Controllers\StockController:Journal');
|
||||
$group->get('/stockmetricspurchases', '\Grocy\Controllers\StockController:StockMetricsPurchases');
|
||||
$group->get('/locationcontentsheet', '\Grocy\Controllers\StockController:LocationContentSheet');
|
||||
$group->get('/quantityunitpluraltesting', '\Grocy\Controllers\StockController:QuantityUnitPluralFormTesting');
|
||||
$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
|
||||
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
|
||||
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 ('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 ('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 ('07 {$this->__t_sql('Beverages')}'); --7'
|
||||
|
||||
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, 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) 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('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
|
||||
|
|
@ -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('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('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;
|
||||
|
||||
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(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(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->OpenProduct(3, 1);
|
||||
$stockService->OpenProduct(6, 1);
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@
|
|||
rel="stylesheet">
|
||||
<link href="{{ $U('/node_modules/bootstrap-select/dist/css/bootstrap-select.min.css?v=', true) }}{{ $version }}"
|
||||
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 }}"
|
||||
rel="stylesheet">
|
||||
<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
|
||||
<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/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/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') }}">
|
||||
{{ $__t('Stock entries') }}
|
||||
</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)
|
||||
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
|
||||
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"
|
||||
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:
|
||||
version "0.12.0"
|
||||
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:
|
||||
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:
|
||||
version "1.0.0"
|
||||
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"
|
||||
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:
|
||||
version "3.6.3"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.3.tgz#23ed2ffed8a19e048814f13391a19afcdba160e6"
|
||||
|
|
@ -538,7 +555,7 @@ moment-timezone@^0.5.34:
|
|||
dependencies:
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
|
||||
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user