Refactor task management and add attachment functionality

- Updated task editing template to handle default status for new tasks and corrected variable names.
- Enhanced work time reporting by rounding time to the nearest quarter hour and adjusting amount formatting.
- Introduced TasksController to manage task-related operations, including status resolution and email notifications.
- Added TaskAttachmentRepository for handling task attachments, including upload, rename, and delete functionalities.
- Implemented WorkTimeRepository to fetch clients with unsettled tasks and calculate total work time.
- Created unit tests for TasksController and TaskAttachmentRepository to ensure functionality and correctness.
This commit is contained in:
2026-02-06 23:11:48 +01:00
parent 1722f171bc
commit 47ffc19a23
18 changed files with 546 additions and 79 deletions

View File

@@ -122,7 +122,7 @@ if ( is_array( $this -> parent_tasks ) )
'label' => 'Status',
'name' => 'status',
'id' => 'status',
'value' => $this -> task[ 'status' ],
'value' => $this -> task[ 'id' ] ? $this -> task[ 'status' ] : 5,
'values' => \factory\Tasks::get_statuses()
] );?>
<!-- priorytet -->
@@ -157,7 +157,7 @@ if ( is_array( $this -> priorities ) )
'label' => 'Powiadom o zmianie statusu',
'name' => 'status_change_mail',
'id' => 'status_change_mail',
'checked' => ( $this -> task[ 'status_change_mail' ] or !$this -> taks['id'] ) ? true : false,
'checked' => ( $this -> task[ 'status_change_mail' ] or !$this -> task['id'] ) ? true : false,
'type' => 'checkbox'
] );
?>
@@ -191,11 +191,11 @@ if ( is_array( $this -> priorities ) )
'type' => 'checkbox'
] );
?>
<div class="form_group" id="recursive-details">
<div class="form_group" id="recursive-details" style="<?= $this -> task[ 'recursively' ] ? '' : 'display: none;';?>">
<label class="label">Powtarzaj co:</label>
<div class="input">
<input type="text" class="form-control" name="frequency" value="<?= $this -> task[ 'id' ] ? $this -> task[ 'frequency' ] : 1;?>">
<select name="period" class="form-control">
<div class="input" style="display: flex; gap: 10px; align-items: center;">
<input type="text" class="form-control" name="frequency" style="max-width: 110px;" value="<?= $this -> task[ 'id' ] ? $this -> task[ 'frequency' ] : 1;?>">
<select name="period" class="form-control" style="max-width: 140px;">
<option value="1" <? if ( $this -> task[ 'period' ] == '1' ):?>selected="selected"<? endif;?>>dni</option>
<option value="2" <? if ( $this -> task[ 'period' ] == '2' ):?>selected="selected"<? endif;?>>m-ce</option>
<option value="3" <? if ( $this -> task[ 'period' ] == '3' ):?>selected="selected"<? endif;?>>lata</option>
@@ -216,7 +216,7 @@ if ( is_array( $this -> priorities ) )
'label' => 'Pokaż w kalendarzu',
'name' => 'show_in_calendar',
'id' => 'show_in_calendar',
'checked' => ( $this -> task[ 'show_in_calendar' ] or !$this -> taks['id'] ) ? true : false,
'checked' => ( $this -> task[ 'show_in_calendar' ] or !$this -> task['id'] ) ? true : false,
'type' => 'checkbox'
] );
?>
@@ -262,11 +262,24 @@ echo $grid -> draw();
<script type="text/javascript">
$(document).ready(function ()
{
function toggleRecursiveDetails() {
if ( $( '#recursively' ).is( ':checked' ) )
$( '#recursive-details' ).show();
else
$( '#recursive-details' ).hide();
}
$('input[type="checkbox"]').iCheck({
checkboxClass: 'icheckbox_square-blue',
radioClass: 'iradio_square-blue',
});
toggleRecursiveDetails();
$( '#recursively' ).on( 'ifChanged change', function() {
toggleRecursiveDetails();
});
$( 'body' ).on( 'click', '.task-remove', function(e)
{
e.preventDefault();
@@ -323,7 +336,19 @@ echo $grid -> draw();
});
$( '#project_id, #client_id, #status' ).select2({
theme: 'bootstrap-5'
theme: 'bootstrap-5',
minimumResultsForSearch: 0
});
$( '#project_id, #client_id, #status' ).on( 'select2:open', function() {
setTimeout( function() {
var search_field = document.querySelector( '.select2-container--open .select2-search__field' );
if ( search_field )
{
search_field.focus();
search_field.select();
}
}, 0 );
});
});
</script>
</script>

