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 + */ +?> + + +
+
+
+
+
    + +
  • '> + +
  • + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
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;?> +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
holiday->type;?>holiday->typeList, 'holiday');?>
holiday->begin;?>
holiday->end;?>
holiday->name;?>
holiday->desc?>
+
+
+
+ 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;?> +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
holiday->type;?>holiday->typeList, $holiday->type);?>
holiday->begin?>begin, "class='form-control form-date' required")?>
holiday->end?>end, "class='form-control form-date' required")?>
holiday->name?>name, "class='form-control' required")?>
holiday->desc?>desc, "class='form-control'")?>
+
+
+
+ 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 .= "
"; + $selectHtml .= "
"; + $selectHtml .= "" . $current . " "; + $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 $ + */ +?> + +