Browse Source

Ticket #5425 - Tasks: Functionality improvements. Stage 2. Budget.

AntonLV 1 month ago
parent
commit
8bd420eff6

+ 39 - 0
modules/boonex/russian/data/langs/bx_tasks/ru.xml

@@ -148,6 +148,21 @@
     <string name="_bx_tasks_popup_title_time_add"><![CDATA[Добавить время]]></string>
     <string name="_bx_tasks_popup_title_time_edit"><![CDATA[Редактировать время]]></string>
 
+    <string name="_bx_tasks_form_budget"><![CDATA[Бюджет]]></string>
+    <string name="_bx_tasks_form_display_budget_add"><![CDATA[Добавить бюджет]]></string>
+    <string name="_bx_tasks_form_display_budget_edit"><![CDATA[Редактировать бюджет]]></string>
+    <string name="_bx_tasks_form_budget_input_sys_submit"><![CDATA[Выполнить]]></string>
+    <string name="_bx_tasks_form_budget_input_submit"><![CDATA[Выполнить]]></string>
+    <string name="_bx_tasks_form_budget_input_sys_cancel"><![CDATA[Отменить]]></string>
+    <string name="_bx_tasks_form_budget_input_cancel"><![CDATA[Отменить]]></string>
+    <string name="_bx_tasks_form_budget_input_sys_controls"><![CDATA[Управление]]></string>
+    <string name="_bx_tasks_form_budget_input_sys_value"><![CDATA[Бюджет]]></string>
+    <string name="_bx_tasks_form_budget_input_value"><![CDATA[Бюджет]]></string>
+    <string name="_bx_tasks_form_budget_input_value_info"><![CDATA[Укажите значение бюджета в часах.]]></string>
+    <string name="_bx_tasks_form_budget_input_value_err"><![CDATA[Если вы хотите добавить бюджет, заполните это поле.]]></string>
+    <string name="_bx_tasks_form_budget_input_sys_text"><![CDATA[Примечание]]></string>
+    <string name="_bx_tasks_form_budget_input_text"><![CDATA[Примечание]]></string>
+
     <string name="_bx_tasks_form_pv_input_title"><![CDATA[Заголовок]]></string>
     <string name="_bx_tasks_form_pv_input_color"><![CDATA[Цвет]]></string>
     <string name="_bx_tasks_form_pv_input_submit"><![CDATA[Выполнить]]></string>
