ordersFilterSpecs = $ordersFilterSpecs;
$this->productsFilterSpecs = $productsFilterSpecs;
}
public function getEmptyGraph()
{
$chart = new Highchart(Highchart::HIGHCHART, -1);
$chart->chart = [
'type' => 'line',
'marginRight' => 130,
'marginBottom' => 25,
];
return $chart;
}
public function getGraph($json = true)
{
$dbcfg = \Settings::getDefault();
$chart = $this->getEmptyGraph();
$chart->subtitle = [
'x' => -20,
];
$chart->xAxis->categories = [];
$chart->yAxis = [
'plotLines' => [
[
'value' => 0,
'width' => 1,
'color' => '#808080',
],
],
];
$methodGraphName = ucfirst($this->type_graph);
$fullRange = in_array($methodGraphName, ['Warehouse'], true);
$dataset = [];
$this->data = ($this->tabInstance ?? $this)->{"getDB{$methodGraphName}Data"}();
foreach ($methodGraphName != 'Sales' ? [$this->data] : $this->data as $stat) {
switch ($this->interval) {
case 'day':
$chart->subtitle->text = translate('intervalDaily', 'stats');
list($x, $data) = $this->getDaysData($stat);
break;
case 'month':
$chart->subtitle->text = translate('intervalMonthly', 'stats');
list($x, $data) = $this->getMonthsData($stat);
break;
case 'year':
default:
$chart->subtitle->text = translate('intervalYearly', 'stats');
list($x, $data) = $this->getYearsData($stat);
break;
}
if ($fullRange) {
$dataset = $data;
} else {
$dataset[] = $data[0];
}
}
$chart->series = $dataset;
$chart->xAxis->categories = $x;
// TODO: udělat dynamický
if ($this->type_graph == 'orders') {
$chart->yAxis['title']['text'] = translate('amount', 'stats');
$chart->title['text'] = translate('ordersAmount', 'stats');
$chart->series[0]['name'] = translate('ordersTotal', 'stats');
// TODO: Tohle dát taky někam společně, je to stejný, pokud teda nebude předefinováno - viz renderTo
$chart->tooltip->formatter = new HighchartJsExpr(
"function() { return ''+ this.series.name +'
'+ this.x +': '+ this.y;}");
} elseif ($this->type_graph == 'sales') {
$chart->yAxis['title']['text'] = translate('orderValue', 'stats')."({$dbcfg->currency})";
$chart->title['text'] = translate('revenue', 'stats');
$chart->series[0]['name'] = translate('revenue', 'stats');
$chart->series[1]['name'] = translate('revenueNoVat', 'stats');
if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_BUY)) {
$chart->series[2]['name'] = translate('priceBuy', 'stats');
$chart->series[3]['name'] = translate('profit', 'stats');
}
$chart->tooltip->formatter = new HighchartJsExpr(
"function() { return ''+ this.series.name +'
'+ this.x +': '+ this.y.toString().replace(/\B(?=(\d{3})+(?!\d))/g, \" \") +' {$dbcfg->currency} ' + ([1, 2, 3].includes(this.series._i) ? '".translate('withoutVat', 'stats')."' : '".translate('withVat', 'stats')."');}");
} elseif ($this->type_graph == 'product') {
$chart->yAxis['title']['text'] = translate('pcsSold', 'stats');
$chart->title['text'] = $this->getProductName();
$chart->series[0]['name'] = translate('pcsSold', 'stats');
$chart->tooltip->formatter = new HighchartJsExpr(
"function() { return ''+ this.series.name +'
'+ this.x +': '+ this.y;}");
} elseif ($this->type_graph == 'warehouse') {
$chart->yAxis['title']['text'] = translate('score', "stats{$methodGraphName}Tab");
$chart->title['text'] = $this->getProductName();
$chart->tooltip->formatter = new HighchartJsExpr(
"function() { return ''+ this.series.name +'
'+ this.x +': '+ this.y;}");
} elseif ($this->type_graph == 'store') {
$chart->yAxis['title']['text'] = translate('score', "stats{$methodGraphName}Tab");
$chart->title['text'] = $this->getProductName();
$chart->series[0]['name'] = translate('stockStore', 'stats');
$chart->tooltip->formatter = new HighchartJsExpr(
"function() { return ''+ this.series.name +'
'+ this.x +': '+ this.y;}");
}
$chart->series[0]['color'] = '#0d4db6';
$chart->title['x'] = -20;
$chart->chart['renderTo'] = $this->renderTo ? $this->renderTo : $this->type_graph;
$chart->legend = [
'layout' => 'vertical',
'align' => 'right',
'verticalAlign' => 'top',
'x' => -5,
'y' => 100,
'borderWidth' => 0,
];
if ($json) {
echo json_encode($chart->renderOptions());
exit;
} else {
return $chart;
}
}
public function getProductName()
{
$id_product = getVal('id_product');
$id_variation = getVal('id_variation');
if (empty($id_product)) {
return '';
}
// /////////////
/** @var QueryBuilderBase $SQL */
$SQL = sqlQueryBuilder()->select('p.title as product_name')
->from('products', 'p');
if (!empty($id_variation)) {
$SQL->addSelect('pv.title as variation_name')
->leftJoin('p', 'products_variations', 'pv', 'pv.id = :id_variation AND pv.id_product = :id_product')
->setParameter('id_variation', $id_variation)
->setParameter('id_product', $id_product);
}
$SQL = $SQL->where(Operator::equals(['p.id' => $id_product]))
->execute();
$names = sqlFetchAssoc($SQL);
if (!empty($names)) {
$name = $names['product_name'];
if (!empty($id_variation)) {
$name .= " ({$names['variation_name']})";
}
return $name;
} else {
return '';
}
}
public function getDBOrdersShareData($field)
{
$SQL = sqlQueryBuilder()
->from('orders', 'o')
->andWhere($this->addIntervalSpec(true))
->orderBy('year', 'ASC');
$SQL->addSelect($field);
$filter = getVal('filter', null, []);
$specs = $this->ordersFilterSpecs->getSpecs($filter);
if ($specs) {
$SQL->andWhere($specs);
}
$SQL = $SQL->execute();
return $this->data = $this->formatData($SQL);
}
public function getDBOrdersData()
{
return $this->getDBOrdersShareData('COUNT(*) as sum');
}
public function getDBSalesData()
{
$sum = (findModule('currencies')) ? ' * o.currency_rate' : '';
$data = [
$this->getDBOrdersShareData('SUM(o.total_price'.$sum.') as sum'),
$this->getDBOrdersShareData('SUM(o.total_price_without_vat'.$sum.') as sum'),
];
if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_BUY)) {
$data[] = $this->getDBOrdersShareData('SUM((SELECT SUM(COALESCE(oi.price_buy'.$sum.', pv.price_buy, p.price_buy) * oi.pieces) FROM order_items oi LEFT JOIN products p ON p.id <=> oi.id_product LEFT JOIN products_variations pv ON pv.id <=> oi.id_variation WHERE oi.id_order=o.id)) as sum');
$data[] = $this->getDBOrdersShareData('(SUM(o.total_price_without_vat'.$sum.') - SUM((SELECT SUM(COALESCE(oi.price_buy'.$sum.', pv.price_buy, p.price_buy) * oi.pieces) FROM order_items oi LEFT JOIN products p ON p.id <=> oi.id_product LEFT JOIN products_variations pv ON pv.id <=> oi.id_variation WHERE oi.id_order=o.id))) as sum');
}
return $data;
}
public function getProductsSellingSubQuery()
{
$subQuery = sqlQueryBuilder()
->from('order_items', 'oi')
->leftJoin('oi', 'orders', 'o', 'o.id=oi.id_order')
->join('oi', 'products', 'p', 'p.id = oi.id_product')
->andWhere('o.status_storno = 0')
->andWhere($this->addIntervalSpec(true));
$filter = getVal('filter', null, []);
$specs = $this->productsFilterSpecs->getSpecs($filter);
if ($specs) {
$subQuery->andWhere($specs);
}
$specs = $this->ordersFilterSpecs->getSpecs($filter);
if ($specs) {
$subQuery->andWhere($specs);
}
if (isset($filter['variations'])) {
$subQuery->andWhere('pv.id = oi.id_variation');
}
$subQuery->select('o.date_created as date_created, o.date_handle as date_handle, oi.pieces as pieces');
$subQuery->resetQueryPart('orderBy');
$subQuery->groupBy('oi.id');
return $subQuery;
}
public function getDBProductData()
{
$subQuery = $this->getProductsSellingSubQuery();
$SQL = sqlQueryBuilder()
->from('('.$subQuery->getSQL().')', 'o')
->andWhere($this->addIntervalSpec(true))
->addSelect('SUM(o.pieces) as sum')
->addParameters($subQuery->getParameters(), $subQuery->getParameterTypes())
->orderBy('year', 'ASC');
$SQL = $SQL->execute();
return $this->data = $this->formatData($SQL);
}
public function getProductsSelling($page = null, $limit = null)
{
$subQuery = $this->getProductsSellingSubQuery()->addSelect('p.id, p.title, p.price');
/** @var QueryBuilderBase $sql_products */
$sql_products = sqlQueryBuilder()
->select('o.id, o.title, SUM(o.pieces) AS sold, o.price')
->from('('.$subQuery->getSQL().')', 'o')
->addParameters($subQuery->getParameters(), $subQuery->getParameterTypes())
->groupBy('o.id')
->orderBy('sold', 'DESC');
if (!is_null($page) && !is_null($limit)) {
$sql_products->setFirstResult(($page == 0) ? $page : $limit * $page)
->setMaxResults($limit);
}
$products = [];
foreach ($sql_products->execute() as $product) {
$product['variations'] = [];
$sql_variations = $this->getProductsSellingVariationsQueryBuilder()
->andWhere('o.status_storno = 0')
->andWhere($this->addIntervalSpec())
->andWhere(Operator::equals(['oi.id_product' => $product['id']]))
->andWhere($this->getSpecVariations());
foreach ($sql_variations->execute() as $variation) {
$product['variations'][] = $variation;
}
$products[$product['id']] = $product;
}
return $products;
}
protected function getSpecVariations()
{
$filter = getVal('filter', null, []);
return function (QueryBuilder $qb) use ($filter) {
if (isset($filter['variations'])) {
$variationIds = $filter['variations'];
$invert = isset($filter['variations_invert']);
$variationsValuesOperator = $filter['variationsValuesOperator'];
if (in_array($variationsValuesOperator, ['AND', 'OR'])) {
$expression = Filter::variationsFilterUtil($variationIds, null, $variationsValuesOperator == 'AND');
return ($invert) ? Operator::not($expression) : $expression;
}
}
return null;
};
}
protected function getProductsSellingVariationsQueryBuilder(): QueryBuilder
{
$qb = sqlQueryBuilder()->select('COALESCE(oi.id_variation, "no_variation") id_variation', 'COALESCE(pv.title, "Bez varianty") title', 'SUM(oi.pieces) AS sold', 'pv.price')
->from('order_items', 'oi')
->leftJoin('oi', 'products_variations', 'pv', 'oi.id_product = pv.id_product AND oi.id_variation = pv.id')
->leftJoin('oi', 'orders', 'o', 'o.id=oi.id_order');
$filter = getVal('filter', null, []);
unset($filter['orderproducts']);
$specs = $this->ordersFilterSpecs->getSpecs($filter);
if ($specs) {
$qb->andWhere($specs);
}
$qb->resetQueryParts(['groupBy', 'orderBy']);
$qb->groupBy('id_variation')
->orderBy('sold', 'DESC');
return $qb;
}
private function addIntervalSpec($addInterval = false)
{
return function (QueryBuilder $qb) use ($addInterval) {
$date = $this->intervalType;
$qb->setParameter('from', $this->prepareDate($this->from).' 00:00:00')
->setParameter('to', $this->prepareDate($this->to).' 23:59:59');
if ($this->to) {
$qb->andWhere("{$date} >= :from");
}
if ($this->from) {
$qb->andWhere("{$date} <= :to");
}
if ($addInterval) {
$qb->select("(YEAR({$date})- YEAR(:from)) as year")
->groupBy("YEAR({$date})");
switch ($this->interval) {
case 'month':
$qb->addSelect("MONTH({$date}) as month")
->addGroupBy("MONTH({$date})")
->addOrderBy('month');
break;
case 'year':
break;
case 'day':
default:
$qb->addSelect("DAY({$date}) as day, MONTH({$date}) as month")
->addGroupBy("MONTH({$date}), DAY({$date})")
->addOrderBy('month,day');
break;
}
}
};
}
/**
* @param bool|\DateTime $from
*
* @return StatsDataLoader
*/
public function setFrom($from)
{
if (empty($from)) {
$from = date(\Settings::getDateFormat(), time() - (365 * 86400));
}
$this->from = \DateTime::createFromFormat(\Settings::getDateFormat(), $from);
return $this;
}
/**
* @return $this
*/
public function setTo($to)
{
if (empty($to)) {
$to = date(\Settings::getDateFormat(), time());
}
$this->to = \DateTime::createFromFormat(\Settings::getDateFormat(), $to);
return $this;
}
public function setTypeInterval($type)
{
if ($type) {
$this->intervalType = $type;
}
return $this;
}
/**
* @param string $type_graph
*
* @return StatsDataLoader
*/
public function setTypeGraph($type_graph)
{
$this->type_graph = $type_graph;
return $this;
}
/**
* @return $this
*/
public function setInterval($interval)
{
$this->interval = $interval;
return $this;
}
/**
* @return StatsDataLoader
*/
public function setRenderTo($renderTo)
{
$this->renderTo = $renderTo;
return $this;
}
/**
* @return StatsDataLoader
*/
public function setTabInstance($tabInstance)
{
$this->tabInstance = $tabInstance;
return $this;
}
}