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; } }