@@ -302,6 +317,12 @@
     <string name="_bx_tasks_page_block_title_entries_time_in_context"><![CDATA[Мое время в <a href="{profile_link}">{display_name}</a>]]></string>
     <string name="_bx_tasks_page_block_title_sys_entries_time_in_context_administration"><![CDATA[Всё время в контексте]]></string>
     <string name="_bx_tasks_page_block_title_entries_time_in_context_administration"><![CDATA[Всё время в <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_tasks_page_title_sys_entries_budget_in_context_administration"><![CDATA[Контекст: Бюджет]]></string>
+    <string name="_bx_tasks_page_title_entries_budget_in_context_administration"><![CDATA[Бюджет в {display_name}]]></string>
+    <string name="_bx_tasks_page_block_title_sys_entries_budget_in_context_summary"><![CDATA[Сводка бюджета в контексте]]></string>
+    <string name="_bx_tasks_page_block_title_entries_budget_in_context_summary"><![CDATA[Сводка бюджета]]></string>
+    <string name="_bx_tasks_page_block_title_sys_entries_budget_in_context_administration"><![CDATA[Бюджет в контексте]]></string>
+    <string name="_bx_tasks_page_block_title_entries_budget_in_context_administration"><![CDATA[Бюджет в <a href="{profile_link}">{display_name}</a>]]></string>
     <string name="_bx_tasks_page_title_sys_context_pre_values"><![CDATA[Контекст: Свойства]]></string>
     <string name="_bx_tasks_page_title_context_pre_values"><![CDATA[Свойства]]></string>
     <string name="_bx_tasks_page_block_title_system_context_pre_values"><![CDATA[Свойства]]></string>
@@ -392,6 +413,8 @@
     <string name="_bx_tasks_menu_item_title_view_context_time"><![CDATA[Моё время]]></string>
     <string name="_bx_tasks_menu_item_title_system_view_context_time_administration"><![CDATA[Всё время]]></string>
     <string name="_bx_tasks_menu_item_title_view_context_time_administration"><![CDATA[Всё время]]></string>
+    <string name="_bx_tasks_menu_item_title_system_view_context_budget_administration"><![CDATA[Бюджет]]></string>
+    <string name="_bx_tasks_menu_item_title_view_context_budget_administration"><![CDATA[Бюджет]]></string>
     <string name="_bx_tasks_menu_item_title_system_manage_context_pre_values"><![CDATA[Свойства]]></string>
     <string name="_bx_tasks_menu_item_title_manage_context_pre_values"><![CDATA[Свойства]]></string>
     <string name="_bx_tasks_menu_item_title_system_manage_context_settings"><![CDATA[Настройки]]></string>
@@ -446,6 +469,17 @@
     <string name="_bx_tasks_grid_filter_item_title_tm_select_one_context_module"><![CDATA[Любой модуль]]></string>
     <string name="_bx_tasks_grid_filter_item_title_tm_select_one_object_id"><![CDATA[Любая задача]]></string>
 
+    <string name="_bx_tasks_grid_column_title_bdt_profile_id"><![CDATA[Профиль]]></string>
+    <string name="_bx_tasks_grid_column_title_bdt_text"><![CDATA[Примечание]]></string>
+    <string name="_bx_tasks_grid_column_title_bdt_value"><![CDATA[Значение]]></string>
+    <string name="_bx_tasks_grid_column_title_bdt_date"><![CDATA[Добавлено]]></string>
+    <string name="_bx_tasks_grid_action_title_bdt_add"><![CDATA[Добавить]]></string>
+    <string name="_bx_tasks_grid_action_title_bdt_edit"><![CDATA[Редактировать]]></string>
+    <string name="_bx_tasks_grid_action_title_bdt_delete"><![CDATA[Удалить]]></string>
+    <string name="_bx_tasks_grid_action_title_bdt_calculate"><![CDATA[Рассчитать]]></string>
+    <string name="_bx_tasks_grid_popup_title_bdt_add"><![CDATA[Добавить бюджет]]></string>
+    <string name="_bx_tasks_grid_popup_title_bdt_edit"><![CDATA[Редактировать бюджет]]></string>
+
     <string name="_bx_tasks_grid_column_title_pv_active"><![CDATA[Активные]]></string>
     <string name="_bx_tasks_grid_column_title_pv_title"><![CDATA[Заголовок]]></string>
     <string name="_bx_tasks_grid_action_title_pv_add"><![CDATA[Добавить]]></string>
@@ -572,6 +606,11 @@
     <string name="_bx_tasks_txt_timer_clear"><![CDATA[Очистить]]></string>
     <string name="_bx_tasks_txt_timer_log"><![CDATA[Внести]]></string>
 
+    <string name="_bx_tasks_txt_budget_total"><![CDATA[Бюджет:]]></string>
+    <string name="_bx_tasks_txt_budget_spent"><![CDATA[Использовано:]]></string>
+    <string name="_bx_tasks_txt_budget_remains"><![CDATA[Осталось:]]></string>
+    <string name="_bx_tasks_txt_budget_remains_value"><![CDATA[{0} ({1}%)]]></string>
+
     <string name="_bx_tasks_txt_err_cannot_perform_action"><![CDATA[Невозможно выполнить действие. Пожалуйста, оповестите нас об этой проблеме.]]></string>
     <string name="_bx_tasks_txt_err_empty_pre_list"><![CDATA[Пожалуйста, выберите список, в который хотите добавить элементы.]]></string>
     <string name="_bx_tasks_txt_msg_status_hidden"><![CDATA[Данное задание было скрыто.]]></string>

+ 8 - 2
modules/boonex/tasks/classes/BxTasksConfig.php

@@ -42,6 +42,8 @@ class BxTasksConfig extends BxBaseModTextConfig
             'TABLE_TIME' => $aModule['db_prefix'] . 'time',
             'TABLE_TIME_TRACK' => $aModule['db_prefix'] . 'time_track',
             'TABLE_TIMERS' => $aModule['db_prefix'] . 'timers',
+            'TABLE_BUDGET' => $aModule['db_prefix'] . 'budget',
+            'TABLE_BUDGET_TRACK' => $aModule['db_prefix'] . 'budget_track',
             'TABLE_PRE_LISTS' => $aModule['db_prefix'] . 'pre_lists',
             'TABLE_PRE_VALUES' => $aModule['db_prefix'] . 'pre_values',
             'TABLE_POLLS' => '',
@@ -174,6 +176,9 @@ class BxTasksConfig extends BxBaseModTextConfig
             'OBJECT_FORM_TIME' => 'bx_tasks_time',
             'OBJECT_FORM_TIME_DISPLAY_ADD' => 'bx_tasks_time_add',
             'OBJECT_FORM_TIME_DISPLAY_EDIT' => 'bx_tasks_time_edit',
+            'OBJECT_FORM_BUDGET' => 'bx_tasks_budget',
+            'OBJECT_FORM_BUDGET_DISPLAY_ADD' => 'bx_tasks_budget_add',
+            'OBJECT_FORM_BUDGET_DISPLAY_EDIT' => 'bx_tasks_budget_edit',
             'OBJECT_MENU_ENTRY_ATTACHMENTS' => 'bx_tasks_entry_attachments', // attachments menu in create/edit forms
             'OBJECT_MENU_ACTIONS_VIEW_ENTRY' => 'bx_tasks_view', // actions menu on view entry page
             'OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL' => 'bx_tasks_view_actions', // all actions menu on view entry page
@@ -182,11 +187,10 @@ class BxTasksConfig extends BxBaseModTextConfig
             'OBJECT_MENU_SUBMENU_VIEW_ENTRY' => 'bx_tasks_view_submenu', // view entry submenu
             'OBJECT_MENU_SUBMENU_VIEW_ENTRY_MAIN_SELECTION' => 'tasks-home', // first item in view entry submenu from main module submenu
             'OBJECT_MENU_SNIPPET_META' => 'bx_tasks_snippet_meta', // menu for snippet meta info
-            'OBJECT_MENU_MANAGE_TOOLS' => 'bx_tasks_menu_manage_tools', //manage menu in content administration tools
             'OBJECT_MENU_SUBMENU_VIEW_CONTEXT' => 'bx_tasks_view_context_submenu',
             'OBJECT_MENU_USE_TOOLS_SUBMENU' => 'bx_tasks_use_tools_submenu',
             'OBJECT_MENU_MANAGE_TOOLS_SUBMENU' => 'bx_tasks_manage_tools_submenu',
-            'OBJECT_MENU_MANAGE_TOOLS' => 'bx_tasks_menu_manage_tools', //manage item menu in content administration tools
+            'OBJECT_MENU_MANAGE_TOOLS' => 'bx_tasks_menu_manage_tools', //manage menu in content administration tools
             'OBJECT_MENU_BROWSE' => 'bx_tasks_browse',
             'OBJECT_GRID_ADMINISTRATION' => 'bx_tasks_administration',
             'OBJECT_GRID_COMMON' => 'bx_tasks_common',
@@ -194,6 +198,7 @@ class BxTasksConfig extends BxBaseModTextConfig
             'OBJECT_GRID_TIME_COMMON' => 'bx_tasks_time_common',
             'OBJECT_GRID_TIME_CONTEXT_ADMINISTRATION' => 'bx_tasks_time_context_administration',
             'OBJECT_GRID_TIME_CONTEXT_COMMON' => 'bx_tasks_time_context_common',
+            'OBJECT_GRID_BUDGET_CONTEXT_ADMINISTRATION' => 'bx_tasks_budget_context_administration',
             'OBJECT_GRID_PRE_VALUES' => 'bx_tasks_pre_values',
             'OBJECT_UPLOADERS' => array('bx_tasks_simple', 'bx_tasks_html5'),
             'OBJECT_CONTENT_INFO' => 'bx_tasks',
@@ -302,6 +307,7 @@ class BxTasksConfig extends BxBaseModTextConfig
             'tasks' => $sPrefix . '-tasks',
             'time_popup' => $sPrefix . '-time-popup',
             'total_popup' => $sPrefix . '-total-popup',
+            'budget_popup' => $sPrefix . '-budget-popup',
             'filter_popup' => $sPrefix . '-filter-popup',
             'timer' => $sPrefix . '-timer-',
             'timer_actions' => $sPrefix . '-timer-actions',

+ 112 - 0
modules/boonex/tasks/classes/BxTasksDb.php

@@ -219,6 +219,17 @@ class BxTasksDb extends BxBaseModTextDb
         return count($aResult) == (int)$this->query("UPDATE `" . $CNF['TABLE_ENTRIES'] . "` SET `" . $CNF['FIELD_EXPIRED'] . "` = '1' WHERE `id` IN (" . $this->implode_escape($aResult) . ")") ? $aResult : false;
     }
 
+    public function getContextId($iContentId)
+    {
+        $CNF = $this->_oConfig->CNF;
+
+        $aData = $this->getContentInfoById($iContentId);
+        if($aData && is_array($aData) && ($iValue = (int)($aData[$CNF['FIELD_ALLOW_VIEW_TO']] ?? 0)) < 0)
+            return abs($iValue);
+
+        return 0;
+    }
+
     public function getContextsIdsByType($sType, $iProfileId)
     {
         $CNF = $this->_oConfig->CNF;
@@ -500,6 +511,107 @@ class BxTasksDb extends BxBaseModTextDb
         return $this->query("DELETE FROM `" . $CNF['TABLE_TIMERS'] . "` WHERE " . $this->arrayToSQL($aParamsWhere, " AND "));
     }
 
+    public function getBudgetTracks($aParams = []) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aMethod = ['name' => 'getAll', 'params' => [0 => 'query']];
+        $sSelectClause = '`tbt`.*';
+        $sJoinClause = $sWhereClause = $sOrderClause = '';
+
+        if(!empty($aParams))
+            switch($aParams['sample']) {
+                case 'id':
+                    $aMethod['name'] = 'getRow';
+                    $aMethod['params'][1] = [
+                        'id' => $aParams['id']
+                    ];
+
+                    $sWhereClause = "AND `tbt`.`id` = :id";
+                    break;
+            }
+
+        if(!empty($sOrderClause))
+            $sOrderClause = "ORDER BY " . $sOrderClause;
+
+        $aMethod['params'][0] = "SELECT 
+                " . $sSelectClause . " 
+            FROM `" . $CNF['TABLE_BUDGET_TRACK'] . "` AS `tbt` " . $sJoinClause . " 
+            WHERE 1 " . $sWhereClause . " " . $sOrderClause;
+
+        return call_user_func_array([$this, $aMethod['name']], $aMethod['params']);
+    }
+
+    public function getBudget($aParams = []) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aMethod = ['name' => 'getAll', 'params' => [0 => 'query']];
+        $sSelectClause = '`tb`.*';
+        $sJoinClause = $sWhereClause = $sOrderClause = '';
+
+        if(!empty($aParams))
+            switch($aParams['sample']) {
+                case 'id':
+                    $aMethod['name'] = 'getRow';
+                    $aMethod['params'][1] = [
+                        'id' => $aParams['id']
+                    ];
+
+                    $sWhereClause = "AND `tb`.`id` = :id";
+                    break;
+
+                case 'context_id':
+                    $aMethod['name'] = 'getRow';
+                    $aMethod['params'][1] = [
+                        'context_id' => $aParams['context_id']
+                    ];
+
+                    $sWhereClause = "AND `tb`.`context_id` = :context_id";
+                    break;
+            }
+
+        if(!empty($sOrderClause))
+            $sOrderClause = "ORDER BY " . $sOrderClause;
+
+        $aMethod['params'][0] = "SELECT 
+                " . $sSelectClause . " 
+            FROM `" . $CNF['TABLE_BUDGET'] . "` AS `tb` " . $sJoinClause . " 
+            WHERE 1 " . $sWhereClause . " " . $sOrderClause;
+
+        return call_user_func_array([$this, $aMethod['name']], $aMethod['params']);
+    }
+
+    public function insertBudget($iContextId, $iValue)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return $this->query("INSERT INTO `" . $CNF['TABLE_BUDGET'] . "` (`context_id`, `value_total`) VALUES (:context_id, :value_total) ON DUPLICATE KEY UPDATE `value_total`=`value_total`+:value_total", [
+            'context_id' => $iContextId,
+            'value_total' => $iValue
+        ]) ? $this->lastId() : false;
+    }
+
+    public function updateBudgetTotal($iContextId, $iValue)
+    {
+        return $this->updateBudgetValue('total', $iContextId, $iValue);
+    }
+
+    public function updateBudgetSpent($iContextId, $iValue)
+    {
+        return $this->updateBudgetValue('spent', $iContextId, $iValue);
+    }
+
+    public function updateBudgetValue($sField, $iContextId, $iValue)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return $this->query("UPDATE `" . $CNF['TABLE_BUDGET'] . "` SET `value_" . $sField . "` = `value_" . $sField . "` + :value WHERE `context_id` = :context_id", [
+            'context_id' => $iContextId,
+            'value' => $iValue,
+        ]) !== false;
+    }
+
     public function getPreLists($aParams = []) 
     {
         $CNF = &$this->_oConfig->CNF;

+ 25 - 0
modules/boonex/tasks/classes/BxTasksFormBudget.php

@@ -0,0 +1,25 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT 
+ * @defgroup    Tasks Tasks
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxTasksFormBudget extends BxTemplFormView
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_tasks';
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        parent::__construct($aInfo, $oTemplate);
+    }
+}
+
+/** @} */

