Skip to content

Commit

Permalink
tweak(Timetracker TS searchCount) include HR data in summary, added c…
Browse files Browse the repository at this point in the history
…leared amount property
  • Loading branch information
paulmhh committed Sep 20, 2024
1 parent 8302290 commit d56503b
Show file tree
Hide file tree
Showing 19 changed files with 239 additions and 18 deletions.
29 changes: 29 additions & 0 deletions tests/tine20/Timetracker/JsonTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,35 @@ public function testSearchTimeaccountsWithTAFilter()
$this->assertEquals($timeaccountData['id'], $searchResult['filter'][0]['value']['id']);
}

public function testExtendedTSSearchCountData()
{
$dailyWTRTest = new HumanResources_Controller_DailyWTReportTests();
$dailyWTRTest->setUp();

$dailyWTRTest->testCalculateReportsForEmployeeTimesheetsWithStartAndEnd();

$employee = HumanResources_Controller_Employee::getInstance()->search(
Tinebase_Model_Filter_FilterGroup::getFilterForModel(HumanResources_Model_Employee::class, [
[Tinebase_Model_Filter_Abstract::FIELD => 'account_id', Tinebase_Model_Filter_Abstract::OPERATOR => Tinebase_Model_Filter_Abstract::OP_EQUALS, Tinebase_Model_Filter_Abstract::VALUE => Tinebase_Core::getUser()->getId()]
]))->getFirstRecord();

$fromUntil = ['from' => new Tinebase_DateTime('2018-08-01 00:00:00'), 'until' => new Tinebase_DateTime('2018-08-31 00:00:00')];
$contract = HumanResources_Controller_Contract::getInstance()->getValidContracts($fromUntil, $employee->getId())->getFirstRecord();
$contract->{HumanResources_Model_Contract::FLD_YEARLY_TURNOVER_GOAL} = 100;
HumanResources_Controller_Contract::getInstance()->update($contract);

$result = $this->_json->searchTimesheets([
['field' => 'start_date', 'operator' => 'within', 'value' => $fromUntil], //['from' => '2018-08-01 00:00:00', 'until' => '2018-08-31 00:00:00']],
['field' => 'account_id', 'operator' => 'equals', 'value' => Tinebase_Core::getUser()->getId()],
], []);

// 31 days out of 365
$this->assertSame(round(100 * 31 / 365, 2), $result['turnOverGoal']);
// 23 working days in august 2018
$this->assertSame(23 * 8 * 3600, $result['workingTimeTarget']);
$this->assertSame(0, $result['clearedAmount']);
}

/**
* try to get a Timesheet with a timeaccount_id filter
*/
Expand Down
6 changes: 2 additions & 4 deletions tine20/Addressbook/Controller/Contact.php
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,7 @@ protected function _inspectAfterUpdate($updatedRecord, $record, $currentRecord)
array('field' => $updatedRecord->getIdProperty(), 'operator' => 'equals', 'value' => $updatedRecord->getId())
));

// record does not match the filter, attention searchCount returns a STRING! "1"...
if ($this->searchCount($filter) != 1) {
if ($this->searchCount($filter) !== 1) {

if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
. ' record did not match filter of syncBackend "' . $backendId . '"');
Expand Down Expand Up @@ -503,8 +502,7 @@ protected function _inspectAfterCreate($_createdRecord, Tinebase_Record_Interfac
array('field' => $_createdRecord->getIdProperty(), 'operator' => 'equals', 'value' => $_createdRecord->getId())
));

// record does not match the filter, attention searchCount returns a STRING! "1"...
if ($this->searchCount($filter) != 1) {
if ($this->searchCount($filter) !== 1) {

if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
. ' record did not match filter of syncBackend "' . $backendId . '"');
Expand Down
3 changes: 1 addition & 2 deletions tine20/Addressbook/Frontend/Cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,7 @@ public function syncbackends($_opts)
array('field' => $contact->getIdProperty(), 'operator' => 'equals', 'value' => $contact->getId())
));

