diff --git a/config/zentaopms.php b/config/zentaopms.php
index 76822302e5..f86b810f5b 100644
--- a/config/zentaopms.php
+++ b/config/zentaopms.php
@@ -206,6 +206,7 @@ define('TABLE_API_LIB_RELEASE', '`' . $config->db->prefix . 'api_lib_release`');
define('TABLE_MODULE', '`' . $config->db->prefix . 'module`');
define('TABLE_ACTION', '`' . $config->db->prefix . 'action`');
define('TABLE_FILE', '`' . $config->db->prefix . 'file`');
+define('TABLE_HOLIDAY', '`' . $config->db->prefix . 'holiday`');
define('TABLE_HISTORY', '`' . $config->db->prefix . 'history`');
define('TABLE_EXTENSION', '`' . $config->db->prefix . 'extension`');
define('TABLE_CRON', '`' . $config->db->prefix . 'cron`');
@@ -216,6 +217,7 @@ define('TABLE_SUITECASE', '`' . $config->db->prefix . 'suitecase`');
define('TABLE_TESTREPORT', '`' . $config->db->prefix . 'testreport`');
define('TABLE_ENTRY', '`' . $config->db->prefix . 'entry`');
+define('TABLE_WEEKLYREPORT', '`' . $config->db->prefix . 'weeklyreport`');
define('TABLE_WEBHOOK', '`' . $config->db->prefix . 'webhook`');
define('TABLE_LOG', '`' . $config->db->prefix . 'log`');
define('TABLE_SCORE', '`' . $config->db->prefix . 'score`');
diff --git a/db/update15.9.sql b/db/update15.9.sql
index c9a0fdbcb4..a4c6ad8742 100644
--- a/db/update15.9.sql
+++ b/db/update15.9.sql
@@ -46,3 +46,34 @@ CREATE TABLE IF NOT EXISTS `zt_designspec` (
`files` varchar(255) NOT NULL,
UNIQUE KEY `design` (`design`,`version`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- DROP TABLE IF EXISTS `zt_weeklyreport`;
+CREATE TABLE IF NOT EXISTS `zt_weeklyreport`(
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `weekStart` date NOT NULL,
+ `pv` float(9,2) NOT NULL,
+ `ev` float(9,2) NOT NULL,
+ `ac` float(9,2) NOT NULL,
+ `sv` float(9,2) NOT NULL,
+ `cv` float(9,2) NOT NULL,
+ `staff` smallint(5) unsigned NOT NULL,
+ `progress` varchar(255) NOT NULL,
+ `workload` varchar(255) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `week` (`project`,`weekStart`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- DROP TABLE IF EXISTS `zt_holiday`;
+CREATE TABLE IF NOT EXISTS `zt_holiday` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(30) NOT NULL DEFAULT '',
+ `type` enum('holiday', 'working') NOT NULL DEFAULT 'holiday',
+ `desc` text NOT NULL,
+ `year` char(4) NOT NULL,
+ `begin` date NOT NULL,
+ `end` date NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `year` (`year`),
+ KEY `name` (`name`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
diff --git a/db/zentao.sql b/db/zentao.sql
index 4a5a29f530..957418f640 100644
--- a/db/zentao.sql
+++ b/db/zentao.sql
@@ -586,6 +586,19 @@ CREATE TABLE IF NOT EXISTS `zt_grouppriv` (
`method` char(30) NOT NULL default '',
UNIQUE KEY `group` (`group`,`module`,`method`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+-- DROP TABLE IF EXISTS `zt_holiday`;
+CREATE TABLE IF NOT EXISTS `zt_holiday` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(30) NOT NULL DEFAULT '',
+ `type` enum('holiday', 'working') NOT NULL DEFAULT 'holiday',
+ `desc` text NOT NULL,
+ `year` char(4) NOT NULL,
+ `begin` date NOT NULL,
+ `end` date NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `year` (`year`),
+ KEY `name` (`name`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_history`;
CREATE TABLE IF NOT EXISTS `zt_history` (
`id` mediumint(8) unsigned NOT NULL auto_increment,
@@ -1477,6 +1490,22 @@ CREATE TABLE IF NOT EXISTS `zt_userview` (
`sprints` mediumtext NOT NULL,
UNIQUE KEY `account` (`account`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+-- DROP TABLE IF EXISTS `zt_weeklyreport`;
+CREATE TABLE IF NOT EXISTS `zt_weeklyreport`(
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `weekStart` date NOT NULL,
+ `pv` float(9,2) NOT NULL,
+ `ev` float(9,2) NOT NULL,
+ `ac` float(9,2) NOT NULL,
+ `sv` float(9,2) NOT NULL,
+ `cv` float(9,2) NOT NULL,
+ `staff` smallint(5) unsigned NOT NULL,
+ `progress` varchar(255) NOT NULL,
+ `workload` varchar(255) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `week` (`project`,`weekStart`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_webhook`;
CREATE TABLE IF NOT EXISTS `zt_webhook` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
diff --git a/module/block/control.php b/module/block/control.php
index bfb4294944..8847d27b8e 100644
--- a/module/block/control.php
+++ b/module/block/control.php
@@ -881,7 +881,7 @@ class block extends control
}
$today = helper::today();
- if(isset($this->config->maxVersion)) $monday = date('Ymd', strtotime($this->loadModel('weekly')->getThisMonday($today)));
+ $monday = date('Ymd', strtotime($this->loadModel('weekly')->getThisMonday($today)));
$tasks = $this->dao->select("project,
sum(consumed) as totalConsumed,
sum(if(status != 'cancel' and status != 'closed', `left`, 0)) as totalLeft")
@@ -901,7 +901,7 @@ class block extends control
$project->progress = $project->allStories == 0 ? 0 : round($project->doneStories / $project->allStories, 3) * 100;
$project->executions = $this->project->getStats($projectID, 'all', 0, 0, 30, 'id_desc', $pager);
}
- elseif($project->model == 'waterfall' and isset($this->config->maxVersion))
+ elseif($project->model == 'waterfall')
{
$begin = $project->begin;
$weeks = $this->weekly->getWeekPairs($begin);
diff --git a/module/common/lang/menu.php b/module/common/lang/menu.php
index 025951b915..655861ecbd 100644
--- a/module/common/lang/menu.php
+++ b/module/common/lang/menu.php
@@ -467,18 +467,16 @@ $lang->admin->menu->dev = array('link' => "$lang->redev|dev|api", 'alias'
$lang->admin->menu->message = array('link' => "{$lang->message->common}|message|index", 'subModule' => 'message,mail,webhook');
$lang->admin->menu->system = array('link' => "{$lang->admin->system}|backup|index", 'subModule' => 'cron,backup,action,admin,search', 'exclude' => 'admin-index,admin-xuanxuan,admin-register,admin-ztcompany');
-if($config->systemMode == 'new')
-{
- $lang->admin->menu->model['dropMenu'] = new stdclass();
- $lang->admin->menu->model['dropMenu']->allModel = array('link' => "{$lang->globalSetting}|custom|browsestoryconcept|", 'subModule' => 'subject,custom,meetingroom,baseline');
- $lang->admin->menu->model['dropMenu']->waterfall = array('link' => "{$lang->waterfallModel}|stage|setType|", 'subModule' => 'stage,measurement,auditcl,cmcl,process,activity,zoutput,classify,sqlbuilder,reviewcl,reviewsetting,report');
+$lang->admin->menu->model['dropMenu'] = new stdclass();
+$lang->admin->menu->model['dropMenu']->allModel = array('link' => "{$lang->globalSetting}|custom|browsestoryconcept|", 'subModule' => 'subject,custom,meetingroom,baseline');
+$lang->admin->menu->model['dropMenu']->waterfall = array('link' => "{$lang->waterfallModel}|stage|setType|", 'subModule' => 'stage,measurement,auditcl,cmcl,process,activity,zoutput,classify,sqlbuilder,reviewcl,reviewsetting,report');
- $lang->admin->menu->allModel['subMenu'] = new stdclass();
- $lang->admin->menu->allModel['subMenu']->storyConcept = array('link' => '需求概念|custom|browsestoryconcept|');
+$lang->admin->menu->allModel['subMenu'] = new stdclass();
+$lang->admin->menu->allModel['subMenu']->storyConcept = array('link' => "{$lang->storyConcept}|custom|browsestoryconcept|");
+$lang->admin->menu->allModel['menuOrder'][5] = 'storyConcept';
- $lang->admin->menu->waterfall['subMenu'] = new stdclass();
- $lang->admin->menu->waterfall['subMenu']->stage = array('link' => '阶段|stage|setType|', 'subModule' => 'stage');
-}
+$lang->admin->menu->waterfall['subMenu'] = new stdclass();
+$lang->admin->menu->waterfall['subMenu']->stage = array('link' => '阶段|stage|setType|', 'subModule' => 'stage');
/* Admin menu order. */
$lang->admin->menuOrder[5] = 'index';
@@ -490,11 +488,6 @@ $lang->admin->menuOrder[30] = 'extension';
$lang->admin->menuOrder[35] = 'dev';
$lang->admin->menuOrder[40] = 'system';
-$lang->admin->menu->model['subMenu'] = new stdclass();
-$lang->admin->menu->model['subMenu']->storyConcept = array('link' => "{$lang->storyConcept}|custom|browsestoryconcept|");
-
-$lang->admin->menu->model['menuOrder'][5] = 'storyConcept';
-
$lang->admin->menu->message['subMenu'] = new stdclass();
$lang->admin->menu->message['subMenu']->message = new stdclass();
$lang->admin->menu->message['subMenu']->mail = array('link' => "{$lang->mail->common}|mail|index", 'subModule' => 'mail');
diff --git a/module/holiday/config.php b/module/holiday/config.php
new file mode 100644
index 0000000000..f46a285b1e
--- /dev/null
+++ b/module/holiday/config.php
@@ -0,0 +1,5 @@
+holiday)) $config->holiday = new stdclass();
+$config->holiday->require = new stdclass();
+$config->holiday->require->create = 'name,begin,end';
+$config->holiday->require->edit = 'name,begin,end';
diff --git a/module/holiday/control.php b/module/holiday/control.php
new file mode 100644
index 0000000000..e4dcf4fc8e
--- /dev/null
+++ b/module/holiday/control.php
@@ -0,0 +1,117 @@
+
+ * @package holiday
+ * @version $Id
+ * @link http://www.zentao.net
+ */
+class holiday extends control
+{
+ /**
+ * Holiday list.
+ *
+ * @access public
+ * @return void
+ */
+ public function index()
+ {
+ $this->locate(inlink('browse'));
+ }
+
+ /**
+ * Holiday list.
+ *
+ * @param string $year
+ * @access public
+ * @return void
+ */
+ public function browse($year = '')
+ {
+ $holidays = $this->holiday->getList($year);
+ $yearList = $this->holiday->getYearPairs();
+
+ $this->view->title = $this->lang->holiday->browse;
+ $this->view->holidays = $holidays;
+ $this->view->yearList = $yearList;
+ $this->view->currentYear = $year;
+ $this->display();
+ }
+
+ /**
+ * Create a holiday.
+ *
+ * @access public
+ * @return void
+ */
+ public function create()
+ {
+ if($_POST)
+ {
+ $holidayID = $this->holiday->create();
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+ $actionID = $this->loadModel('action')->create('holiday', $holidayID, 'created');
+ return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'parent'));
+ }
+
+ $this->view->title = $this->lang->holiday->create;
+ $this->display();
+ }
+
+ /**
+ * Edit holiday.
+ *
+ * @param int $id
+ * @access public
+ * @return void
+ */
+ public function edit($id)
+ {
+ $holiday = $this->holiday->getById($id);
+ if($_POST)
+ {
+ $this->holiday->update($id);
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+ return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'parent'));
+ }
+
+ $this->view->title = $this->lang->holiday->edit;
+ $this->view->holiday = $holiday;
+ $this->display();
+ }
+
+ /**
+ * Delete holiday.
+ *
+ * @param int $id
+ * @param int $confirm
+ * @access public
+ * @return void
+ */
+ public function delete($id, $confirm = 'no')
+ {
+ if($confirm == 'no')
+ {
+ die(js::confirm($this->lang->holiday->confirmDelete, inLink('delete', "id=$id&confirm=yes")));
+ }
+ else
+ {
+ $holidayInformation = $this->dao->select('begin, end')->from(TABLE_HOLIDAY)->where('id')->eq($id)->fetch();
+ $this->dao->delete()->from(TABLE_HOLIDAY)->where('id')->eq($id)->exec();
+
+ /* Update project. */
+ $this->holiday->updateProgramPlanDuration($holidayInformation->begin, $holidayInformation->end);
+ $this->holiday->updateProjectRealDuration($holidayInformation->begin, $holidayInformation->end);
+
+ /* Update task. */
+ $this->holiday->updateTaskPlanDuration($holidayInformation->begin, $holidayInformation->end);
+ $this->holiday->updateTaskRealDuration($holidayInformation->begin, $holidayInformation->end);
+
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+ die(js::reload('parent'));
+ }
+ }
+}
diff --git a/module/holiday/css/browse.css b/module/holiday/css/browse.css
new file mode 100644
index 0000000000..50d38310c3
--- /dev/null
+++ b/module/holiday/css/browse.css
@@ -0,0 +1,3 @@
+.tree .active{font-weight: bold;}
+.with-side .side {position: absolute; width: 130px;}
+.with-side .main {padding-left: 145px; float: left;}
diff --git a/module/holiday/css/index.html b/module/holiday/css/index.html
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/module/holiday/ext/model/zentaomax.php b/module/holiday/ext/model/zentaomax.php
new file mode 100644
index 0000000000..90de78ecd8
--- /dev/null
+++ b/module/holiday/ext/model/zentaomax.php
@@ -0,0 +1,276 @@
+dao->select('*')->from(TABLE_HOLIDAY)
+ ->where('type')->eq('holiday')
+ ->andWhere('begin')->le($end)
+ ->andWhere('end')->ge($begin)
+ ->fetchAll('id');
+
+ $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);
+}
+
+public function getWorkingDays($begin = '', $end = '')
+{
+ $records = $this->dao->select('*')->from(TABLE_HOLIDAY)
+ ->where('type')->eq('working')
+ ->andWhere('begin')->le($end)
+ ->andWhere('end')->ge($begin)
+ ->fetchAll('id');
+
+ $workingDays = array();
+ foreach($records as $record)
+ {
+ $dates = $this->getDaysBetween($record->begin, $record->end);
+ $workingDays = array_merge($workingDays, $dates);
+ }
+ return $workingDays;
+}
+
+public function getActualWorkingDays($begin, $end)
+{
+ if(empty($begin) or empty($end) or $begin == '0000-00-00' or $end == '0000-00-00') return array();
+
+ $actualDays = array();
+ $currentDay = $begin;
+
+ $holidays = $this->getHolidays($begin, $end);
+ $workingDays = $this->getWorkingDays($begin, $end);
+ $weekend = isset($this->config->project->weekend) ? $this->config->project->weekend : 2;
+
+ /* When the start date and end date are the same. */
+ if($begin == $end)
+ {
+ if(in_array($begin, $workingDays)) return $actualDays[] = $begin;
+ if(in_array($begin, $holidays)) return $actualDays;
+
+ $w = date('w', strtotime($begin));
+ if($weekend == 2)
+ {
+ if($w == 0 or $w == 6) return $actualDays;
+ }
+ else
+ {
+ if($w == 0) return $actualDays;
+ }
+
+ $actualDays[] = $begin;
+ return $actualDays;
+ }
+
+ for($i = 0; $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($weekend == 2)
+ {
+ if($w == 0 or $w == 6) continue;
+ }
+ else
+ {
+ if($w == 0) continue;
+ }
+ $actualDays[] = $currentDay;
+ }
+
+ return $actualDays;
+}
+
+public function getDaysBetween($begin, $end)
+{
+ $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;
+}
+
+public function isHoliday($date)
+{
+ $record = $this->dao->select('*')->from(TABLE_HOLIDAY)
+ ->where('type')->eq('holiday')
+ ->andWhere('begin')->le($date)
+ ->andWhere('end')->ge($date)
+ ->fetch();
+ return !empty($record);
+}
+
+public function isWorkingDay($date)
+{
+ $record = $this->dao->select('*')->from(TABLE_HOLIDAY)
+ ->where('type')->eq('working')
+ ->andWhere('begin')->le($date)
+ ->andWhere('end')->ge($date)
+ ->fetch();
+ return !empty($record);
+}
+
+public function update($id)
+{
+ $result = parent::update($id);
+
+ if($result)
+ {
+ $beginDate = $this->post->begin;
+ $endDate = $this->post->end;
+
+ /* Update project. */
+ $this->updateProgramPlanDuration($beginDate, $endDate);
+ $this->updateProjectRealDuration($beginDate, $endDate);
+
+ /* Update task. */
+ $this->updateTaskPlanDuration($beginDate, $endDate);
+ $this->updateTaskRealDuration($beginDate, $endDate);
+ }
+
+ return $result;
+}
+
+public function create()
+{
+ $lastInsertID = parent::create();
+
+ if($lastInsertID)
+ {
+ $beginDate = $this->post->begin;
+ $endDate = $this->post->end;
+
+ /* Update project. */
+ $this->updateProgramPlanDuration($beginDate, $endDate);
+ $this->updateProjectRealDuration($beginDate, $endDate);
+
+ /* Update task. */
+ $this->updateTaskPlanDuration($beginDate, $endDate);
+ $this->updateTaskRealDuration($beginDate, $endDate);
+ }
+
+ return $lastInsertID;
+}
+
+public function delete($id, $null = null)
+{
+ $holidayInformation = $this->dao->select('begin,end')->from(TABLE_HOLIDAY)->where('id')->eq($id)->fetch();
+
+ $result = parent::delete($id, $null = null);
+ if($result)
+ {
+ /* Update project. */
+ $this->updateProgramPlanDuration($holidayInformation->begin, $holidayInformation->end);
+ $this->updateProjectRealDuration($holidayInformation->begin, $holidayInformation->end);
+
+ /* Update task. */
+ $this->updateTaskPlanDuration($holidayInformation->begin, $holidayInformation->end);
+ $this->updateTaskRealDuration($holidayInformation->begin, $holidayInformation->end);
+ }
+
+ return $result;
+}
+
+public function updateProgramPlanDuration($beginDate, $endDate)
+{
+ $updateProjectList = $this->dao->select('id, begin, end')
+ ->from(TABLE_PROJECT)
+ ->where('begin')->between($beginDate, $endDate)
+ ->orWhere('end')->between($beginDate, $endDate)
+ ->orWhere("(begin < '$beginDate' AND end > '$endDate')")
+ ->andWhere('status')->ne('done')
+ ->fetchAll();
+
+ foreach($updateProjectList as $project)
+ {
+ $realDuration = $this->getActualWorkingDays($project->begin, $project->end);
+ $realDuration = count($realDuration);
+
+ $this->dao->update(TABLE_PROJECT)
+ ->set('planDuration')->eq($realDuration)
+ ->where('id')->eq($project->id)
+ ->exec();
+ }
+}
+
+public function updateProjectRealDuration($beginDate, $endDate)
+{
+ $updateProjectList = $this->dao->select('id, realBegan, realEnd')
+ ->from(TABLE_PROJECT)
+ ->where('realBegan')->between($beginDate, $endDate)
+ ->orWhere('realEnd')->between($beginDate, $endDate)
+ ->orWhere("(realBegan < '$beginDate' AND realEnd > '$endDate')")
+ ->andWhere('status')->ne('done')
+ ->fetchAll();
+
+ foreach($updateProjectList as $project)
+ {
+ $realDuration = $this->getActualWorkingDays($project->realBegan, $project->realEnd);
+ $realDuration = count($realDuration);
+
+ $this->dao->update(TABLE_PROJECT)
+ ->set('realDuration')->eq($realDuration)
+ ->where('id')->eq($project->id)
+ ->exec();
+ }
+}
+
+public function updateTaskPlanDuration($beginDate, $endDate)
+{
+ $updateTaskList = $this->dao->select('id, estStarted, deadline')
+ ->from(TABLE_TASK)
+ ->where('estStarted')->between($beginDate, $endDate)
+ ->orWhere('deadline')->between($beginDate, $endDate)
+ ->orWhere("(estStarted < '$beginDate' AND deadline > '$endDate')")
+ ->andWhere('status') ->ne('done')
+ ->fetchAll();
+
+ foreach($updateTaskList as $task)
+ {
+ $planduration = $this->getActualWorkingDays($task->estStarted, $task->deadline);
+ $planduration = count($planduration);
+
+ $this->dao->update(TABLE_TASK)
+ ->set('planduration')->eq($planduration)
+ ->where('id')->eq($task->id)
+ ->exec();
+ }
+
+}
+
+public function updateTaskRealDuration($beginDate, $endDate)
+{
+ $updateTaskList = $this->dao->select('id, realStarted, finishedDate')
+ ->from(TABLE_TASK)
+ ->where('realStarted')->between($beginDate, $endDate)
+ ->orWhere("date_format(finishedDate,'%Y-%m-%d')")->between($beginDate, $endDate)
+ ->orWhere("(realStarted < '$beginDate' AND date_format(finishedDate,'%Y-%m-%d') > '$endDate')")
+ ->andWhere('status')->ne('done')
+ ->fetchAll();
+
+ foreach($updateTaskList as $task)
+ {
+ $realDuration = $this->getActualWorkingDays($task->realBegan, date('Y-m-d',strtotime($task->finishedDate)));
+ $realDuration = count($realDuration);
+
+ $this->dao->update(TABLE_TASK)
+ ->set('realDuration')->eq($realDuration)
+ ->where('id')->eq($task->id)
+ ->exec();
+ }
+}
diff --git a/module/holiday/lang/de.php b/module/holiday/lang/de.php
new file mode 100644
index 0000000000..c922267c1c
--- /dev/null
+++ b/module/holiday/lang/de.php
@@ -0,0 +1,27 @@
+holiday)) $lang->holiday = new stdclass();
+$lang->holiday->common = 'Holiday';
+$lang->holiday->browse = 'Browse';
+$lang->holiday->create = 'Create';
+$lang->holiday->edit = 'Edit';
+$lang->holiday->delete = 'Delete';
+
+$lang->holiday->createAction = 'Create Holiday';
+$lang->holiday->editAction = 'Edit Holiday';
+$lang->holiday->deleteAction = 'Delete Holiday';
+
+$lang->holiday->id = 'ID';
+$lang->holiday->name = 'Name';
+$lang->holiday->desc = 'Description';
+$lang->holiday->type = 'Type';
+$lang->holiday->begin = 'Begin';
+$lang->holiday->end = 'End';
+$lang->holiday->all = 'All';
+
+$lang->holiday->holiday = 'Holiday';
+
+$lang->holiday->typeList['holiday'] = 'Holiday';
+$lang->holiday->typeList['working'] = 'Working Day';
+
+$lang->holiday->emptyTip = 'No Holiday';
+$lang->holiday->confirmDelete = 'Confirm removal of holidays?';
diff --git a/module/holiday/lang/en.php b/module/holiday/lang/en.php
new file mode 100644
index 0000000000..c922267c1c
--- /dev/null
+++ b/module/holiday/lang/en.php
@@ -0,0 +1,27 @@
+holiday)) $lang->holiday = new stdclass();
+$lang->holiday->common = 'Holiday';
+$lang->holiday->browse = 'Browse';
+$lang->holiday->create = 'Create';
+$lang->holiday->edit = 'Edit';
+$lang->holiday->delete = 'Delete';
+
+$lang->holiday->createAction = 'Create Holiday';
+$lang->holiday->editAction = 'Edit Holiday';
+$lang->holiday->deleteAction = 'Delete Holiday';
+
+$lang->holiday->id = 'ID';
+$lang->holiday->name = 'Name';
+$lang->holiday->desc = 'Description';
+$lang->holiday->type = 'Type';
+$lang->holiday->begin = 'Begin';
+$lang->holiday->end = 'End';
+$lang->holiday->all = 'All';
+
+$lang->holiday->holiday = 'Holiday';
+
+$lang->holiday->typeList['holiday'] = 'Holiday';
+$lang->holiday->typeList['working'] = 'Working Day';
+
+$lang->holiday->emptyTip = 'No Holiday';
+$lang->holiday->confirmDelete = 'Confirm removal of holidays?';
diff --git a/module/holiday/lang/fr.php b/module/holiday/lang/fr.php
new file mode 100644
index 0000000000..c922267c1c
--- /dev/null
+++ b/module/holiday/lang/fr.php
@@ -0,0 +1,27 @@
+holiday)) $lang->holiday = new stdclass();
+$lang->holiday->common = 'Holiday';
+$lang->holiday->browse = 'Browse';
+$lang->holiday->create = 'Create';
+$lang->holiday->edit = 'Edit';
+$lang->holiday->delete = 'Delete';
+
+$lang->holiday->createAction = 'Create Holiday';
+$lang->holiday->editAction = 'Edit Holiday';
+$lang->holiday->deleteAction = 'Delete Holiday';
+
+$lang->holiday->id = 'ID';
+$lang->holiday->name = 'Name';
+$lang->holiday->desc = 'Description';
+$lang->holiday->type = 'Type';
+$lang->holiday->begin = 'Begin';
+$lang->holiday->end = 'End';
+$lang->holiday->all = 'All';
+
+$lang->holiday->holiday = 'Holiday';
+
+$lang->holiday->typeList['holiday'] = 'Holiday';
+$lang->holiday->typeList['working'] = 'Working Day';
+
+$lang->holiday->emptyTip = 'No Holiday';
+$lang->holiday->confirmDelete = 'Confirm removal of holidays?';
diff --git a/module/holiday/lang/index.html b/module/holiday/lang/index.html
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/module/holiday/lang/vi.php b/module/holiday/lang/vi.php
new file mode 100644
index 0000000000..a1560a635d
--- /dev/null
+++ b/module/holiday/lang/vi.php
@@ -0,0 +1,27 @@
+holiday)) $lang->holiday = new stdclass();
+$lang->holiday->common = 'Ngày lễ';
+$lang->holiday->browse = 'Browse';
+$lang->holiday->create = 'Tạo';
+$lang->holiday->edit = 'Sửa';
+$lang->holiday->delete = 'Xóa';
+
+$lang->holiday->createAction = 'Create Holiday';
+$lang->holiday->editAction = 'Edit Holiday';
+$lang->holiday->deleteAction = 'Delete Holiday';
+
+$lang->holiday->id = 'ID';
+$lang->holiday->name = 'Tên';
+$lang->holiday->desc = 'Mô tả';
+$lang->holiday->type = 'Loại';
+$lang->holiday->begin = 'Bắt đầu';
+$lang->holiday->end = 'Kết thúc';
+$lang->holiday->all = 'All';
+
+$lang->holiday->holiday = 'Ngày lễ';
+
+$lang->holiday->typeList['holiday'] = 'Ngày lễ';
+$lang->holiday->typeList['working'] = 'Ngày làm việc';
+
+$lang->holiday->emptyTip = 'No Holiday';
+$lang->holiday->confirmDelete = 'Confirm removal of holidays?';
diff --git a/module/holiday/lang/zh-cn.php b/module/holiday/lang/zh-cn.php
new file mode 100644
index 0000000000..5f38ebbcda
--- /dev/null
+++ b/module/holiday/lang/zh-cn.php
@@ -0,0 +1,27 @@
+holiday)) $lang->holiday = new stdclass();
+$lang->holiday->common = '节假日';
+$lang->holiday->browse = '浏览';
+$lang->holiday->create = '新建';
+$lang->holiday->edit = '编辑';
+$lang->holiday->delete = '删除';
+
+$lang->holiday->createAction = '创建节假日';
+$lang->holiday->editAction = '编辑节假日';
+$lang->holiday->deleteAction = '删除节假日';
+
+$lang->holiday->id = '编号';
+$lang->holiday->name = '名称';
+$lang->holiday->desc = '描述';
+$lang->holiday->type = '类型';
+$lang->holiday->begin = '开始日期';
+$lang->holiday->end = '结束日期';
+$lang->holiday->all = '所有';
+
+$lang->holiday->holiday = '假期';
+
+$lang->holiday->typeList['holiday'] = '假期';
+$lang->holiday->typeList['working'] = '补班';
+
+$lang->holiday->emptyTip = '暂时没有节假日。';
+$lang->holiday->confirmDelete = '确认删除节假日?';
diff --git a/module/holiday/lang/zh-tw.php b/module/holiday/lang/zh-tw.php
new file mode 100644
index 0000000000..43e0cd41ab
--- /dev/null
+++ b/module/holiday/lang/zh-tw.php
@@ -0,0 +1,27 @@
+holiday)) $lang->holiday = new stdclass();
+$lang->holiday->common = '節假日';
+$lang->holiday->browse = '瀏覽';
+$lang->holiday->create = '新建';
+$lang->holiday->edit = '編輯';
+$lang->holiday->delete = '刪除';
+
+$lang->holiday->createAction = '創建節假日';
+$lang->holiday->editAction = '編輯節假日';
+$lang->holiday->deleteAction = '刪除節假日';
+
+$lang->holiday->id = '編號';
+$lang->holiday->name = '名稱';
+$lang->holiday->desc = '描述';
+$lang->holiday->type = '類型';
+$lang->holiday->begin = '開始日期';
+$lang->holiday->end = '結束日期';
+$lang->holiday->all = '所有';
+
+$lang->holiday->holiday = '假期';
+
+$lang->holiday->typeList['holiday'] = '假期';
+$lang->holiday->typeList['working'] = '補班';
+
+$lang->holiday->emptyTip = '暫時沒有節假日。';
+$lang->holiday->confirmDelete = '確認刪除節假日?';
diff --git a/module/holiday/model.php b/module/holiday/model.php
new file mode 100644
index 0000000000..004dcc41e7
--- /dev/null
+++ b/module/holiday/model.php
@@ -0,0 +1,410 @@
+
+ * @package holiday
+ * @version $Id
+ * @link http://www.zentao.net
+ */
+class holidayModel extends model
+{
+ /**
+ * Get holiday by id.
+ *
+ * @param int $id
+ * @access public
+ * @return object
+ */
+ public function getById($id)
+ {
+ return $this->dao->select('*')->from(TABLE_HOLIDAY)->where('id')->eq($id)->fetch();
+ }
+
+ /**
+ * Get holiday list.
+ *
+ * @param string $year
+ * @param string $type
+ * @access public
+ * @return object
+ */
+ public function getList($year = '', $type = 'all')
+ {
+ return $this->dao->select('*')->from(TABLE_HOLIDAY)
+ ->where('1')
+ ->beginIf(!empty($year))
+ ->andWhere('year', true)->eq($year)
+ ->orWhere('begin')->like("$year-%")
+ ->orWhere('end')->like("$year-%")
+ ->markright(1)
+ ->fi()
+ ->beginIf($type != 'all' && $type)->andWhere('type')->eq($type)->fi()
+ ->fetchAll('id');
+ }
+
+ /**
+ * Get year pairs.
+ *
+ * @access public
+ * @return array
+ */
+ public function getYearPairs()
+ {
+ return $this->dao->select('year,year')->from(TABLE_HOLIDAY)->groupBy('year')->orderBy('year_desc')->fetchPairs();
+ }
+
+ /**
+ * Create a holiday.
+ *
+ * @access public
+ * @return int
+ */
+ public function create()
+ {
+ $holiday = fixer::input('post')->get();
+ $holiday->year = substr($holiday->begin, 0, 4);
+ if(helper::isZeroDate($holiday->year)) return dao::$errors['begin'][] = sprintf($this->lang->error->date, $this->lang->holiday->begin);
+ if(helper::isZeroDate($holiday->end)) return dao::$errors['end'][] = sprintf($this->lang->error->date, $this->lang->holiday->end);
+
+ $this->dao->insert(TABLE_HOLIDAY)->data($holiday)
+ ->autoCheck()
+ ->batchCheck($this->config->holiday->require->create, 'notempty')
+ ->check('end', 'ge', $holiday->begin)
+ ->exec();
+ if(dao::isError()) return false;
+
+ $beginDate = $this->post->begin;
+ $endDate = $this->post->end;
+
+ /* Update project. */
+ $this->updateProgramPlanDuration($beginDate, $endDate);
+ $this->updateProjectRealDuration($beginDate, $endDate);
+
+ /* Update task. */
+ $this->updateTaskPlanDuration($beginDate, $endDate);
+ $this->updateTaskRealDuration($beginDate, $endDate);
+
+ return $this->dao->lastInsertID();
+ }
+
+ /**
+ * Edit holiday.
+ *
+ * @param int $id
+ * @access public
+ * @return bool
+ */
+ public function update($id)
+ {
+ $holiday = fixer::input('post')->get();
+ $holiday->year = substr($holiday->begin, 0, 4);
+ if(helper::isZeroDate($holiday->year)) return dao::$errors['begin'][] = sprintf($this->lang->error->date, $this->lang->holiday->begin);
+ if(helper::isZeroDate($holiday->end)) return dao::$errors['end'][] = sprintf($this->lang->error->date, $this->lang->holiday->end);
+
+ $this->dao->update(TABLE_HOLIDAY)
+ ->data($holiday)
+ ->autoCheck()
+ ->batchCheck($this->config->holiday->require->edit, 'notempty')
+ ->check('end', 'ge', $holiday->begin)
+ ->where('id')->eq($id)
+ ->exec();
+
+ if(!dao::isError())
+ {
+ $beginDate = $this->post->begin;
+ $endDate = $this->post->end;
+
+ /* Update project. */
+ $this->updateProgramPlanDuration($beginDate, $endDate);
+ $this->updateProjectRealDuration($beginDate, $endDate);
+
+ /* Update task. */
+ $this->updateTaskPlanDuration($beginDate, $endDate);
+ $this->updateTaskRealDuration($beginDate, $endDate);
+ }
+ return !dao::isError();
+ }
+
+ /**
+ * Get holidays by begin and end.
+ *
+ * @param string $begin
+ * @param string $end
+ * @access public
+ * @return array
+ */
+ public function getHolidays($begin, $end)
+ {
+ $records = $this->dao->select('*')->from(TABLE_HOLIDAY)
+ ->where('type')->eq('holiday')
+ ->andWhere('begin')->le($end)
+ ->andWhere('end')->ge($begin)
+ ->fetchAll('id');
+
+ $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($begin = '', $end = '')
+ {
+ $records = $this->dao->select('*')->from(TABLE_HOLIDAY)
+ ->where('type')->eq('working')
+ ->andWhere('begin')->le($end)
+ ->andWhere('end')->ge($begin)
+ ->fetchAll('id');
+
+ $workingDays = array();
+ foreach($records as $record)
+ {
+ $dates = $this->getDaysBetween($record->begin, $record->end);
+ $workingDays = array_merge($workingDays, $dates);
+ }
+ return $workingDays;
+ }
+
+ /**
+ * Get actual working days.
+ *
+ * @param string $begin
+ * @param string $end
+ * @access public
+ * @return array
+ */
+ public function getActualWorkingDays($begin, $end)
+ {
+ if(empty($begin) or empty($end) or $begin == '0000-00-00' or $end == '0000-00-00') return array();
+
+ $actualDays = array();
+ $currentDay = $begin;
+
+ $holidays = $this->getHolidays($begin, $end);
+ $workingDays = $this->getWorkingDays($begin, $end);
+ $weekend = isset($this->config->project->weekend) ? $this->config->project->weekend : 2;
+
+ /* When the start date and end date are the same. */
+ if($begin == $end)
+ {
+ if(in_array($begin, $workingDays)) return $actualDays[] = $begin;
+ if(in_array($begin, $holidays)) return $actualDays;
+
+ $w = date('w', strtotime($begin));
+ if($weekend == 2)
+ {
+ if($w == 0 or $w == 6) return $actualDays;
+ }
+ else
+ {
+ if($w == 0) return $actualDays;
+ }
+
+ $actualDays[] = $begin;
+ return $actualDays;
+ }
+
+ for($i = 0; $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($weekend == 2)
+ {
+ if($w == 0 or $w == 6) continue;
+ }
+ else
+ {
+ if($w == 0) continue;
+ }
+ $actualDays[] = $currentDay;
+ }
+
+ return $actualDays;
+ }
+
+ /**
+ * Get diff days.
+ *
+ * @param varchar $begin
+ * @param varchar $end
+ * @access public
+ * @return bool
+ */
+ public function getDaysBetween($begin, $end)
+ {
+ $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;
+ }
+
+ /**
+ * Judge if is holiday.
+ *
+ * @param string $date
+ * @access public
+ * @return bool
+ */
+ public function isHoliday($date)
+ {
+ $record = $this->dao->select('*')->from(TABLE_HOLIDAY)
+ ->where('type')->eq('holiday')
+ ->andWhere('begin')->le($date)
+ ->andWhere('end')->ge($date)
+ ->fetch();
+ return !empty($record);
+ }
+
+ /**
+ * Judge if is working days.
+ *
+ * @param string $date
+ * @access public
+ * @return bool
+ */
+ public function isWorkingDay($date)
+ {
+ $record = $this->dao->select('*')->from(TABLE_HOLIDAY)
+ ->where('type')->eq('working')
+ ->andWhere('begin')->le($date)
+ ->andWhere('end')->ge($date)
+ ->fetch();
+ return !empty($record);
+ }
+
+ /**
+ * Update project plan duration.
+ *
+ * @param string $beginDate
+ * @param string $endDate
+ * @access public
+ * @return void
+ */
+ public function updateProgramPlanDuration($beginDate, $endDate)
+ {
+ $updateProjectList = $this->dao->select('id, begin, end')
+ ->from(TABLE_PROJECT)
+ ->where('begin')->between($beginDate, $endDate)
+ ->orWhere('end')->between($beginDate, $endDate)
+ ->orWhere("(begin < '$beginDate' AND end > '$endDate')")
+ ->andWhere('status')->ne('done')
+ ->fetchAll();
+
+ foreach($updateProjectList as $project)
+ {
+ $realDuration = $this->getActualWorkingDays($project->begin, $project->end);
+ $realDuration = count($realDuration);
+
+ $this->dao->update(TABLE_PROJECT)->set('planDuration')->eq($realDuration)->where('id')->eq($project->id)->exec();
+ }
+ }
+
+ /**
+ * Update project real duration.
+ *
+ * @param string $beginDate
+ * @param string $endDate
+ * @access public
+ * @return void
+ */
+ public function updateProjectRealDuration($beginDate, $endDate)
+ {
+ $updateProjectList = $this->dao->select('id, realBegan, realEnd')
+ ->from(TABLE_PROJECT)
+ ->where('realBegan')->between($beginDate, $endDate)
+ ->orWhere('realEnd')->between($beginDate, $endDate)
+ ->orWhere("(realBegan < '$beginDate' AND realEnd > '$endDate')")
+ ->andWhere('status')->ne('done')
+ ->fetchAll();
+
+ foreach($updateProjectList as $project)
+ {
+ $realDuration = $this->getActualWorkingDays($project->realBegan, $project->realEnd);
+ $realDuration = count($realDuration);
+
+ $this->dao->update(TABLE_PROJECT)->set('realDuration')->eq($realDuration)->where('id')->eq($project->id)->exec();
+ }
+ }
+
+ /**
+ * Update task plan duration.
+ *
+ * @param string $beginDate
+ * @param string $endDate
+ * @access public
+ * @return void
+ */
+ public function updateTaskPlanDuration($beginDate, $endDate)
+ {
+ $updateTaskList = $this->dao->select('id, estStarted, deadline')
+ ->from(TABLE_TASK)
+ ->where('estStarted')->between($beginDate, $endDate)
+ ->orWhere('deadline')->between($beginDate, $endDate)
+ ->orWhere("(estStarted < '$beginDate' AND deadline > '$endDate')")
+ ->andWhere('status') ->ne('done')
+ ->fetchAll();
+
+ foreach($updateTaskList as $task)
+ {
+ $planduration = $this->getActualWorkingDays($task->estStarted, $task->deadline);
+ $planduration = count($planduration);
+
+ $this->dao->update(TABLE_TASK)->set('planduration')->eq($planduration)->where('id')->eq($task->id)->exec();
+ }
+ }
+
+ /**
+ * Update task real duration.
+ *
+ * @param string $beginDate
+ * @param string $endDate
+ * @access public
+ * @return void
+ */
+ public function updateTaskRealDuration($beginDate, $endDate)
+ {
+ $updateTaskList = $this->dao->select('id, realStarted, finishedDate')
+ ->from(TABLE_TASK)
+ ->where('realStarted')->between($beginDate, $endDate)
+ ->orWhere("date_format(finishedDate,'%Y-%m-%d')")->between($beginDate, $endDate)
+ ->orWhere("(realStarted < '$beginDate' AND date_format(finishedDate,'%Y-%m-%d') > '$endDate')")
+ ->andWhere('status')->ne('done')
+ ->fetchAll();
+
+ foreach($updateTaskList as $task)
+ {
+ $realDuration = $this->getActualWorkingDays($task->realStarted, date('Y-m-d',strtotime($task->finishedDate)));
+ $realDuration = count($realDuration);
+
+ $this->dao->update(TABLE_TASK)->set('realDuration')->eq($realDuration)->where('id')->eq($task->id)->exec();
+ }
+ }
+}
\ No newline at end of file
diff --git a/module/holiday/view/browse.html.php b/module/holiday/view/browse.html.php
new file mode 100644
index 0000000000..9f59722738
--- /dev/null
+++ b/module/holiday/view/browse.html.php
@@ -0,0 +1,72 @@
+
+ * @package holiday
+ * @version $Id$
+ * @link https://www.zentao.net
+ */
+?>
+
+
+
+ createLink('holiday', 'browse'), '' . $lang->holiday->all . '', '', "class='btn btn-link btn-active-text'");
+ ?>
+
+
+ " . $lang->holiday->create, '', "class='btn btn-primary iframe'", '', true)?>
+
+
+
+
+
+
+
+
+
+ | holiday->name;?> |
+ holiday->holiday;?> |
+ holiday->type;?> |
+ holiday->desc;?> |
+ actions;?> |
+
+
+
+
+ | name;?> |
+ begin, DT_DATE1) . ' ~ ' . formatTime($holiday->end, DT_DATE1);?> |
+ holiday->typeList, $holiday->type);?> |
+ desc;?> |
+
+ id", $holiday, 'list', '', '', 'iframe', 'yes');?>
+ id", $holiday, 'list', '', 'hiddenwin');?>
+ |
+
+
+
+
+
+
+ holiday->emptyTip;?>
+
+
+
+
+
+
diff --git a/module/holiday/view/create.html.php b/module/holiday/view/create.html.php
new file mode 100644
index 0000000000..f764d14933
--- /dev/null
+++ b/module/holiday/view/create.html.php
@@ -0,0 +1,54 @@
+
+ * @package holiday
+ * @version $Id$
+ * @link https://www.zentao.net
+ */
+?>
+
+
+
+
+
+
+ holiday->create;?>
+
+
+
+
+
+
diff --git a/module/holiday/view/edit.html.php b/module/holiday/view/edit.html.php
new file mode 100644
index 0000000000..bcb7e739f6
--- /dev/null
+++ b/module/holiday/view/edit.html.php
@@ -0,0 +1,54 @@
+
+ * @package holiday
+ * @version $Id$
+ * @link https://www.zentao.net
+ */
+?>
+
+
+
+
+
+
+ holiday->edit;?>
+
+
+
+
+
+
diff --git a/module/holiday/view/index.html b/module/holiday/view/index.html
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/module/weekly/control.php b/module/weekly/control.php
new file mode 100644
index 0000000000..2528af2a63
--- /dev/null
+++ b/module/weekly/control.php
@@ -0,0 +1,92 @@
+
+ * @package weekly
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+class weekly extends control
+{
+ /**
+ * The construct function, load users.
+ *
+ * @access public
+ * @return void
+ */
+
+ public function __construct()
+ {
+ parent::__construct();
+ $this->view->users = $this->loadModel('user')->getPairs('noletter');
+ }
+
+ /**
+ * Common action.
+ *
+ * @param int $projectID
+ * @access public
+ * @return void
+ */
+ public function commonAction($projectID = 0)
+ {
+ $this->loadModel('project')->setMenu($projectID);
+ }
+
+ /**
+ * Index
+ *
+ * @param int $projectID
+ * @param string $date
+ * @access public
+ * @return void
+ */
+ public function index($projectID = 0, $date = '')
+ {
+ $this->commonAction($projectID);
+ if(!$date) $date = helper::today();
+ $date = date('Y-m-d', strtotime($date));
+
+ $this->view->title = $this->lang->weekly->common;
+
+ $this->view->pv = $this->weekly->getPV($projectID, $date);
+ $this->view->ev = $this->weekly->getEV($projectID, $date);
+ $this->view->ac = $this->weekly->getAC($projectID, $date);
+ $this->view->sv = $this->weekly->getSV($this->view->ev, $this->view->pv);
+ $this->view->cv = $this->weekly->getCV($this->view->ev, $this->view->ac);
+
+ $this->view->project = $this->loadModel('project')->getByID($projectID);
+ $this->view->weekSN = $this->weekly->getWeekSN($this->view->project->begin, $date);
+ $this->view->monday = $this->weekly->getThisMonday($date);
+ $this->view->lastDay = $this->weekly->getLastDay($date);
+ $this->view->staff = $this->weekly->getStaff($projectID, $date);
+ $this->view->finished = $this->weekly->getFinished($projectID, $date);
+ $this->view->postponed = $this->weekly->getPostponed($projectID, $date);
+ $this->view->nextWeek = $this->weekly->getTasksOfNextWeek($projectID, $date);
+ $this->view->workload = $this->weekly->getWorkloadByType($projectID, $date);
+ $this->weekly->save($projectID, $date);
+
+ $this->lang->modulePageNav = $this->weekly->getPageNav($this->view->project, $date);
+ $this->display();
+ }
+
+ /**
+ * ComputeWeekly
+ *
+ * @access public
+ * @return void
+ */
+ public function computeWeekly()
+ {
+ $projects = $this->dao->select('id, name')->from(TABLE_PROJECT)
+ ->where('deleted')->eq(0)
+ ->andWhere('type')->eq('project')
+ ->fetchPairs();
+ $date = helper::today();
+
+ foreach($projects as $projectID => $project) $this->weekly->save($projectID, $date);
+ }
+}
diff --git a/module/weekly/lang/de.php b/module/weekly/lang/de.php
new file mode 100644
index 0000000000..f665865f2e
--- /dev/null
+++ b/module/weekly/lang/de.php
@@ -0,0 +1,39 @@
+
+ * @package weekly
+ * @version $Id
+ * @link http://www.zentao.net
+ */
+$lang->weekly->common = 'Project Weekly';
+$lang->weekly->index = 'Weekly Overview';
+$lang->weekly->progress = 'Progress';
+$lang->weekly->workload = 'Workload';
+$lang->weekly->total = 'Total';
+
+$lang->weekly->reportTtitle = 'Project: % s Weekly (Week % s)';
+$lang->weekly->summary = 'Project Progress';
+$lang->weekly->finished = 'Work finished this week (100% completed work)';
+$lang->weekly->postponed = 'Work unfinished this week';
+$lang->weekly->nextWeek = 'Work planned for next week';
+$lang->weekly->workloadByType = 'Workload Summary';
+
+$lang->weekly->term = 'Reporting Cycle';
+$lang->weekly->project = 'Project Name';
+$lang->weekly->master = 'Project Manager ';
+$lang->weekly->staff = 'The number of men in this week';
+
+$lang->weekly->weekDesc = 'Week % s (% s ~% s)';
+$lang->weekly->progress = 'Progress of the project';
+$lang->weekly->analysisResult = 'Analysis';
+$lang->weekly->cost = 'Project Cost';
+
+$lang->weekly->pv = 'Planned Value(PV)';
+$lang->weekly->ev = 'Earned Value(EV)';
+$lang->weekly->ac = 'Actual Cost(AC)';
+$lang->weekly->sv = 'Schedule Variance(SV%)';
+$lang->weekly->cv = 'Cost Variance(CV%)';
diff --git a/module/weekly/lang/en.php b/module/weekly/lang/en.php
new file mode 100644
index 0000000000..f665865f2e
--- /dev/null
+++ b/module/weekly/lang/en.php
@@ -0,0 +1,39 @@
+
+ * @package weekly
+ * @version $Id
+ * @link http://www.zentao.net
+ */
+$lang->weekly->common = 'Project Weekly';
+$lang->weekly->index = 'Weekly Overview';
+$lang->weekly->progress = 'Progress';
+$lang->weekly->workload = 'Workload';
+$lang->weekly->total = 'Total';
+
+$lang->weekly->reportTtitle = 'Project: % s Weekly (Week % s)';
+$lang->weekly->summary = 'Project Progress';
+$lang->weekly->finished = 'Work finished this week (100% completed work)';
+$lang->weekly->postponed = 'Work unfinished this week';
+$lang->weekly->nextWeek = 'Work planned for next week';
+$lang->weekly->workloadByType = 'Workload Summary';
+
+$lang->weekly->term = 'Reporting Cycle';
+$lang->weekly->project = 'Project Name';
+$lang->weekly->master = 'Project Manager ';
+$lang->weekly->staff = 'The number of men in this week';
+
+$lang->weekly->weekDesc = 'Week % s (% s ~% s)';
+$lang->weekly->progress = 'Progress of the project';
+$lang->weekly->analysisResult = 'Analysis';
+$lang->weekly->cost = 'Project Cost';
+
+$lang->weekly->pv = 'Planned Value(PV)';
+$lang->weekly->ev = 'Earned Value(EV)';
+$lang->weekly->ac = 'Actual Cost(AC)';
+$lang->weekly->sv = 'Schedule Variance(SV%)';
+$lang->weekly->cv = 'Cost Variance(CV%)';
diff --git a/module/weekly/lang/fr.php b/module/weekly/lang/fr.php
new file mode 100644
index 0000000000..f665865f2e
--- /dev/null
+++ b/module/weekly/lang/fr.php
@@ -0,0 +1,39 @@
+
+ * @package weekly
+ * @version $Id
+ * @link http://www.zentao.net
+ */
+$lang->weekly->common = 'Project Weekly';
+$lang->weekly->index = 'Weekly Overview';
+$lang->weekly->progress = 'Progress';
+$lang->weekly->workload = 'Workload';
+$lang->weekly->total = 'Total';
+
+$lang->weekly->reportTtitle = 'Project: % s Weekly (Week % s)';
+$lang->weekly->summary = 'Project Progress';
+$lang->weekly->finished = 'Work finished this week (100% completed work)';
+$lang->weekly->postponed = 'Work unfinished this week';
+$lang->weekly->nextWeek = 'Work planned for next week';
+$lang->weekly->workloadByType = 'Workload Summary';
+
+$lang->weekly->term = 'Reporting Cycle';
+$lang->weekly->project = 'Project Name';
+$lang->weekly->master = 'Project Manager ';
+$lang->weekly->staff = 'The number of men in this week';
+
+$lang->weekly->weekDesc = 'Week % s (% s ~% s)';
+$lang->weekly->progress = 'Progress of the project';
+$lang->weekly->analysisResult = 'Analysis';
+$lang->weekly->cost = 'Project Cost';
+
+$lang->weekly->pv = 'Planned Value(PV)';
+$lang->weekly->ev = 'Earned Value(EV)';
+$lang->weekly->ac = 'Actual Cost(AC)';
+$lang->weekly->sv = 'Schedule Variance(SV%)';
+$lang->weekly->cv = 'Cost Variance(CV%)';
diff --git a/module/weekly/lang/vi.php b/module/weekly/lang/vi.php
new file mode 100644
index 0000000000..f665865f2e
--- /dev/null
+++ b/module/weekly/lang/vi.php
@@ -0,0 +1,39 @@
+
+ * @package weekly
+ * @version $Id
+ * @link http://www.zentao.net
+ */
+$lang->weekly->common = 'Project Weekly';
+$lang->weekly->index = 'Weekly Overview';
+$lang->weekly->progress = 'Progress';
+$lang->weekly->workload = 'Workload';
+$lang->weekly->total = 'Total';
+
+$lang->weekly->reportTtitle = 'Project: % s Weekly (Week % s)';
+$lang->weekly->summary = 'Project Progress';
+$lang->weekly->finished = 'Work finished this week (100% completed work)';
+$lang->weekly->postponed = 'Work unfinished this week';
+$lang->weekly->nextWeek = 'Work planned for next week';
+$lang->weekly->workloadByType = 'Workload Summary';
+
+$lang->weekly->term = 'Reporting Cycle';
+$lang->weekly->project = 'Project Name';
+$lang->weekly->master = 'Project Manager ';
+$lang->weekly->staff = 'The number of men in this week';
+
+$lang->weekly->weekDesc = 'Week % s (% s ~% s)';
+$lang->weekly->progress = 'Progress of the project';
+$lang->weekly->analysisResult = 'Analysis';
+$lang->weekly->cost = 'Project Cost';
+
+$lang->weekly->pv = 'Planned Value(PV)';
+$lang->weekly->ev = 'Earned Value(EV)';
+$lang->weekly->ac = 'Actual Cost(AC)';
+$lang->weekly->sv = 'Schedule Variance(SV%)';
+$lang->weekly->cv = 'Cost Variance(CV%)';
diff --git a/module/weekly/lang/zh-cn.php b/module/weekly/lang/zh-cn.php
new file mode 100644
index 0000000000..d0fc86ef86
--- /dev/null
+++ b/module/weekly/lang/zh-cn.php
@@ -0,0 +1,39 @@
+
+ * @package weekly
+ * @version $Id
+ * @link http://www.zentao.net
+ */
+$lang->weekly->common = '项目周报';
+$lang->weekly->index = '周报总览';
+$lang->weekly->progress = '完成百分比';
+$lang->weekly->workload = '工作量';
+$lang->weekly->total = '合计';
+
+$lang->weekly->reportTtitle = '项目: %s 周报(第 %s 周)';
+$lang->weekly->summary = '项目进展状况';
+$lang->weekly->finished = '本周工作完成情况(100%完成的工作)';
+$lang->weekly->postponed = '本周未完成工作';
+$lang->weekly->nextWeek = '下周工作计划';
+$lang->weekly->workloadByType = '工作量统计';
+
+$lang->weekly->term = '报告周期';
+$lang->weekly->project = '项目名称';
+$lang->weekly->master = '项目经理 ';
+$lang->weekly->staff = '本周投入人数';
+
+$lang->weekly->weekDesc = '第 %s 周( %s ~ %s)';
+$lang->weekly->progress = '项目当前进展状况';
+$lang->weekly->analysisResult = '分析结果';
+$lang->weekly->cost = '项目成本';
+
+$lang->weekly->pv = '计划完成的工作(PV)';
+$lang->weekly->ev = '实际完成的工作(EV)';
+$lang->weekly->ac = '实际花费的成本(AC)';
+$lang->weekly->sv = '进度偏差率(SV%)';
+$lang->weekly->cv = '成本偏差率(CV%)';
diff --git a/module/weekly/lang/zh-tw.php b/module/weekly/lang/zh-tw.php
new file mode 100644
index 0000000000..20f44ed51f
--- /dev/null
+++ b/module/weekly/lang/zh-tw.php
@@ -0,0 +1,39 @@
+
+ * @package weekly
+ * @version $Id
+ * @link http://www.zentao.net
+ */
+$lang->weekly->common = '項目周報';
+$lang->weekly->index = '周報總覽';
+$lang->weekly->progress = '完成百分比';
+$lang->weekly->workload = '工作量';
+$lang->weekly->total = '合計';
+
+$lang->weekly->reportTtitle = '項目: %s 周報(第 %s 周)';
+$lang->weekly->summary = '項目進展狀況';
+$lang->weekly->finished = '本週工作完成情況(100%完成的工作)';
+$lang->weekly->postponed = '本週未完成工作';
+$lang->weekly->nextWeek = '下周工作計劃';
+$lang->weekly->workloadByType = '工作量統計';
+
+$lang->weekly->term = '報告周期';
+$lang->weekly->project = '項目名稱';
+$lang->weekly->master = '項目經理 ';
+$lang->weekly->staff = '本週投入人數';
+
+$lang->weekly->weekDesc = '第 %s 周( %s ~ %s)';
+$lang->weekly->progress = '項目當前進展狀況';
+$lang->weekly->analysisResult = '分析結果';
+$lang->weekly->cost = '項目成本';
+
+$lang->weekly->pv = '計劃完成的工作(PV)';
+$lang->weekly->ev = '實際完成的工作(EV)';
+$lang->weekly->ac = '實際花費的成本(AC)';
+$lang->weekly->sv = '進度偏差率(SV%)';
+$lang->weekly->cv = '成本偏差率(CV%)';
diff --git a/module/weekly/model.php b/module/weekly/model.php
new file mode 100644
index 0000000000..42a2ddb47e
--- /dev/null
+++ b/module/weekly/model.php
@@ -0,0 +1,511 @@
+
+ * @package weekly
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+class weeklyModel extends model
+{
+ /**
+ * GetPageNav
+ *
+ * @param int $project
+ * @param int $date
+ * @access public
+ * @return string
+ */
+ public function getPageNav($project, $date)
+ {
+ $date = date('Ymd', strtotime($this->getThisMonday($date)));
+ $begin = $project->begin;
+ $weeks = $this->getWeekPairs($begin);
+ $current = zget($weeks, $date, '');
+ $selectHtml = "";
+ $selectHtml .= html::a('###', $this->lang->weekly->common . $this->lang->colon . $project->name, '', "class='btn'");
+ $selectHtml .= '
';
+
+ $selectHtml .= "';
+ return $selectHtml;
+
+ }
+
+ /**
+ * GetWeekPairs
+ *
+ * @param int $begin
+ * @access public
+ * @return array
+ */
+ public function getWeekPairs($begin)
+ {
+ $sn = $this->getWeekSN($begin, date('Y-m-d'));
+ $weeks = array();
+ for($i = 0; $i <= $sn; $i++)
+ {
+ $monday = $this->getThisMonday($begin);
+ $sunday = $this->getThisSunday($begin);
+ $begin = date('Y-m-d', strtotime("$begin +7 days"));
+ $key = date('Ymd', strtotime($monday));
+ $weeks[$key] = sprintf($this->lang->weekly->weekDesc, $i + 1, $monday, $sunday);
+ }
+ krsort($weeks);
+ return $weeks;
+ }
+
+ /**
+ * GetFromDB
+ *
+ * @param int $project
+ * @param int $date
+ * @access public
+ * @return object
+ */
+ public function getFromDB($project, $date)
+ {
+ $monday = $this->getThisMonday($date);
+ return $this->dao->select('*')
+ ->from(TABLE_WEEKLYREPORT)
+ ->where('weekStart')->eq($monday)
+ ->andWhere('project')->eq($project)
+ ->fetch();
+ }
+
+ /**
+ * Save data.
+ *
+ * @param int $project
+ * @param int $date
+ * @access public
+ * @return void
+ */
+ public function save($project, $date)
+ {
+ $report = new stdclass;
+ $report->pv = $this->getPV($project, $date);
+ $report->ev = $this->getEV($project, $date);
+ $report->ac = $this->getAC($project, $date);
+ $report->sv = $this->getSV($report->ev, $report->pv);
+ $report->cv = $this->getCV($report->ev, $report->ac);
+ $report->project = $project;
+ $report->weekStart = $this->getThisMonday($date);
+ $report->staff = $this->getStaff($project);
+ $report->workload = json_encode($this->getWorkloadByType($project, $date));
+ $this->dao->replace(TABLE_WEEKLYREPORT)->data($report)->exec();
+ }
+
+ /**
+ * GetWeekSN
+ *
+ * @param int $begin
+ * @param int $date
+ * @access public
+ * @return int
+ */
+ public function getWeekSN($begin, $date)
+ {
+ return ceil((strtotime($date) - strtotime($begin)) / 7 / 86400);
+ }
+
+ /**
+ * Get monday for a date.
+ *
+ * @param int $date
+ * @access public
+ * @return date
+ */
+ public function getThisMonday($date)
+ {
+ $day = date('w', strtotime($date));
+ if($day == 0) $day = 7;
+ $days = $day - 1;
+ return date('Y-m-d', strtotime("$date - $days days"));
+ }
+
+ /**
+ * GetThisSunday
+ *
+ * @param int $date
+ * @access public
+ * @return date
+ */
+ public function getThisSunday($date)
+ {
+ $monday = $this->getThisMonday($date);
+ return date('Y-m-d', strtotime("$monday +6 days"));
+ }
+
+ /**
+ * GetLastDay
+ *
+ * @param int $date
+ * @access public
+ * @return string
+ */
+ public function getLastDay($date)
+ {
+ $this->loadModel('project');
+ $weekend = zget($this->config->project, 'weekend', 2);
+ $monday = $this->getThisMonday($date);
+ $sunday = $this->getThisSunday($date);
+ $workdays = $this->loadModel('holiday')->getActualWorkingDays($monday, $sunday);
+ return end($workdays);
+ }
+
+ /**
+ * GetStaff
+ *
+ * @param int $project
+ * @param string $date
+ * @access public
+ * @return array
+ */
+ public function getStaff($project, $date = '')
+ {
+ if(!$date) $date = date('Y-m-d');
+ $monday = $this->getThisMonday($date);
+ $sunday = $this->getThisSunday($date);
+ $executions = $this->loadModel('execution')->getList($project, 'all', 'all', 0, 0, 0);
+ $executionIdList = array_keys($executions);
+ return $this->dao->select('count(distinct account) as count')
+ ->from(TABLE_EFFORT)
+ ->where('objectType')->eq('task')
+ ->andWhere('execution')->in($executionIdList)
+ ->andWhere('date')->ge($monday)
+ ->andWhere('date')->le($sunday)
+ ->fetch('count');
+ }
+
+ /**
+ * GetFinished
+ *
+ * @param int $project
+ * @param string $date
+ * @param int $pager
+ * @access public
+ * @return void
+ */
+ public function getFinished($project, $date = '', $pager = null)
+ {
+ if(!$date) $date = date('Y-m-d');
+ $monday = $this->getThisMonday($date);
+ $sunday = $this->getThisSunday($date);
+
+ $executions = $this->loadModel('execution')->getList($project, 'all', $status = 'all', $limit = 0, $productID = 0, $branch = 0);
+ $executionIdList = array_keys($executions);
+
+ $tasks = $this->dao->select('*')
+ ->from(TABLE_TASK)
+ ->where('execution')->in($executionIdList)
+ ->andWhere("(status='done' or closedReason= 'done')")
+ ->andWhere('finishedDate')->ge($monday)
+ ->andWhere('finishedDate')->le($sunday)
+ ->fetchAll();
+ return $this->loadModel('task')->processTasks($tasks);
+ }
+
+ /**
+ * GetPostponed
+ *
+ * @param int $project
+ * @param string $date
+ * @access public
+ * @return void
+ */
+ public function getPostponed($project, $date = '')
+ {
+ if(!$date) $date = date('Y-m-d');
+ $monday = $this->getThisMonday($date);
+ $sunday = $this->getThisSunday($date);
+ $nextMonday = date('Y-m-d', strtotime("$sunday +1 days"));
+
+ $executions = $this->loadModel('execution')->getList($project, 'all', $status = 'all', $limit = 0, $productID = 0, $branch = 0);
+ $executionIdList = array_keys($executions);
+ $unFinished = $this->dao->select('*')
+ ->from(TABLE_TASK)
+ ->where('execution')->in($executionIdList)
+ ->andWhere('status')->in('wait,doing,pause')
+ ->andWhere('deadline')->ge($monday)
+ ->andWhere('deadline')->le($sunday)
+ ->fetchAll('id');
+
+ $postponed = $this->dao->select('*')
+ ->from(TABLE_TASK)
+ ->where('execution')->in($executionIdList)
+ ->andWhere('finishedDate')->gt($nextMonday)
+ ->andWhere('deadline')->ge($monday)
+ ->andWhere('deadline')->lt($nextMonday)
+ ->fetchAll('id');
+
+ $tasks = array_merge($unFinished, $postponed);
+ return $this->loadModel('task')->processTasks($tasks);
+ }
+
+ /**
+ * GetTasksOfNextWeek
+ *
+ * @param int $project
+ * @param string $date
+ * @access public
+ * @return void
+ */
+ public function getTasksOfNextWeek($project, $date = '')
+ {
+ if(!$date) $date = date('Y-m-d');
+ $sunday = $this->getThisSunday($date);
+ $nextMonday = date('Y-m-d', strtotime("$sunday +1 days"));
+ $sencondMondy = date('Y-m-d', strtotime("$sunday +8 days"));
+
+ $executions = $this->loadModel('execution')->getList($project, 'all', $status = 'all', $limit = 0, $productID = 0, $branch = 0);
+ $executionIdList = array_keys($executions);
+
+ $tasks = $this->dao->select('*')
+ ->from(TABLE_TASK)
+ ->where('execution')->in($executionIdList)
+ ->andWhere("((deadline > '$nextMonday' and deadline < '$sencondMondy') or (estStarted > '$nextMonday' and estStarted < '$sencondMondy'))")
+ ->fetchAll('id');
+
+ return $this->loadModel('task')->processTasks($tasks);
+ }
+
+ /**
+ * GetWorkloadByType
+ *
+ * @param int $project
+ * @param string $date
+ * @access public
+ * @return object
+ */
+ public function getWorkloadByType($project, $date = '')
+ {
+ if(!$date) $date = date('Y-m-d');
+
+ $sunday = $this->getThisSunday($date);
+ $nextMonday = date('Y-m-d', strtotime("$sunday +1 days"));
+ $sencondMondy = date('Y-m-d', strtotime("$sunday +8 days"));
+
+ $executions = $this->loadModel('execution')->getList($project, 'all', $status = 'all', $limit = 0, $productID = 0, $branch = 0);
+ $executionIdList = array_keys($executions);
+
+ return $this->dao->select('type, sum(cast(estimate as decimal(10,2))) as workload')
+ ->from(TABLE_TASK)
+ ->where('execution')->in($executionIdList)
+ ->groupBy('type')
+ ->fetchPairs();
+ }
+
+ /**
+ * GetPlanedTaskByWeek
+ *
+ * @param int $project
+ * @param string $date
+ * @access public
+ * @return array
+ */
+ public function getPlanedTaskByWeek($project, $date = '')
+ {
+ if(!$date) $date = date('Y-m-d');
+ $monday = $this->getThisMonday($date);
+ $nextMonday = date('Y-m-d', strtotime("$monday +7 days"));
+
+ $executions = $this->loadModel('execution')->getList($status = 'all', $limit = 0, $productID = 0, $branch = 0, $project);
+ $executionIdList = array_keys($executions);
+
+ return $this->dao->select('*')
+ ->from(TABLE_TASK)
+ ->where('execution')->in($executionIdList)
+ ->andWhere('deadline')->ge($monday)
+ ->fetchAll('id');
+ }
+
+ /**
+ * GetPV
+ *
+ * @param int $project
+ * @param string $date
+ * @access public
+ * @return int
+ */
+ public function getPV($projectID, $date = '')
+ {
+ $report = $this->getFromDB($projectID, $date);
+ if(!empty($report)) return $report->pv;
+
+ if(!$date) $date = date('Y-m-d');
+ $monday = $this->getThisMonday($date);
+ $sunday = $this->getThisSunday($date);
+ $lastDay = $this->getLastDay($date);
+ $nextMonday = date('Y-m-d', strtotime("$sunday +1 days"));
+ $workdays = $this->loadModel('holiday')->getActualWorkingDays($monday, $sunday);
+
+ $executions = $this->loadModel('execution')->getList($projectID);
+ $executionIdList = array_keys($executions);
+
+ $tasks = $this->dao->select('*')->from(TABLE_TASK)
+ ->where('execution')->in($executionIdList)
+ ->andWhere("(estStarted < '$nextMonday' or estStarted='0000-00-00')")
+ ->fetchAll('id');
+
+ $PV = 0;
+ foreach($tasks as $task)
+ {
+ if($task->estStarted == '0000-00-00') $task->estStarted = date('Y-m-d', strtotime($task->openedDate));
+ if($task->deadline < $nextMonday)
+ {
+ $PV += $task->estimate;
+ continue;
+ }
+
+ $fullDays = $this->loadModel('holiday')->getActualWorkingDays($task->estStarted, $task->deadline);
+ $passedDays = $this->loadModel('holiday')->getActualWorkingDays($task->estStarted, $sunday);
+
+ if(empty($fullDays) or empty($passedDays) or empty($task->estimate)) continue;
+ $PV += count($passedDays) * $task->estimate / count($fullDays);
+ }
+
+ return round($PV, 2);
+ }
+
+ /**
+ * Get EV data.
+ *
+ * @param int $projectID
+ * @param string $date
+ * @access public
+ * @return int
+ */
+ public function getEV($projectID, $date = '')
+ {
+ $report = $this->getFromDB($projectID, $date);
+ if(!empty($report)) return $report->ev;
+
+ $executions = $this->loadModel('execution')->getList($projectID);
+ $executionIdList = array_keys($executions);
+
+ if(!$date) $date = date('Y-m-d');
+ $monday = $this->getThisMonday($date);
+ $sunday = $this->getThisSunday($date);
+ $lastDay = $this->getLastDay($date);
+ $nextMonday = date('Y-m-d', strtotime("$sunday +1 days"));
+
+ $tasks = $this->dao->select('*')
+ ->from(TABLE_TASK)
+ ->where('execution')->in($executionIdList)
+ ->andWhere('consumed')->gt(0)
+ ->andWhere('status')->ne('cancel')
+ ->fetchAll('id');
+
+ $EV = 0;
+ foreach($tasks as $task)
+ {
+ if($task->status == 'done' or $task->closedReason == 'done')
+ {
+ $EV += $task->estimate;
+ }
+ else
+ {
+ $task->progress = round($task->consumed / ($task->consumed + $task->left), 2) * 100;
+ $EV += $task->estimate * $task->progress / 100;
+ }
+ }
+ return round($EV, 2);
+ }
+
+ /**
+ * Get AC data.
+ *
+ * @param int $project
+ * @param string $date
+ * @access public
+ * @return int
+ */
+ public function getAC($project, $date = '')
+ {
+ $report = $this->getFromDB($project, $date);
+ if(!empty($report)) return $report->ac;
+
+ if(!$date) $date = date('Y-m-d');
+
+ $monday = $this->getThisMonday($date);
+ $nextMonday = date('Y-m-d', strtotime("$monday +7 days"));
+ $executions = $this->loadModel('execution')->getList($project, 'all', 'all', 0, 0, 0);
+ $executionIdList = array_keys($executions);
+
+ $AC = $this->dao->select('sum(consumed) as consumed')
+ ->from(TABLE_EFFORT)
+ ->where('objectType')->eq('task')
+ ->andWhere('execution')->in($executionIdList)
+ ->andWhere('date')->ge($monday)
+ ->andWhere('date')->lt($nextMonday)
+ ->fetch('consumed');
+
+ return round($AC, 2);
+ }
+
+ /**
+ * Get SV data.
+ *
+ * @param int $ev
+ * @param int $pv
+ * @access public
+ * @return int
+ */
+ public function getSV($ev, $pv)
+ {
+ if($pv == 0) return 0;
+ $sv = -1 * (1- ($ev / $pv));
+ return number_format($sv * 100, 2);
+ }
+
+ /**
+ * GetCV
+ *
+ * @param int $ev
+ * @param int $ac
+ * @access public
+ * @return int
+ */
+ public function getCV($ev, $ac)
+ {
+ if($ac == 0) return 0;
+ $cv = -1 * (1 - ($ev / $ac));
+ return number_format($cv * 100, 2);
+ }
+
+ /**
+ * GetTips
+ *
+ * @param string $type
+ * @param int $data
+ * @access public
+ * @return string
+ */
+ public function getTips($type = 'progress', $data = 0)
+ {
+ $this->app->loadConfig('custom');
+ if($type == 'progress') $tipsConfig = isset($this->config->custom->SV->progressTip) ? $this->config->custom->SV->progressTip : '';
+ if($type == 'cost') $tipsConfig = isset($this->config->custom->CV->costTip) ? $this->config->custom->CV->costTip : '';
+
+ if(empty($tipsConfig)) return '';
+
+ $tipsConfig = json_decode($tipsConfig);
+ foreach($tipsConfig as $tipConfig)
+ {
+ if($tipConfig->min <= $data and $tipConfig->max >= $data) return $tipConfig->tip;
+ }
+
+ return '';
+ }
+}
diff --git a/module/weekly/view/index.html.php b/module/weekly/view/index.html.php
new file mode 100644
index 0000000000..8158c426a0
--- /dev/null
+++ b/module/weekly/view/index.html.php
@@ -0,0 +1,186 @@
+
+ * @package ZenTaoPMS
+ * @version $Id: index.html.php 5094 2013-07-10 08:46:15Z chencongzhi520@gmail.com $
+ */
+?>
+
+
+
+
+
+
+ | weekly->term;?> |
+ |
+ weekly->master;?> |
+ PM, '');?> |
+
+
+ | weekly->project;?> |
+ name;?> |
+ weekly->staff;?> |
+ |
+
+
+
weekly->summary;?>
+
+
+ | weekly->progress;?> |
+ |
+ weekly->analysisResult;?> |
+ |
+
+
+ | weekly->pv;?> |
+ |
+ weekly->progress;?> |
+ weekly->getTips('progress', $sv);?> |
+
+
+ | weekly->ev;?> |
+ |
+
+
+ | weekly->ac;?> |
+ |
+
+
+ | weekly->sv;?> |
+ |
+
+
+ | weekly->cv;?> |
+ |
+ weekly->cost;?> |
+ config->custom, 'cost', 1);?>
+ |
+
+
+
weekly->finished;?>
+
+
+
+ | idAB;?> |
+ task->name;?> |
+ task->estStarted;?> |
+ task->deadline;?> |
+ task->realStarted;?> |
+ task->finishedBy;?> |
+
+
+
+
+
+ |
+ id);?>
+ |
+
+ createLink('task', 'view', 'id=' . $task->id), $task->name); ?>
+ |
+ estStarted;?> |
+ deadline;?> |
+ realStarted)) echo substr($task->realStarted, 0, 11);?> |
+ finishedBy);?> |
+
+
+
+
+
+
weekly->postponed;?>
+
+
+
+ | idAB;?> |
+ task->name;?> |
+ task->assignedTo;?> |
+ task->estStarted;?> |
+ task->deadline;?> |
+ task->realStarted;?> |
+ task->progress;?> |
+
+
+
+
+
+ |
+ id);?>
+ |
+
+ createLink('task', 'view', 'id=' . $task->id), $task->name); ?>
+ |
+ assignedTo);?> |
+ estStarted;?> |
+ deadline;?> |
+ realStarted)) echo substr($task->realStarted, 0, 11);?> |
+ progress;?>% |
+
+
+
+
+
+
weekly->nextWeek;?>
+
+
+
+ | idAB;?> |
+ task->name;?> |
+ task->assignedTo;?> |
+ task->estStarted;?> |
+ task->deadline;?> |
+
+
+
+
+
+ |
+ id);?>
+ |
+
+ createLink('task', 'view', 'id=' . $task->id), $task->name); ?>
+ |
+ assignedTo);?> |
+ estStarted;?> |
+ deadline;?> |
+
+
+
+
+
+
weekly->workloadByType;?>
+
+
+
+ | task->type;?> |
+ task->typeList as $type => $name):?>
+
+ |
+
+ weekly->total;?> |
+
+
+
+
+ | weekly->workload;?> |
+
+ task->typeList as $type => $name):?>
+
+
+ |
+
+
+
+ |
+
+
+
+
+
+
+