+ 211 - 0
modules/boonex/tasks/classes/BxTasksGridBudgetContextAdministration.php

@@ -0,0 +1,211 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT 
+ * @defgroup    Tasks Tasks
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+require_once(BX_DOL_DIR_STUDIO_INC . 'utils.inc.php');
+
+class BxTasksGridBudgetContextAdministration extends BxBaseModGeneralGrid
+{
+    protected $_iLogged;
+    protected $_iContextPid;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_tasks';
+
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_iLogged = bx_get_logged_profile_id();
+
+        if(($iContextPid = bx_get('context_pid')) !== false) 
+            $this->setContextPid($iContextPid);
+    }
+
+    public function setContextPid($iContextPid)
+    {
+        $this->_iContextPid = (int)$iContextPid;
+        $this->_aQueryAppend['context_pid'] = $this->_iContextPid;
+    }
+
+    public function getFormCallBackUrlAPI($sAction, $iId = 0)
+    {
+         return '/api.php?r=system/perfom_action_api/TemplServiceGrid/&params[]=&o=' . $this->_sObject . '&a=' . $sAction . '&context_pid=' . $this->_iContextPid . '&id=' . $iId;
+    }
+
+    public function getFormBlockTitleAPI($sAction, $iId = 0)
+    {
+        $sResult = '';
+
+        switch($sAction) {
+            case 'add':
+                $sResult = _t('_bx_tasks_grid_popup_title_bdt_add');
+                break;
+
+            case 'edit':
+                $sResult = _t('_bx_tasks_grid_popup_title_bdt_edit');
+                break;
+
+            default:
+                parent::getFormBlockTitleAPI($sAction, $iId);
+        }
+
+        return $sResult;
+    }
+    
+    public function performActionAdd()
+    {
+        $sAction = 'add';
+
+        $oForm = $this->_getFormObject($sAction);
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $iNow = time();
+            $aValsToAdd = [
+                'context_id' => $this->_iContextPid,
+                'profile_id' => $this->_iLogged,
+                'date' => $iNow
+            ];
+
+            $iTrackId = $oForm->insert($aValsToAdd);
+            if(!$iTrackId)
+                return $this->_getActionResult(['msg' => _t('_bx_tasks_txt_err_cannot_perform_action')]);
+
+            $this->_oModule->_oDb->insertBudget($this->_iContextPid, 60 * $oForm->getCleanValue('value'));
+
+            return $this->_bIsApi ? [] : echoJson(['grid' => $this->getCode(false), 'blink' => $iTrackId]);    
+        }
+
+        if($this->_bIsApi)
+            return $this->getFormBlockAPI($oForm, $sAction);
+
+        $sContent = BxTemplFunctions::getInstance()->popupBox($this->_oModule->_oConfig->getHtmlIds('budget_popup'), _t('_bx_tasks_grid_popup_title_bdt_add'), $this->_oModule->_oTemplate->parseHtmlByName('popup_budget.html', [
+            'form_id' => $oForm->getId(),
+            'form' => $oForm->getCode(true),
+            'object' => $this->_sObject,
+            'action' => $sAction
+        ]));
+
+        return echoJson(['popup' => ['html' => $sContent, 'options' => ['closeOnOuterClick' => false]]]);
+    }
+
+    public function performActionEdit()
+    {
+        $sAction = 'edit';
+
+        $iTrackId = $this->_getId();
+        $aTrack = $this->_oModule->_oDb->getBudgetTracks(['sample' => 'id', 'id' => $iTrackId]);
+        if(empty($aTrack) || !is_array($aTrack) || (int)$aTrack['context_id'] !== $this->_iContextPid)
+            return $this->_getActionResult([]);
+
+        $oForm = $this->_getFormObject($sAction, $aTrack);
+        $oForm->initChecker($aTrack);
+        if($oForm->isSubmittedAndValid()) {
+            if(!$oForm->update($iTrackId))
+                return $this->_getActionResult(['msg' => _t('_bx_tasks_txt_err_cannot_perform_action')]);
+
+            $this->_oModule->_oDb->updateBudgetTotal($this->_iContextPid, 60 * ($oForm->getCleanValue('value') - (int)$aTrack['value']));
+
+            return $this->_bIsApi ? [] : echoJson(['grid' => $this->getCode(false), 'blink' => $iTrackId]);    
+        }
+
+        if($this->_bIsApi)
+            return $this->getFormBlockAPI($oForm, $sAction, $iTrackId);
+
+        $sContent = BxTemplFunctions::getInstance()->popupBox($this->_oModule->_oConfig->getHtmlIds('budget_popup'), _t('_bx_tasks_grid_popup_title_bdt_edit'), $this->_oModule->_oTemplate->parseHtmlByName('popup_budget.html', [
+            'form_id' => $oForm->getId(),
+            'form' => $oForm->getCode(true),
+            'object' => $this->_sObject,
+            'action' => $sAction
+        ]));
+
+        return echoJson(['popup' => ['html' => $sContent, 'options' => ['closeOnOuterClick' => false]]]);
+    }
+
+    protected function _delete ($mixedId)
+    {
+        $aTrack = $this->_oModule->_oDb->getBudgetTracks(['sample' => 'id', 'id' => (int)$mixedId]);
+        if(empty($aTrack) || !is_array($aTrack) || (int)$aTrack['context_id'] !== $this->_iContextPid)
+            return false;
+
+        $bResult = parent::_delete($mixedId);
+        if($bResult)
+            $this->_oModule->_oDb->updateBudgetTotal($this->_iContextPid, -(int)$aTrack['value']);
+
+        return $bResult;
+    }
+
+    protected function _getCellProfileId($mixedValue, $sKey, $aField, $aRow)
+    {
+        if($this->_bIsApi)
+            return ['type' => 'profile', 'data' => BxDolProfile::getData($mixedValue)];
+
+        return parent::_getCellDefault(BxDolProfile::getInstanceMagic($mixedValue)->getUnit(), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellValue($mixedValue, $sKey, $aField, $aRow)
+    {
+        if (!$this->_bIsApi)
+            $mixedValue = $this->_oModule->_oConfig->timeI2S(60 * (int)$mixedValue);
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellDate($mixedValue, $sKey, $aField, $aRow)
+    {
+        if ($this->_bIsApi)
+            return ['type' => 'time', 'data' => $mixedValue];
+
+        return parent::_getCellDefault(bx_time_js($mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getFormObject($sAction, $aTrack = [])
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $bActionEdit = $sAction == 'edit';
+
+        $bTrack = !empty($aTrack) && is_array($aTrack);
+
+        $aActionParams = [
+            'o' => $this->_sObject, 
+            'a' => $sAction,
+            'context_pid' => $this->_iContextPid
+        ];
+        if($bActionEdit && $bTrack)
+            $aActionParams['id'] = (int)$aTrack['id'];
+
+        $sForm = $CNF['OBJECT_FORM_BUDGET_DISPLAY_' . strtoupper($sAction)];
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_BUDGET'], $sForm);
+        $oForm->setId($sForm);
+        $oForm->setName($sForm);
+    	$oForm->setAction(BX_DOL_URL_ROOT . bx_append_url_params('grid.php', $aActionParams));
+
+        return $oForm;
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+        $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND `context_id`=?", $this->_iContextPid);
+
+        return parent::_getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+    }
+
+    protected function _getId()
+    {
+        if(($aIds = bx_get('ids')) && is_array($aIds))
+            return reset($aIds);
+
+        if(($iId = bx_get('id')) !== false) 
+            return (int)$iId;
+
+        return false;
+    }
+}
+
+/** @} */

+ 2 - 0
modules/boonex/tasks/classes/BxTasksGridTime.php

@@ -104,6 +104,8 @@ class BxTasksGridTime extends BxBaseModGeneralGrid
         if($bResult && ($oTime = BxDolReport::getObjectInstance($CNF['OBJECT_REPORTS_TIME'], $iObjectId)) && $oTime->isEnabled())
             $oTime->putReport($iObjectId, $iAuthorId, $aTrack, true);
 
+        $this->_oModule->updateBudgetByContentId($iObjectId, -$aTrack['value']);
+
         return $bResult;
     }
 

+ 6 - 1
modules/boonex/tasks/classes/BxTasksGridTimeContextCommon.php

@@ -50,10 +50,11 @@ class BxTasksGridTimeContextCommon extends BxTasksGridTimeContextAdministration
         $oForm->initChecker();
         if($oForm->isSubmittedAndValid()) {
             $iNow = time();
+            $iValue = $this->_oModule->_oConfig->timeA2I([(int)$oForm->getCleanValue('value_h'), (int)$oForm->getCleanValue('value_m')]);
             $aValsToAdd = [
                 'author_id' => $this->_iLogged,
                 'author_nip' => bx_get_ip_hash(getVisitorIP()),
-                'value' => $this->_oModule->_oConfig->timeA2I([(int)$oForm->getCleanValue('value_h'), (int)$oForm->getCleanValue('value_m')]),
+                'value' => $iValue,
                 'value_date' => $oForm->getCleanValue('value_date') ?: $iNow,
                 'date' => $iNow
             ];
@@ -67,6 +68,8 @@ class BxTasksGridTimeContextCommon extends BxTasksGridTimeContextAdministration
             if(($oTime = BxDolReport::getObjectInstance($sSystem, $iObjectId)) && $oTime->isEnabled())
                 $oTime->putReport($iObjectId, $this->_iLogged, $iTrackId);
 
+            $this->_oModule->updateBudgetByContentId($iObjectId, $iValue);
+
             return $this->_bIsApi ? [] : echoJson(['grid' => $this->getCode(false), 'blink' => $iTrackId]);    
         }
 
@@ -115,6 +118,8 @@ class BxTasksGridTimeContextCommon extends BxTasksGridTimeContextAdministration
                 $oTime->putReport($iObjectId, $iAuthorId, $iTrackId); //--- Process new value
             }
 
+            $this->_oModule->updateBudgetByContentId($iObjectId, $aValsToAdd['value'] - $aTrack['value']);
+
             return $this->_bIsApi ? [] : echoJson(['grid' => $this->getCode(false), 'blink' => $iTrackId]);    
         }
 

+ 65 - 1
modules/boonex/tasks/classes/BxTasksModule.php

@@ -585,13 +585,14 @@ class BxTasksModule extends BxBaseModTextModule implements iBxDolCalendarService
                     break;
 
                 list($iH, $iM) = $this->_oConfig->timeI2A($aTimer['duration'], true);
+                $iSpent = $this->_oConfig->timeA2I([(int)$iH, (int)$iM + 1]);
 
                 $iNow = time();
                 $iTrackId = $this->_oDb->insertTimeTrack([
                     'object_id' => $iContentId,
                     'author_id' => $iProfileId,
                     'author_nip' => $iProfileId == $this->_iProfileId ? bx_get_ip_hash(getVisitorIP()) : 0,
-                    'value' => $this->_oConfig->timeA2I([(int)$iH, (int)$iM + 1]),
+                    'value' => $iSpent,
                     'value_date' => $iNow,
                     'date' => $iNow
                 ]);
@@ -610,6 +611,8 @@ class BxTasksModule extends BxBaseModTextModule implements iBxDolCalendarService
                     'profile_id' => $iProfileId
                 ]);
 
