Feature: Stock Purchase Metrics

This commit is contained in:
Travis Raup 2023-02-12 08:29:44 -05:00
parent 91700e7dae
commit 24b178fe75
10 changed files with 326 additions and 2 deletions

View File

@ -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', [

View File

@ -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 ""

View File

@ -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
View 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();
});

View File

@ -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');

View File

@ -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);

View File

@ -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>

View 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>&nbsp;{{ $__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>&nbsp;{{ $__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

View File

@ -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') }}">

View File

@ -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==