znt/module/metric/calc.class.php

768 lines
18 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* 度量项定义基类。
* Base class of measurement func.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @author qixinzhi <qixinzhi@easycorp.ltd>
* @package
* @license LGPL
* @Link https://www.zentao.net
*/
class baseCalc
{
/**
* 来源数据集。
* dataset
*
* @var int
* @access public
*/
public $dataset = null;
/**
* 数据库连接。
* Database connection.
*
* @var object
* @access public
*/
public $dao = null;
/**
* Git 仓库对象。
* Git repository object.
*
* @var object
* @access public
*/
public $scm = null;
/**
* 参数列表。
* fieldList
*
* @var array
* @access public
*/
public $fieldList = array();
/**
* 数据主表。
* Main table.
*
* @var string
* @access public
*/
public $mainTable;
/**
* 连接表。
* Left join tables.
*
* @var array
* @access public
*/
public $subTables;
/**
* 过滤条件。
* Filters of data.
*
* @var array
* @access public
*/
public $filters;
/**
* 指标结果。
* Result of indicators.
*
* @var array|float
* @access public
*/
public $result = 0;
/**
* 节假日信息。
* holidays
*
* @var array
* @access public
*/
public $holidays = array();
/**
* 休息日
* weekend
*
* @var float
* @access public
*/
public $weekend = 2;
/**
* 是否复用
*
* @var bool
* @access public
*/
public $reuse = false;
/**
* 是否启用initMetricRecords尽可能补零逻辑
*
* @var bool
* @access public
*/
public $initRecord = true;
/**
* 是否支持独立查询
*
* @var bool
* @access public
*/
public $supportSingleQuery = false;
/**
* 是否使用独立查询
*
* @var bool
* @access public
*/
public $useSingleQuery = false;
/**
* 独立查询sql
*
* @var string
* @access public
*/
public $singleSql = '';
/**
* 是否使用 SCM。
*
* @var bool
* @access public
*/
public $useSCM = false;
/**
* GitFox 仓库列表
*
* @var array
* @access public
*/
public $repos = array();
/**
* 设置DAO 。
* Set DAO.
*
* @param object $dao
* @access public
* @return void
*/
public function setDAO($dao)
{
$this->dao = $dao;
}
public function setSCM($scm)
{
$this->scm = $scm;
}
public function setHolidays($holidays)
{
$this->holidays = $holidays;
}
public function setWeekend($weekend)
{
$this->weekend = $weekend;
}
public function setGitFoxRepos($repos)
{
$this->repos = $repos;
}
/**
* 获取数据查询句柄。
* Get pdo statement of sql query.
*
* @param object $dao
* @access public
* @return PDOStatement
*/
public function getStatement()
{
}
/**
* 计算度量项。
* Calculate metric.
*
* @param object $data
* @access public
* @return void
*/
public function calculate($data)
{
}
/**
* 获取度量项结果。
*
* @param array $options
* @access public
* @return mixed
*/
public function getResult($options = array())
{
if(empty($this->result)) return null;
return array((object)array('value' => 0));
}
/**
* 根据选项过滤数据。
* Filter rows by options.
*
* @param array|int $rows
* @param array $options array('product' => '1,2,3,4')
* @access protected
* @return array|false
*/
protected function filterByOptions($rows, $options)
{
if(empty($options)) return $rows;
$rows = (array)$rows;
$options = $this->expandOptions($options);
$filteredRows = array();
foreach($rows as $row)
{
$satisify = true;
foreach($options as $scope => $option)
{
$row = (array)$row;
if(!isset($row[$scope])) continue;
$satisify = ($satisify && in_array($row[$scope], $option));
}
if($satisify) $filteredRows[] = $row;
}
return !empty($filteredRows) ? $filteredRows : false;
}
/**
* 判断是否为日期。
* Check if a string is a date.
*
* @param string $dateString
* @access public
* @return bool
*/
public function isDate($dateString)
{
if(empty($dateString)) return false;
$format = 'Y-m-d';
$dateTime = DateTime::createFromFormat($format, $dateString);
return $dateTime && $dateTime->format($format) === $dateString;
}
/**
* 扩展选项。
* Expand options.
*
* @param array $options
* @access protected
* @return array
*/
protected function expandOptions($options)
{
foreach($options as $scope => $option) $options[$scope] = explode(',', $option);
return $options;
}
/**
* 获取数据源的SQL语句
* Get SQL of data source.
*
* @param object $dao
* @param object $config
* @access public
* @return string
*/
public function getSQL($dao, $config)
{
if(!empty($this->dataset))
{
$dataSource = $this->dataset;
include_once dirname(__FILE__) . '/dataset.php';
$dataset = new dataset($dao, $config);
$statement = $dataset->$dataSource(implode(',', $this->fieldList));
return $statement->queryString;
}
return $this->dao->get();
}
/**
* 打印数据源的SQL语句
* Print SQL of data source.
*
* @param object $dao
* @param object $config
* @access public
* @return mixed
*/
public function printSQL($dao, $config)
{
echo $this->getSQL($dao, $config);
}
/**
* 获取数据源的结果集。
* Get rows of data source.
*
* @param object $dao
* @param object $config
* @access public
* @return array
*/
public function getRows($dao, $config)
{
if(!empty($this->dataset))
{
$dataSource = $this->dataset;
include_once dirname(__FILE__) . '/dataset.php';
$dataset = new dataset($dao, $config);
$statement = $dataset->$dataSource(implode(',', $this->fieldList));
return $statement->fetchAll();
}
return $this->getStatement()->fetchAll();
}
/**
* 转换计算结果数组为度量记录数据数组。
* Get records from result.
*
* @param array $keyNames
* @access public
* @return array
*/
public function getRecords($keyNames, $result = null)
{
if($this->useSingleQuery) return $this->singleQuery();
if(empty($result)) $result = $this->result;
if(empty($keyNames)) return $result;
if(current($keyNames) == 'value') return array(array('value' => $result));
$records = array();
$keyName = array_shift($keyNames);
foreach($result as $key => $value) $records[] = array($keyName => $key, 'value' => $value);
while($keyName = array_shift($keyNames))
{
if($keyName == 'value') break;
$newRecords = array();
foreach($records as $record)
{
foreach($record['value'] as $key => $value)
{
$record[$keyName] = $key;
$record['value'] = $value;
$newRecords[] = $record;
}
}
$records = $newRecords;
}
return $records;
}
/**
* 独立查询。
*
* @access public
* @return void
*/
public function singleQuery()
{
$sql = $this->singleSql;
return $this->dao->select('*')->from("($sql) tt")->fetchAll();
}
/**
* 设置独立查询sql语句。
*
* @param string $sql
* @access public
* @return void
*/
public function setSingleSql($sql)
{
$this->singleSql = $sql;
}
/**
* 获取独立查询sql语句。
*
* @access public
* @return void
*/
public function getSingleSql()
{
return "({$this->singleSql}) tt";
}
/**
* 启用独立查询。
*
* @access public
* @return void
*/
public function enableSingleQuery()
{
$this->useSingleQuery = true;
}
/**
* 获取日期的周数。
* Get week of date.
*
* @param string $dateStr
* @access public
* @return string
*/
public function getWeek($dateStr)
{
if(empty($dateStr)) return false;
if(strlen($dateStr) > 10) $dateStr = substr($dateStr, 0, 10);
$date = DateTime::createFromFormat('Y-m-d', $dateStr);
return substr($date->format('oW'), -2);
}
/**
* 获取日期的年份。
* Get year of date.
*
* @param string $dateStr
* @access public
* @return string|false
*/
public function getYear($dateStr)
{
if(empty($dateStr)) return false;
if(strlen($dateStr) > 8) $dateStr = substr($dateStr, 0, 8);
$year = substr($dateStr, 0, 4);
return $year == '0000' ? false : $year;
}
/**
* GetLastDay
*
* @param int $date
* @access public
* @return string
*/
public function getLastDay($date)
{
$monday = $this->getThisMonday($date);
$sunday = $this->getThisSunday($date);
$workdays = $this->getActualWorkingDays($monday, $sunday);
return end($workdays);
}
/**
* Get monday for a date.
*
* @param int $date
* @access public
* @return date
*/
public function getThisMonday($date)
{
$timestamp = strtotime($date);
$day = date('w', $timestamp);
if($day == 0) $day = 7;
return date('Y-m-d', $timestamp - (($day - 1) * 24 * 3600));
}
/**
* Get fridays.
*
* @param string $start
* @param string $end
* @access public
* @return array
*/
public function getFridays($start, $end)
{
$start = new DateTime($start);
$end = new DateTime($end);
$interval = new DateInterval('P1D'); // 间隔为1天
$period = new DatePeriod($start, $interval, $end);
$fridays = array();
foreach ($period as $date) {
if ($date->format('N') == 5) { // 5代表星期五
$fridays[] = $date->format('Y-m-d');
}
}
return $fridays;
}
public function getFridayByWeek($year, $week) {
$first_day = date("Y-m-d", strtotime($year . "-01-01"));
$first_friday = date("Y-m-d", strtotime("{$first_day} next Friday"));
$friday = date("Y-m-d", strtotime("{$first_friday} + " . ($week - 1) . " weeks"));
return $friday;
}
/**
* GetThisSunday
*
* @param int $date
* @access public
* @return date
*/
public function getThisSunday($date)
{
$monday = $this->getThisMonday($date);
return date('Y-m-d', strtotime($monday) + (6 * 24 * 3600));
}
/**
* 获取开始和结束日期间的日期。
* Get the dates between the begin and end.
*
* @param string $begin
* @param string $end
* @access public
* @return array
*/
public function getDaysBetween(string $begin, string $end): array
{
$beginTime = strtotime($begin);
$endTime = strtotime($end);
$days = ($endTime - $beginTime) / 86400;
$dateList = array();
for($i = 0; $i <= $days; $i ++) $dateList[] = date('Y-m-d', strtotime("+{$i} days", $beginTime));
return $dateList;
}
/**
* 通过开始和结束日期获取节假日。
* Get holidays by begin and end.
*
* @param string $begin
* @param string $end
* @access public
* @return array
*/
public function getHolidays(string $begin, string $end): array
{
$records = array();
foreach($this->holidays as $holiday)
{
if($holiday->type != 'holiday') continue;
if($holiday->begin > $end || $holiday->end < $begin) continue;
$records[] = $holiday;
}
$naturalDays = $this->getDaysBetween($begin, $end);
$holidays = array();
foreach($records as $record)
{
$dates = $this->getDaysBetween($record->begin, $record->end);
$holidays = array_merge($holidays, $dates);
}
return array_intersect($naturalDays, $holidays);
}
/**
* 获取工作日。
* Get working days.
*
* @param string $begin
* @param string $end
* @access public
* @return array
*/
public function getWorkingDays(string $begin = '', string $end = ''): array
{
$records = array();
foreach($this->holidays as $holiday)
{
if($holiday->type != 'working') continue;
if($holiday->begin > $end || $holiday->end < $begin) continue;
$records[] = $holiday;
}
$workingDays = array();
foreach($records as $record)
{
$dates = $this->getDaysBetween($record->begin, $record->end);
$workingDays = array_merge($workingDays, $dates);
}
return $workingDays;
}
/**
* 获取系统休息日配置。
* Get system weekend.
*
* @access public
* @return void
*/
public function getSystemWeekend()
{
$weekend = $this->dao->select('value')->from(TABLE_CONFIG)
->where('module')->eq('execution')
->andWhere('key')->eq('weekend')
->fetch('value');
return $weekend ? $weekend : 2;
}
/**
* 获取实际工作日。
* Get actual working days.
*
* @param string $begin
* @param string $end
* @access public
* @return array
*/
public function getActualWorkingDays(string $begin, string $end): array
{
if(empty($begin) || empty($end) || $begin == '0000-00-00' || $end == '0000-00-00') return array();
/* Get holidays, working days and weekend days .*/
$holidays = $this->getHolidays($begin, $end);
$workingDays = $this->getWorkingDays($begin, $end);
$weekend = $this->weekend;
/* When the start date and end date are the same. */
$actualDays = array();
if($begin == $end)
{
if(in_array($begin, $workingDays)) return array($begin);
if(in_array($begin, $holidays)) return array();
$w = date('w', strtotime($begin));
if($w == 0 || ($weekend == 2 && $w == 6)) return array();
return array($begin);
}
/* Process actual working days. */
for($i = 0, $currentDay = $begin; $currentDay < $end; $i ++)
{
$currentDay = date('Y-m-d', strtotime("{$begin} + {$i} days"));
$w = date('w', strtotime($currentDay));
if(in_array($currentDay, $workingDays))
{
$actualDays[] = $currentDay;
continue;
}
if(in_array($currentDay, $holidays)) continue;
if($w == 0 || ($weekend == 2 && $w == 6)) continue;
$actualDays[] = $currentDay;
}
return $actualDays;
}
public function getDevAccountList()
{
global $dao;
return $dao->select('account')->from(TABLE_USER)->where('role')->eq('dev')->andWhere('deleted')->eq('0')->fetchPairs();
}
public function getExecutions()
{
global $dao;
$executions = $dao->select("t1.id, t1.name, if(t1.multiple='1', t1.status, t2.status) as status, if(t1.multiple='1', t1.realBegan, t2.realBegan) as realBegan, if(t1.multiple='1', t1.closedDate, t2.closedDate) as closedDate")->from(TABLE_PROJECT)->alias('t1')
->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')
->where('t1.deleted')->eq(0)
->andWhere('t2.deleted')->eq(0)
->andWhere('t1.type')->in('sprint,stage,kanban')
->andWhere("t1.vision LIKE '%rnd%'", true)
->orWhere("t1.vision IS NULL")->markRight(1)
->fetchAll('id');
return $executions;
}
/**
* 检查日期。
* Validate date.
*
* @param string $date
* @access public
* @return bool
*/
public function validateDate(string $date): bool
{
return $this->getYear($date) !== false;
}
/**
* 按日期递增计数。
* Count incrementally by date.
*
* @param int $dataID
* @param string $date
* @access public
* @return void
*/
public function incrementDateCount(int $dataID, string $date)
{
$date = substr($date, 0, 10);
list($year, $month, $day) = explode('-', $date);
if(!isset($this->result[$dataID])) $this->result[$dataID] = array();
if(!isset($this->result[$dataID][$year])) $this->result[$dataID][$year] = array();
if(!isset($this->result[$dataID][$year][$month])) $this->result[$dataID][$year][$month] = array();
if(!isset($this->result[$dataID][$year][$month][$day])) $this->result[$dataID][$year][$month][$day] = 0;
$this->result[$dataID][$year][$month][$day]++;
}
}