+                $this->updateBudgetByContentId($iContentId, $iSpent);
+
                 $aResult = $this->_bIsApi ? $this->_oTemplate->getTimer($iContentId, $iProfileId) : ['code' => 0];
                 break;
 
@@ -821,6 +824,58 @@ class BxTasksModule extends BxBaseModTextModule implements iBxDolCalendarService
         ] : $mixedResult;
     }
 
+    public function serviceGetBlockManageBudget($sType = 'administration', $iContextPid = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $bContextPid = !empty($iContextPid);
+        if(!$bContextPid)
+            return $this->_bIsApi ? [] : '';
+
+        $sGrid = $CNF['OBJECT_GRID_BUDGET_' . ($bContextPid ? 'CONTEXT_' : '') . strtoupper($sType)];
+        $oGrid = BxDolGrid::getObjectInstance($sGrid);
+        if(!$oGrid)
+            return $this->_bIsApi ? [] : '';
+
+        if($bContextPid)
+            $oGrid->setContextPid($iContextPid);
+
+        if($this->_bIsApi)
+            return [
+                bx_api_get_block('grid', $oGrid->getCodeAPI())
+            ];
+
+        list($aCssCalendar, $aJsCalendar) = BxBaseFormView::getCssJsCalendar();
+
+        $this->_oTemplate->addCss(array_merge($aCssCalendar, ['manage_tools.css', 'budget.css']));
+        $this->_oTemplate->addJs(array_merge($aJsCalendar, ['modules/base/text/js/|manage_tools.js', 'budget.js']));
+        $this->_oTemplate->addJsTranslation(['_sys_grid_search']);
+        $aResult = [
+            'content' => $this->_oTemplate->getJsCode('budget', [
+                'sObjNameGrid' => $sGrid
+            ]) . $oGrid->getCode()
+        ];
+
+        if(!$bContextPid)
+            $aResult['menu'] = BxDolMenu::getObjectInstance($CNF['OBJECT_MENU_MANAGE_TOOLS_SUBMENU']);
+
+        return $aResult;
+    }
+
+    public function serviceGetBlockBudget($iContextPid = 0)
+    {
+        $aBudget = $this->_oDb->getBudget(['sample'=> 'context_id', 'context_id' => $iContextPid]);
+        if(!$aBudget || !is_array($aBudget))
+            return $this->_bIsApi ? [] : MsgBox(_t('_Empty'));
+
+        if($this->_bIsApi)
+            return [
+                bx_api_get_block('tasks_budget', $aBudget)
+            ];
+
+        return $this->_oTemplate->getBlockBudget($aBudget);
+    }
+
     /**
      * Data for Timeline module
      */