View File

@@ -4,8 +4,16 @@ $format_time = function( $seconds ) {
return sprintf( "%02d%s%02d%s%02d", floor( $seconds / 3600 ), ':', ( $seconds / 60 ) % 60, ':', $seconds % 60 );
};
$round_time_to_quarter = function( $seconds ) {
$seconds = (int)$seconds;
if ( $seconds <= 0 )
return 0;
return (int)( round( $seconds / 900 ) * 900 );
};
$format_amount = function( $amount ) {
return number_format( (float)$amount, 2, '.', '' ) . ' z&#322;';
return number_format( (float)$amount, 0, '.', '' ) . ' z&#322;';
};
$billing_clients = [];
@@ -37,11 +45,12 @@ foreach ( $this -> work_time_clients as $client )
foreach ( $tasks as $task )
{
$task_time = isset( $task['time'] ) ? (int)$task['time'] : 0;
$task_time = $round_time_to_quarter( $task_time );
if ( isset( $task['pay_rate'] ) and $task['pay_rate'] !== null and $task['pay_rate'] !== '' )
$task_amount = (float)$task['pay_rate'];
$task_amount = round( (float)$task['pay_rate'] );
else
$task_amount = (float)$this -> settings['hourly_rate'] * ( $task_time / 3600 );
$task_amount = round( (float)$this -> settings['hourly_rate'] * ( $task_time / 3600 ) );
$summary['tasks_count']++;
$summary['time'] += $task_time;
@@ -122,7 +131,7 @@ usort( $billing_clients, function( $a, $b ) {
<tbody>
<? foreach ( $billing_clients as $summary ):?>
<? $details_id = 'billing-details-' . md5( $summary['firm'] );?>
<tr class="billing-client-row" data-details-id="<?= $details_id;?>" data-tasks-count="<?= $summary['tasks_count'];?>" data-time="<?= $summary['time'];?>" data-amount="<?= number_format( (float)$summary['amount'], 2, '.', '' );?>">
<tr class="billing-client-row" data-details-id="<?= $details_id;?>" data-tasks-count="<?= $summary['tasks_count'];?>" data-time="<?= $summary['time'];?>" data-amount="<?= number_format( (float)$summary['amount'], 0, '.', '' );?>">
<td class="billing-client-name"><a href="#<?= $summary['firm'];?>"><?= $summary['firm'];?></a></td>
<td class="text-center billing-client-tasks"><?= $summary['tasks_count'];?></td>
<td class="text-center billing-client-time"><?= $format_time( $summary['time'] );?></td>
@@ -145,7 +154,7 @@ usort( $billing_clients, function( $a, $b ) {
</thead>
<tbody>
<? foreach ( $summary['rows'] as $row ):?>
<tr class="billing-task-row" data-task-time="<?= (int)$row['time'];?>" data-task-amount="<?= number_format( (float)$row['amount'], 2, '.', '' );?>">
<tr class="billing-task-row" data-task-time="<?= (int)$row['time'];?>" data-task-amount="<?= number_format( (float)$row['amount'], 0, '.', '' );?>">
<td><?= $row['month'];?></td>
<td><?= $row['name'];?></td>
<td class="text-center"><?= $format_time( $row['time'] );?></td>
@@ -184,7 +193,7 @@ usort( $billing_clients, function( $a, $b ) {
function formatAmount( amount ) {
amount = parseFloat( amount ) || 0;
return amount.toFixed( 2 ) + ' z\u0142';
return Math.round( amount ) + ' z\u0142';
}
function refreshGlobalKpis() {
@@ -311,7 +320,7 @@ usort( $billing_clients, function( $a, $b ) {
{
summary_row.attr( 'data-tasks-count', client_tasks_count );
summary_row.attr( 'data-time', client_time );
summary_row.attr( 'data-amount', client_amount.toFixed( 2 ) );
summary_row.attr( 'data-amount', Math.round( client_amount ) );
summary_row.find( '.billing-client-tasks' ).text( client_tasks_count );
summary_row.find( '.billing-client-time' ).text( formatTime( client_time ) );
summary_row.find( '.billing-client-amount' ).text( formatAmount( client_amount ) );