// record does not match the filter, attention searchCount returns a STRING! "1"...
if ($controller->searchCount($filter) != 1) {
if ($controller->searchCount($filter) !== 1) {

if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
. ' record did not match filter of syncBackend "' . $backendId . '"');
Expand Down
1 change: 1 addition & 0 deletions tine20/GDPR/Controller/DataIntendedPurposeRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ protected function checkAgreeWithdrawDates(GDPR_Model_DataIntendedPurposeRecord
if (null !== $_record->getId()) {
$filter[] = [TMFA::FIELD => TMCC::ID, TMFA::OPERATOR => 'not', TMFA::VALUE => $_record->getId()];
}
/** @phpstan-ignore-next-line */
if ($this->searchCount(Tinebase_Model_Filter_FilterGroup::getFilterForModel($this->_modelName, $filter)) > 0) {
throw new Tinebase_Exception_Record_Validation('agreeDate and withdrawDate must not overlap');
}
Expand Down
3 changes: 3 additions & 0 deletions tine20/HumanResources/Controller/Contract.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ protected function _inspectBookedResources(HumanResources_Model_Contract $contra
$dwtrCtrl = HumanResources_Controller_DailyWTReport::getInstance();
$dwtrRaii = new Tinebase_RAII($dwtrCtrl->assertPublicUsage());

/** @phpstan-ignore-next-line */
if ($ftCtrl->searchCount(Tinebase_Model_Filter_FilterGroup::getFilterForModel(
HumanResources_Model_FreeTime::class, array_merge([
['field' => 'lastday_date', 'operator' => 'after_or_equals', 'value' => $contract->start_date],
Expand All @@ -143,6 +144,7 @@ protected function _inspectBookedResources(HumanResources_Model_Contract $contra
return false;
}

/** @phpstan-ignore-next-line */
if ($dwtrCtrl->searchCount(Tinebase_Model_Filter_FilterGroup::getFilterForModel(
HumanResources_Model_DailyWTReport::class, array_merge([
['field' => 'date', 'operator' => 'after_or_equals', 'value' => $contract->start_date],
Expand Down Expand Up @@ -257,6 +259,7 @@ protected function _checkDateOverlap(HumanResources_Model_Contract $_record)
['field' => 'start_date', 'operator' => 'before_or_equals', 'value' => $_record->end_date],
]));

/** @phpstan-ignore-next-line */
if ($this->searchCount($filter) > 0) {
$translation = Tinebase_Translation::getTranslation($this->_applicationName);
throw new Tinebase_Exception_SystemGeneric($translation->_('Contracts may not overlap'));
Expand Down
3 changes: 3 additions & 0 deletions tine20/HumanResources/Controller/FreeTime.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,15 @@ protected function _inspect(HumanResources_Model_FreeTime $record, ?HumanResourc
$dwtrCtrl = HumanResources_Controller_DailyWTReport::getInstance();
$dwtrRaii = new Tinebase_RAII($dwtrCtrl->assertPublicUsage());

/** @phpstan-ignore-next-line */
if (($oldRecord && $dwtrCtrl->searchCount(Tinebase_Model_Filter_FilterGroup::getFilterForModel(
HumanResources_Model_DailyWTReport::class,[
['field' => 'date', 'operator' => 'after_or_equals', 'value' => $oldRecord->firstday_date],
['field' => 'employee_id', 'operator' => 'equals', 'value' => $oldRecord->employee_id],
['field' => 'is_cleared', 'operator' => 'equals', 'value' => true],
['field' => 'date', 'operator' => 'before_or_equals', 'value' => $oldRecord->lastday_date],
])) > 0) ||
/** @phpstan-ignore-next-line */
$dwtrCtrl->searchCount(Tinebase_Model_Filter_FilterGroup::getFilterForModel(
HumanResources_Model_DailyWTReport::class,[
['field' => 'date', 'operator' => 'after_or_equals', 'value' => $record->firstday_date],
Expand Down Expand Up @@ -270,6 +272,7 @@ protected function _inspectDelete(array $_ids)
$dwtrRaii = new Tinebase_RAII($dwtrCtrl->assertPublicUsage());
/** @var HumanResources_Model_FreeTime $freeTime */
foreach ($this->getMultiple($_ids, true) as $freeTime) {
/** @phpstan-ignore-next-line */
if ($dwtrCtrl->searchCount(Tinebase_Model_Filter_FilterGroup::getFilterForModel(
HumanResources_Model_DailyWTReport::class,[
['field' => 'date', 'operator' => 'after_or_equals', 'value' => $freeTime->firstday_date],
Expand Down
3 changes: 3 additions & 0 deletions tine20/HumanResources/Controller/WorkingTimeScheme.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ protected function _checkGrant($_record, $_action, $_throw = TRUE, $_errorMessag
$divisionCtrl = HumanResources_Controller_Division::getInstance();
$oldValue = $divisionCtrl->doContainerACLChecks(false);
try {
/** @phpstan-ignore-next-line */
if (0 < $divisionCtrl->searchCount($filter)) {
return true;
}
Expand All @@ -100,6 +101,7 @@ protected function _checkGrant($_record, $_action, $_throw = TRUE, $_errorMessag
}

// if we see a contract with this working time scheme, we do see the working time scheme
/** @phpstan-ignore-next-line */
if (0 < HumanResources_Controller_Contract::getInstance()->searchCount(
Tinebase_Model_Filter_FilterGroup::getFilterForModel(HumanResources_Model_Contract::class, [
['field' => HumanResources_Model_Contract::FLD_WORKING_TIME_SCHEME, 'operator' => 'equals', 'value' => $_record->getId()],
Expand Down Expand Up @@ -150,6 +152,7 @@ public function checkFilterACL(Tinebase_Model_Filter_FilterGroup $_filter, $_act
$divisionCtrl = HumanResources_Controller_Division::getInstance();
$oldValue = $divisionCtrl->doContainerACLChecks(false);
try {
/** @phpstan-ignore-next-line */
if (0 < $divisionCtrl->searchCount($filter)) {
// add where type !== individual
$orWrapper->addFilter(
Expand Down
1 change: 1 addition & 0 deletions tine20/OnlyOfficeIntegrator/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,7 @@ protected function doSave($requestData, $token)
}

// if there was no token created in the meantime, we allow the save anyway, otherwise we save a conflict
/** @phpstan-ignore-next-line */
if (OnlyOfficeIntegrator_Controller_AccessToken::getInstance()->searchCount(
Tinebase_Model_Filter_FilterGroup::getFilterForModel(OnlyOfficeIntegrator_Model_AccessToken::class, [
['field' => OnlyOfficeIntegrator_Model_AccessToken::FLDS_TOKEN, 'operator' => 'not',
Expand Down
1 change: 1 addition & 0 deletions tine20/OnlyOfficeIntegrator/Controller/AccessToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ public function reactivateTokens(Tinebase_Record_RecordSet $tokens)
continue;
}
if ($timeLimit->isLater($token->{OnlyOfficeIntegrator_Model_AccessToken::FLDS_LAST_SEEN}) ||
/** @phpstan-ignore-next-line */
$this->searchCount(
Tinebase_Model_Filter_FilterGroup::getFilterForModel(OnlyOfficeIntegrator_Model_AccessToken::class, [
['field' => OnlyOfficeIntegrator_Model_AccessToken::FLDS_NODE_ID, 'operator' => 'equals',
Expand Down
1 change: 1 addition & 0 deletions tine20/Tasks/Controller/Task.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ protected function _checkGrant($_record, $_action, $_throw = TRUE, $_errorMessag

if (!$result && (self::ACTION_GET === $_action || ($_oldRecord && self::ACTION_UPDATE === $_action))) {
// check attendees for Tinebase_Core::getUser()->contact_id
/** @phpstan-ignore-next-line */
$result = Tasks_Controller_Attendee::getInstance()->searchCount(
Tinebase_Model_Filter_FilterGroup::getFilterForModel(Tasks_Model_Attendee::class, [
[TMFA::FIELD => Tasks_Model_Attendee::FLD_TASK_ID, TMFA::OPERATOR => TMFA::OP_EQUALS, TMFA::VALUE => $_record->getId()],
Expand Down
3 changes: 2 additions & 1 deletion tine20/Timetracker/Backend/Timesheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ public function __construct($_dbAdapter = NULL, $_options = array())
$this->_additionalSearchCountCols = array(
'is_billable_combined' => null, // taken from _foreignTables
'duration' => 'duration',
'accounting_time_billable' => null // taken from _foreignTables
'accounting_time_billable' => null, // taken from _foreignTables
Timetracker_Model_Timesheet::FLD_CLEARED_AMOUNT => Timetracker_Model_Timesheet::FLD_CLEARED_AMOUNT,
);

$this->_foreignTables['is_billable_combined']['select'] = array(
Expand Down
104 changes: 101 additions & 3 deletions tine20/Timetracker/Controller/Timesheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
* @subpackage Controller
* @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
* @author Philipp Schüle <p.schuele@metaways.de>
* @copyright Copyright (c) 2007-2023 Metaways Infosystems GmbH (http://www.metaways.de)
* @copyright Copyright (c) 2007-2024 Metaways Infosystems GmbH (http://www.metaways.de)
*/

use Tinebase_Model_Filter_Abstract as TMFA;

/**
* Timesheet controller class for Timetracker application
*
Expand Down Expand Up @@ -259,7 +261,79 @@ protected function _checkDeadline(Timetracker_Model_Timesheet $_record, $_throwE
}

/****************************** overwritten functions ************************/


public function searchCount(Tinebase_Model_Filter_FilterGroup $_filter, $_action = self::ACTION_GET)
{
$result = parent::searchCount($_filter, $_action);

if (class_exists('HumanResources_Config') && Tinebase_Application::getInstance()->isInstalled(HumanResources_Config::APP_NAME, true)
&& ($periodFilter = $_filter->findFilterWithoutOr('start_date'))
&& ($aFilter = $_filter->findFilterWithoutOr('account_id')) && $aFilter->getOperator() === TMFA::OP_EQUALS
&& ($accountId = $aFilter->toArray()['value'] ?? null)) {
$oldEmployeeAcl = HumanResources_Controller_Employee::getInstance()->doContainerACLChecks(false);
try {
$employee = HumanResources_Controller_Employee::getInstance()->search(
Tinebase_Model_Filter_FilterGroup::getFilterForModel(HumanResources_Model_Employee::class, [
[TMFA::FIELD => 'account_id', TMFA::OPERATOR => TMFA::OP_EQUALS, TMFA::VALUE => $accountId]
]))->getFirstRecord();
} finally {
HumanResources_Controller_Employee::getInstance()->doContainerACLChecks($oldEmployeeAcl);
}

// ATTENTION employee has been retrieved without ACL check, this code here only used the employee id. Do not use employee data unless checking ACL first!
if ($employee) {
/** @var Tinebase_Model_Filter_Date $periodFilter */
try {
$fromUntil = ['from' => $periodFilter->getStartOfPeriod(), 'until' => $periodFilter->getEndOfPeriod()];
} catch (Tinebase_Exception_UnexpectedValue) {
return $result;
}
$from = $fromUntil['from'];
$from->hasTime(false);
$until = $fromUntil['until'];
$until->hasTime(false);

// get contracts
$contracts = HumanResources_Controller_Contract::getInstance()->getValidContracts($fromUntil, $employee->getId());
$turnOverGoal = 0;
/** @var HumanResources_Model_Contract $contract */
foreach ($contracts as $contract) {
if (0 === ($yGoal = (int)$contract->{HumanResources_Model_Contract::FLD_YEARLY_TURNOVER_GOAL})) {
continue;
}
$f = ($from->isLater($contract->start_date) ? $from : $contract->start_date)->getClone();
$u = (!$contract->end_date || $until->isEarlier($contract->end_date) ? $until : $contract->end_date)
->getClone();

$multiplier = 0.0;
for (;(int)$f->format('Y') < (int)$u->format('Y'); $f->addYear(1)) {
$daysOfYear = $f->format('L') === '1' ? 366 : 365;
$multiplier += ($daysOfYear - (int)$f->format('z')) / $daysOfYear;
$f->setDate((int)$f->format('Y'), 1, 1);
}
$daysOfYear = $u->format('L') === '1' ? 366 : 365;
$multiplier += ((int)$u->format('z') - (int)$f->format('z') + 1) / $daysOfYear;

$turnOverGoal += round($yGoal * $multiplier, 2);
}
$result['turnOverGoal'] = $turnOverGoal;

// get dailyWTRs
$workingTarget = 0;
/** @var HumanResources_Model_DailyWTReport $dailyWTR */
foreach (HumanResources_Controller_DailyWTReport::getInstance()->search(Tinebase_Model_Filter_FilterGroup::getFilterForModel(HumanResources_Model_DailyWTReport::class, [
[TMFA::FIELD => 'employee_id', TMFA::OPERATOR => TMFA::OP_EQUALS, TMFA::VALUE => $employee->getId()],
[TMFA::FIELD => 'date', TMFA::OPERATOR => 'within', TMFA::VALUE => ['from' => $from, 'until' => $until]],
])) as $dailyWTR) {
$workingTarget += $dailyWTR->getShouldWorkingTime();
}
$result['workingTimeTarget'] = $workingTarget;
}
}

return $result;
}

/**
* inspect creation of one record
*
Expand All @@ -273,6 +347,7 @@ protected function _inspectBeforeCreate(Tinebase_Record_Interface $_record)
/** @var Timetracker_Model_Timesheet $_record */
$this->_checkDeadline($_record);
$this->_calculateTimes($_record);
$this->_calcClearedAmount($_record);
}

protected function _inspectAfterCreate($_createdRecord, Tinebase_Record_Interface $_record)
Expand All @@ -297,7 +372,7 @@ protected function _inspectBeforeUpdate($_record, $_oldRecord)
/** @var Timetracker_Model_Timesheet $_record */
$this->_checkDeadline($_record);
$this->_calculateTimes($_record);

$this->_calcClearedAmount($_record, $_oldRecord);
}

protected function _inspectAfterUpdate($updatedRecord, $record, $currentRecord)
Expand All @@ -312,6 +387,29 @@ protected function _inspectAfterUpdate($updatedRecord, $record, $currentRecord)
}
}

protected function _calcClearedAmount(Timetracker_Model_Timesheet $ts, ?Timetracker_Model_Timesheet $oldTs = null): void
{
if (!$ts->is_cleared) {
$ts->{Timetracker_Model_Timesheet::FLD_CLEARED_AMOUNT} = null;
return;
}
if ($oldTs?->is_cleared) {
$ts->{Timetracker_Model_Timesheet::FLD_CLEARED_AMOUNT} = $oldTs?->{Timetracker_Model_Timesheet::FLD_CLEARED_AMOUNT};
return;
}

$taCtrl = Timetracker_Controller_Timeaccount::getInstance();
$oldAcl = $taCtrl->doContainerACLChecks(false);
try {
/** @var Timetracker_Model_Timeaccount $ta */
$ta = Timetracker_Controller_Timeaccount::getInstance()->get($ts->getIdFromProperty('timeaccount_id'));
} finally {
$taCtrl->doContainerACLChecks($oldAcl);
}

$ts->{Timetracker_Model_Timesheet::FLD_CLEARED_AMOUNT} = round(($ts->accounting_time / 60) * (int)$ta->price, 2);
}

protected function _tsChanged(Timetracker_Model_Timesheet $record, ?Timetracker_Model_Timesheet $oldRecord = null)
{
$event = new Tinebase_Event_Record_Update();
Expand Down
7 changes: 7 additions & 0 deletions tine20/Timetracker/Frontend/Json.php
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,13 @@ protected function _getSearchTotalCount($filter, $pagination, $controller, $tota
$totalresult['totalsum'] = $result['sum_duration'];
$totalresult['totalsumbillable'] = $result['sum_accounting_time_billable'];
$totalresult['totalcount'] = $result['count'];
$totalresult['clearedAmount'] = (int)$result['sum_cleared_amount'];
if (isset($result['turnOverGoal'])) {
$totalresult['turnOverGoal'] = $result['turnOverGoal'];
}
if (isset($result['workingTimeTarget'])) {
$totalresult['workingTimeTarget'] = $result['workingTimeTarget'];
}

return $totalresult;
} else {
Expand Down
Loading

0 comments on commit d56503b

Please sign in to comment.