@@ -1331,6 +1386,15 @@ class BxTasksModule extends BxBaseModTextModule implements iBxDolCalendarService
         return $this->_oDb->updateTimer($aSet, ['id' => (int)$iId]) !== false;
     }
 
+    public function updateBudgetByContentId($iContentId, $iValue)
+    {
+        $bResult = false;
+        if(($iContextId = $this->_oDb->getContextId($iContentId)))
+            $bResult = $this->_oDb->updateBudgetSpent($iContextId, $iValue);
+
+        return $bResult;
+    }   
+
     public function onPublished($iContentId)
     {
         parent::onPublished($iContentId);

+ 14 - 1
modules/boonex/tasks/classes/BxTasksTemplate.php

@@ -91,7 +91,20 @@ class BxTasksTemplate extends BxBaseModTextTemplate
             ]
         ]);
     }
-    
+
+    public function getBlockBudget($aBudget)
+    {
+        $iTotal = (int)$aBudget['value_total'];
+        $iSpent = (int)$aBudget['value_spent'];
+        $iRemains = $iTotal - $iSpent;
+
+        return $this->parseHtmlByName('block_budget.html', [
+            'total' => $this->_oConfig->timeI2S($iTotal),
+            'spent' => $this->_oConfig->timeI2S($iSpent),
+            'remains' => _t('_bx_tasks_txt_budget_remains_value', $this->_oConfig->timeI2S($iRemains), round(100 * $iRemains / $iTotal))
+        ]);
+    }
+
     /**
      * Use Gallery image for both because currently there is no Unit types with small thumbnails.
      */

+ 2 - 0
modules/boonex/tasks/classes/BxTasksTime.php

@@ -240,6 +240,8 @@ class BxTasksTime extends BxTemplReport
                 }
             }
 
+            $this->_oModule->updateBudgetByContentId($iContentId, $iValue);
+
             return $aResult;
         }
 

+ 39 - 0
modules/boonex/tasks/install/langs/en.xml

@@ -148,6 +148,21 @@
     <string name="_bx_tasks_form_time_input_sys_text"><![CDATA[Note]]></string>
     <string name="_bx_tasks_form_time_input_text"><![CDATA[Note]]></string>
 
+    <string name="_bx_tasks_form_budget"><![CDATA[Budget]]></string>
+    <string name="_bx_tasks_form_display_budget_add"><![CDATA[Add Budget]]></string>
+    <string name="_bx_tasks_form_display_budget_edit"><![CDATA[Edit Budget]]></string>
+    <string name="_bx_tasks_form_budget_input_sys_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_tasks_form_budget_input_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_tasks_form_budget_input_sys_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_tasks_form_budget_input_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_tasks_form_budget_input_sys_controls"><![CDATA[Controls]]></string>
+    <string name="_bx_tasks_form_budget_input_sys_value"><![CDATA[Budget]]></string>
+    <string name="_bx_tasks_form_budget_input_value"><![CDATA[Budget]]></string>
+    <string name="_bx_tasks_form_budget_input_value_info"><![CDATA[Specify budget value in hours.]]></string>
+    <string name="_bx_tasks_form_budget_input_value_err"><![CDATA[If you want to add budget fill this field in.]]></string>
+    <string name="_bx_tasks_form_budget_input_sys_text"><![CDATA[Note]]></string>
+    <string name="_bx_tasks_form_budget_input_text"><![CDATA[Note]]></string>
+
     <string name="_bx_tasks_form_pv_input_title"><![CDATA[Title]]></string>
     <string name="_bx_tasks_form_pv_input_color"><![CDATA[Color]]></string>
     <string name="_bx_tasks_form_pv_input_submit"><![CDATA[Submit]]></string>
@@ -302,6 +317,12 @@
     <string name="_bx_tasks_page_block_title_entries_time_in_context"><![CDATA[My Time in <a href="{profile_link}">{display_name}</a>]]></string>
     <string name="_bx_tasks_page_block_title_sys_entries_time_in_context_administration"><![CDATA[All time in context]]></string>
     <string name="_bx_tasks_page_block_title_entries_time_in_context_administration"><![CDATA[All Time in <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_tasks_page_title_sys_entries_budget_in_context_administration"><![CDATA[Context: Budget]]></string>
+    <string name="_bx_tasks_page_title_entries_budget_in_context_administration"><![CDATA[Budget in {display_name}]]></string>
+    <string name="_bx_tasks_page_block_title_sys_entries_budget_in_context_summary"><![CDATA[Budget summary in context]]></string>
+    <string name="_bx_tasks_page_block_title_entries_budget_in_context_summary"><![CDATA[Budget summary]]></string>
+    <string name="_bx_tasks_page_block_title_sys_entries_budget_in_context_administration"><![CDATA[Budget in context]]></string>
+    <string name="_bx_tasks_page_block_title_entries_budget_in_context_administration"><![CDATA[Budget in <a href="{profile_link}">{display_name}</a>]]></string>
     <string name="_bx_tasks_page_title_sys_context_pre_values"><![CDATA[Context: Properties]]></string>
     <string name="_bx_tasks_page_title_context_pre_values"><![CDATA[Properties]]></string>
     <string name="_bx_tasks_page_block_title_system_context_pre_values"><![CDATA[Properties]]></string>
@@ -394,6 +415,8 @@
     <string name="_bx_tasks_menu_item_title_view_context_time"><![CDATA[My Time]]></string>
     <string name="_bx_tasks_menu_item_title_system_view_context_time_administration"><![CDATA[All Time]]></string>
     <string name="_bx_tasks_menu_item_title_view_context_time_administration"><![CDATA[All Time]]></string>
+    <string name="_bx_tasks_menu_item_title_system_view_context_budget_administration"><![CDATA[Budget]]></string>
+    <string name="_bx_tasks_menu_item_title_view_context_budget_administration"><![CDATA[Budget]]></string>
     <string name="_bx_tasks_menu_item_title_system_manage_context_pre_values"><![CDATA[Properties]]></string>
     <string name="_bx_tasks_menu_item_title_manage_context_pre_values"><![CDATA[Properties]]></string>
     <string name="_bx_tasks_menu_item_title_system_manage_context_settings"><![CDATA[Settings]]></string>
@@ -452,6 +475,17 @@
     <string name="_bx_tasks_grid_popup_title_tm_edit"><![CDATA[Edit Time]]></string>
     <string name="_bx_tasks_grid_popup_title_tm_calculate"><![CDATA[Total Spent Time]]></string>
 
+    <string name="_bx_tasks_grid_column_title_bdt_profile_id"><![CDATA[Profile]]></string>
+    <string name="_bx_tasks_grid_column_title_bdt_text"><![CDATA[Note]]></string>
+    <string name="_bx_tasks_grid_column_title_bdt_value"><![CDATA[Value]]></string>
+    <string name="_bx_tasks_grid_column_title_bdt_date"><![CDATA[Added]]></string>
+    <string name="_bx_tasks_grid_action_title_bdt_add"><![CDATA[Add]]></string>
+    <string name="_bx_tasks_grid_action_title_bdt_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_tasks_grid_action_title_bdt_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_tasks_grid_action_title_bdt_calculate"><![CDATA[Calculate]]></string>
+    <string name="_bx_tasks_grid_popup_title_bdt_add"><![CDATA[Add Budget]]></string>
+    <string name="_bx_tasks_grid_popup_title_bdt_edit"><![CDATA[Edit Budget]]></string>
+
     <string name="_bx_tasks_grid_column_title_pv_active"><![CDATA[Active]]></string>
     <string name="_bx_tasks_grid_column_title_pv_title"><![CDATA[Title]]></string>
     <string name="_bx_tasks_grid_action_title_pv_add"><![CDATA[Add]]></string>
@@ -584,6 +618,11 @@
     <string name="_bx_tasks_txt_timers_clear"><![CDATA[Clear All]]></string>
     <string name="_bx_tasks_txt_timers_log"><![CDATA[Log All]]></string>
 
+    <string name="_bx_tasks_txt_budget_total"><![CDATA[Budget:]]></string>
+    <string name="_bx_tasks_txt_budget_spent"><![CDATA[Spent:]]></string>
+    <string name="_bx_tasks_txt_budget_remains"><![CDATA[Remains:]]></string>
+    <string name="_bx_tasks_txt_budget_remains_value"><![CDATA[{0} ({1}%)]]></string>
+
     <string name="_bx_tasks_txt_err_cannot_perform_action"><![CDATA[Cannot perform action. Please report.]]></string>
     <string name="_bx_tasks_txt_err_empty_pre_list"><![CDATA[Please select a list to add items to.]]></string>
     <string name="_bx_tasks_txt_err_timer_not_found"><![CDATA[Timer wasn't found or empty.]]></string>

+ 4 - 0
modules/boonex/tasks/install/sql/enable-app.sql

@@ -20,6 +20,9 @@ UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_tasks_context_ti
 UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_tasks_context_time' AND `module`='bx_tasks' AND `title_system`='_bx_tasks_page_block_title_sys_entries_time_in_context' AND `title`='_bx_tasks_page_block_title_entries_time_in_context';
 UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_tasks_context_time_administration' AND `module`='bx_tasks' AND `title_system`='_bx_tasks_page_block_title_sys_menu_in_context' AND `title`='_bx_tasks_page_block_title_menu_in_context';
 UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_tasks_context_time_administration' AND `module`='bx_tasks' AND `title_system`='_bx_tasks_page_block_title_sys_entries_time_in_context_administration' AND `title`='_bx_tasks_page_block_title_entries_time_in_context_administration';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_tasks_context_budget_administration' AND `module`='bx_tasks' AND `title_system`='_bx_tasks_page_block_title_sys_menu_in_context' AND `title`='_bx_tasks_page_block_title_menu_in_context';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_tasks_context_budget_administration' AND `module`='bx_tasks' AND `title_system`='_bx_tasks_page_block_title_sys_entries_budget_in_context_summary' AND `title`='_bx_tasks_page_block_title_entries_budget_in_context_summary';
+UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_tasks_context_budget_administration' AND `module`='bx_tasks' AND `title_system`='_bx_tasks_page_block_title_sys_entries_budget_in_context_administration' AND `title`='_bx_tasks_page_block_title_entries_budget_in_context_administration';
 UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_tasks_context_pre_values' AND `module`='bx_tasks' AND `title_system`='_bx_tasks_page_block_title_system_context_pre_values' AND `title`='_bx_tasks_page_block_title_context_pre_values';
 UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_tasks_context_settings' AND `module`='bx_tasks' AND `title_system`='_bx_tasks_page_block_title_system_context_settings' AND `title`='_bx_tasks_page_block_title_context_settings';
 UPDATE `sys_pages_blocks` SET `active_api`=1 WHERE `object`='bx_tasks_manage' AND `module`='bx_tasks' AND `title_system`='_bx_tasks_page_block_title_system_manage' AND `title`='_bx_tasks_page_block_title_manage';
@@ -49,6 +52,7 @@ UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_tasks_view_actio
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_tasks_view_context_submenu' AND `module`='bx_tasks' AND `name`='tasks-context';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_tasks_view_context_submenu' AND `module`='bx_tasks' AND `name`='tasks-context-time';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_tasks_view_context_submenu' AND `module`='bx_tasks' AND `name`='tasks-context-time-administration';
+UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_tasks_view_context_submenu' AND `module`='bx_tasks' AND `name`='tasks-context-budget-administration';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_tasks_view_context_submenu' AND `module`='bx_tasks' AND `name`='tasks-context-values';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_tasks_use_tools_submenu' AND `module`='bx_tasks' AND `name`='tasks-home';
 UPDATE `sys_menu_items` SET `active_api`=1 WHERE `set_name`='bx_tasks_use_tools_submenu' AND `module`='bx_tasks' AND `name`='tasks-timers';

+ 30 - 2
modules/boonex/tasks/install/sql/enable.sql

@@ -110,6 +110,15 @@ INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `t
 ('bx_tasks_context_time_administration', 2, 'bx_tasks', '_bx_tasks_page_block_title_sys_menu_in_context', '_bx_tasks_page_block_title_menu_in_context', 13, 2147483647, 'service', 'a:3:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:22:"get_block_menu_context";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 1, 1),
 ('bx_tasks_context_time_administration', 3, 'bx_tasks', '_bx_tasks_page_block_title_sys_entries_time_in_context_administration', '_bx_tasks_page_block_title_entries_time_in_context_administration', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:21:"get_block_manage_time";s:6:"params";a:2:{i:0;s:14:"administration";i:1;s:12:"{profile_id}";}}', 0, 0, 1, 1);
 
+-- PAGE: entries' budget in context (all)
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_tasks_context_budget_administration', 'tasks-context-budget-administration', '_bx_tasks_page_title_sys_entries_budget_in_context_administration', '_bx_tasks_page_title_entries_budget_in_context_administration', 'bx_tasks', 13, 510, 1, '', '', '', '', 0, 1, 0, 'BxTasksPageAuthor', 'modules/boonex/tasks/classes/BxTasksPageAuthor.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_tasks_context_budget_administration', 2, 'bx_tasks', '_bx_tasks_page_block_title_sys_menu_in_context', '_bx_tasks_page_block_title_menu_in_context', 13, 2147483647, 'service', 'a:3:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:22:"get_block_menu_context";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 1, 1),
+('bx_tasks_context_budget_administration', 2, 'bx_tasks', '_bx_tasks_page_block_title_sys_entries_budget_in_context_summary', '_bx_tasks_page_block_title_entries_budget_in_context_summary', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:16:"get_block_budget";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 1, 2),
+('bx_tasks_context_budget_administration', 3, 'bx_tasks', '_bx_tasks_page_block_title_sys_entries_budget_in_context_administration', '_bx_tasks_page_block_title_entries_budget_in_context_administration', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:23:"get_block_manage_budget";s:6:"params";a:2:{i:0;s:14:"administration";i:1;s:12:"{profile_id}";}}', 0, 0, 1, 1);
+
 -- PAGE: pre lists and values in context
 INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
 ('bx_tasks_context_pre_values', '_bx_tasks_page_title_sys_context_pre_values', '_bx_tasks_page_title_context_pre_values', 'bx_tasks', 13, 2147483647, 1, 'tasks-context-values', 'page.php?i=tasks-context-values', '', '', '', 0, 1, 0, 'BxTasksPageAuthor', 'modules/boonex/tasks/classes/BxTasksPageAuthor.php');
@@ -271,8 +280,9 @@ INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `titl
 ('bx_tasks_view_context_submenu', 'bx_tasks', 'tasks-context', '_bx_tasks_menu_item_title_system_view_context_entries', '_bx_tasks_menu_item_title_view_context_entries', 'page.php?i=tasks-context&profile_id={profile_id}', '', '', '', '', 2147483647, '', 1, 0, 1),
 ('bx_tasks_view_context_submenu', 'bx_tasks', 'tasks-context-time', '_bx_tasks_menu_item_title_system_view_context_time', '_bx_tasks_menu_item_title_view_context_time', 'page.php?i=tasks-context-time&profile_id={profile_id}', '', '', '', '', 2147483647, '', 1, 0, 2),
 ('bx_tasks_view_context_submenu', 'bx_tasks', 'tasks-context-time-administration', '_bx_tasks_menu_item_title_system_view_context_time_administration', '_bx_tasks_menu_item_title_view_context_time_administration', 'page.php?i=tasks-context-time-administration&profile_id={profile_id}', '', '', '', '', 2147483647, '', 1, 0, 3),
-('bx_tasks_view_context_submenu', 'bx_tasks', 'tasks-context-values', '_bx_tasks_menu_item_title_system_manage_context_pre_values', '_bx_tasks_menu_item_title_manage_context_pre_values', 'page.php?i=tasks-context-values&profile_id={profile_id}', '', '', '', '', 2147483647, '', 1, 0, 4),
-('bx_tasks_view_context_submenu', 'bx_tasks', 'tasks-context-settings', '_bx_tasks_menu_item_title_system_manage_context_settings', '_bx_tasks_menu_item_title_manage_context_settings', 'page.php?i=tasks-context-settings&profile_id={profile_id}', '', '', '', '', 2147483647, '', 1, 0, 5);
+('bx_tasks_view_context_submenu', 'bx_tasks', 'tasks-context-budget-administration', '_bx_tasks_menu_item_title_system_view_context_budget_administration', '_bx_tasks_menu_item_title_view_context_budget_administration', 'page.php?i=tasks-context-budget-administration&profile_id={profile_id}', '', '', '', '', 2147483647, '', 1, 0, 4),
+('bx_tasks_view_context_submenu', 'bx_tasks', 'tasks-context-values', '_bx_tasks_menu_item_title_system_manage_context_pre_values', '_bx_tasks_menu_item_title_manage_context_pre_values', 'page.php?i=tasks-context-values&profile_id={profile_id}', '', '', '', '', 2147483647, '', 1, 0, 5),
+('bx_tasks_view_context_submenu', 'bx_tasks', 'tasks-context-settings', '_bx_tasks_menu_item_title_system_manage_context_settings', '_bx_tasks_menu_item_title_manage_context_settings', 'page.php?i=tasks-context-settings&profile_id={profile_id}', '', '', '', '', 2147483647, '', 1, 0, 6);
 
 -- MENU: use tools submenu
 INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
@@ -520,6 +530,24 @@ INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon
 ('bx_tasks_time_context_common', 'single', 'edit', '_bx_tasks_grid_action_title_tm_edit', 'pencil-alt', 1, 0, 1),
 ('bx_tasks_time_context_common', 'single', 'delete', '_bx_tasks_grid_action_title_tm_delete', 'remove', 1, 1, 2);
 
+-- GRIDS: moderation badget tools
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
+('bx_tasks_budget_context_administration', 'Sql', 'SELECT * FROM `bx_tasks_budget_track` WHERE 1 ', 'bx_tasks_budget_track', 'id', '', '', '', 50, NULL, 'start', '', 'tbt`.`text,tt`.`title,tt`.`text', '', 'like', 'date', '', 2147483647, 'BxTasksGridBudgetContextAdministration', 'modules/boonex/tasks/classes/BxTasksGridBudgetContextAdministration.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_tasks_budget_context_administration', 'checkbox', '_sys_select', '2%', 0, 0, '', 1),
+('bx_tasks_budget_context_administration', 'profile_id', '_bx_tasks_grid_column_title_bdt_profile_id', '30%', 0, 0, '', 2),
+('bx_tasks_budget_context_administration', 'text', '_bx_tasks_grid_column_title_bdt_text', '25%', 0, 32, '', 3),
+('bx_tasks_budget_context_administration', 'value', '_bx_tasks_grid_column_title_bdt_value', '10%', 0, 0, '', 4),
+('bx_tasks_budget_context_administration', 'date', '_bx_tasks_grid_column_title_bdt_date', '15%', 0, 0, '', 5),
+('bx_tasks_budget_context_administration', 'actions', '', '18%', 0, '', '', 6);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `order`) VALUES
+('bx_tasks_budget_context_administration', 'independent', 'add', '_bx_tasks_grid_action_title_bdt_add', '', 0, 0, 1),
+('bx_tasks_budget_context_administration', 'bulk', 'calculate', '_bx_tasks_grid_action_title_bdt_calculate', '', 0, 0, 1),
+('bx_tasks_budget_context_administration', 'single', 'edit', '_bx_tasks_grid_action_title_bdt_edit', 'pencil-alt', 1, 0, 1),
+('bx_tasks_budget_context_administration', 'single', 'delete', '_bx_tasks_grid_action_title_bdt_delete', 'remove', 1, 1, 2);
+
 -- GRIDS: pre lists and values
 INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
 ('bx_tasks_pre_values', 'Sql', 'SELECT `tpv`.*, `tpl`.`title` AS `list_title`, `tpl`.`use_color` AS `list_color`, `tpl`.`use_multiselect` AS `list_multiselect` FROM `bx_tasks_pre_values` AS `tpv` INNER JOIN `bx_tasks_pre_lists` AS `tpl` ON `tpv`.`list`=`tpl`.`name` WHERE 1 ', 'bx_tasks_pre_values', 'id', 'order', 'active', '', 20, NULL, 'start', '', 'tpv`.`name,tpv`.`title,tpl`.`title', '', 'like', '', '', 2147483647, 'BxTasksGridPreValues', 'modules/boonex/tasks/classes/BxTasksGridPreValues.php');

+ 48 - 0
modules/boonex/tasks/install/sql/install.sql

@@ -388,6 +388,26 @@ CREATE TABLE IF NOT EXISTS `bx_tasks_timers` (
   UNIQUE KEY `timer` (`content_id`, `profile_id`)
 );
 
+-- TABLE: budget
+CREATE TABLE IF NOT EXISTS `bx_tasks_budget` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `context_id` int(11) NOT NULL default '0',
+  `value_total` int(11) NOT NULL default '0',
+  `value_spent` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `context_id` (`context_id`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_tasks_budget_track` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `context_id` int(11) NOT NULL default '0',
+  `profile_id` int(11) NOT NULL default '0',
+  `value` int(11) NOT NULL default '0',
+  `text` text NOT NULL default '',
+  `date` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`)
+);
+
 -- TABLE: favorites
 CREATE TABLE `bx_tasks_favorites_track` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
@@ -696,6 +716,34 @@ INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_fo
 ('bx_tasks_time_edit', 'submit', 2147483647, 1, 8),
 ('bx_tasks_time_edit', 'cancel', 2147483647, 1, 9);
 
+-- FORMS: budget
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_tasks_budget', 'bx_tasks', '_bx_tasks_form_budget', '', '', 'submit', 'bx_tasks_budget_track', 'id', '', '', '', 0, 1, 'BxTasksFormBudget', 'modules/boonex/tasks/classes/BxTasksFormBudget.php');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_tasks_budget_add', 'bx_tasks', 'bx_tasks_budget', '_bx_tasks_form_display_budget_add', 0),
+('bx_tasks_budget_edit', 'bx_tasks', 'bx_tasks_budget', '_bx_tasks_form_display_budget_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_tasks_budget', 'bx_tasks', 'value', '', '', 0, 'text', '_bx_tasks_form_budget_input_sys_value', '_bx_tasks_form_budget_input_value', '_bx_tasks_form_budget_input_value_info', 1, 0, 0, '', '', '', 'avail', '', '_bx_tasks_form_budget_input_value_err', 'Int', '', 1, 0),
+('bx_tasks_budget', 'bx_tasks', 'text', '', '', 0, 'textarea', '_bx_tasks_form_budget_input_sys_text', '_bx_tasks_form_budget_input_text', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_tasks_budget', 'bx_tasks', 'submit', '_bx_tasks_form_budget_input_submit', '', 0, 'submit', '_bx_tasks_form_budget_input_sys_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_tasks_budget', 'bx_tasks', 'cancel', '_bx_tasks_form_budget_input_cancel', '', 0, 'button', '_bx_tasks_form_budget_input_sys_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 1, 0),
+('bx_tasks_budget', 'bx_tasks', 'controls', '', 'submit,cancel', 0, 'input_set', '_bx_tasks_form_budget_input_sys_controls', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_tasks_budget_add', 'value', 2147483647, 1, 1),
+('bx_tasks_budget_add', 'text', 2147483647, 1, 2),
+('bx_tasks_budget_add', 'controls', 2147483647, 1, 3),
+('bx_tasks_budget_add', 'submit', 2147483647, 1, 4),
+('bx_tasks_budget_add', 'cancel', 2147483647, 1, 5),
+
+('bx_tasks_budget_edit', 'value', 2147483647, 1, 1),
+('bx_tasks_budget_edit', 'text', 2147483647, 1, 2),
+('bx_tasks_budget_edit', 'controls', 2147483647, 1, 3),
+('bx_tasks_budget_edit', 'submit', 2147483647, 1, 4),
+('bx_tasks_budget_edit', 'cancel', 2147483647, 1, 5);
+
 -- PRE-VALUES
 INSERT INTO `sys_form_pre_lists`(`key`, `title`, `module`, `use_for_sets`) VALUES
 ('bx_tasks_types', '_bx_tasks_pre_lists_types', 'bx_tasks', '0');

+ 1 - 1
modules/boonex/tasks/install/sql/uninstall.sql

@@ -1,6 +1,6 @@
 
 -- TABLES
-DROP TABLE IF EXISTS `bx_tasks_tasks`, `bx_tasks_lists`, `bx_tasks_contexts`, `bx_tasks_assignments`, `bx_tasks_pre_lists`, `bx_tasks_pre_values`, `bx_tasks_filters`, `bx_tasks_covers`, `bx_tasks_files`, `bx_tasks_photos`, `bx_tasks_photos_resized`, `bx_tasks_videos`, `bx_tasks_videos_resized`, `bx_tasks_cmts`, `bx_tasks_cmts_notes`, `bx_tasks_votes`, `bx_tasks_votes_track`, `bx_tasks_reactions`, `bx_tasks_reactions_track`, `bx_tasks_views_track`, `bx_tasks_meta_keywords`, `bx_tasks_meta_locations`, `bx_tasks_meta_mentions`, `bx_tasks_reports`, `bx_tasks_reports_track`, `bx_tasks_time`, `bx_tasks_time_track`, `bx_tasks_timers`, `bx_tasks_favorites_track`, `bx_tasks_scores`, `bx_tasks_scores_track`;
+DROP TABLE IF EXISTS `bx_tasks_tasks`, `bx_tasks_lists`, `bx_tasks_contexts`, `bx_tasks_assignments`, `bx_tasks_pre_lists`, `bx_tasks_pre_values`, `bx_tasks_filters`, `bx_tasks_covers`, `bx_tasks_files`, `bx_tasks_photos`, `bx_tasks_photos_resized`, `bx_tasks_videos`, `bx_tasks_videos_resized`, `bx_tasks_cmts`, `bx_tasks_cmts_notes`, `bx_tasks_votes`, `bx_tasks_votes_track`, `bx_tasks_reactions`, `bx_tasks_reactions_track`, `bx_tasks_views_track`, `bx_tasks_meta_keywords`, `bx_tasks_meta_locations`, `bx_tasks_meta_mentions`, `bx_tasks_reports`, `bx_tasks_reports_track`, `bx_tasks_time`, `bx_tasks_time_track`, `bx_tasks_timers`, `bx_tasks_budget`, `bx_tasks_budget_track`, `bx_tasks_favorites_track`, `bx_tasks_scores`, `bx_tasks_scores_track`;
 
 -- STORAGES & TRANSCODERS
 DELETE FROM `sys_objects_storage` WHERE `object` LIKE 'bx_tasks_%';

+ 7 - 0
modules/boonex/tasks/template/block_budget.html

@@ -0,0 +1,7 @@
+<div class="bx-tasks-budget">
+    <div class="bx-tasks-budget-cnt">
+        <div class="bx-tasks-bc-total"><bx_text:_bx_tasks_txt_budget_total /><span class="pl-2">__total__</span></div>
+        <div class="bx-tasks-bc-spent"><bx_text:_bx_tasks_txt_budget_spent /><span class="pl-2">__spent__</span></div>
+        <div class="bx-tasks-bc-remains"><bx_text:_bx_tasks_txt_budget_remains /><span class="pl-2">__remains__</span></div>
+    </div>
+</div>

+ 19 - 0
modules/boonex/tasks/template/popup_budget.html

@@ -0,0 +1,19 @@
+<div class="bx-tasks-budget-form">
+    <div class="bx-tasks-budget-form-cnt">
+        __form__
+        <script language="javascript">
+            $(document).ready(function () {
+                $("#__form_id__").ajaxForm({ 
+                    dataType: "json",
+                    beforeSubmit: function (formData, jqForm, options) {
+                        bx_loading($("#__form_id__"), true);
+                    },
+                    success: function (data) {
+                        $(".bx-popup-applied:visible").dolPopupHide();
+                        glGrids.__object__.processJson(data, '__action__');
+                    }
+                });
+            });
+        </script>
+    </div>
+</div>