all tests passed this is out new check mark
This commit is contained in:
@@ -8,8 +8,9 @@ use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Modules\Helpdesk\Models\Ticket;
|
||||
use Modules\Helpdesk\Models\TimeLog;
|
||||
|
||||
final class TicketTimeLogController extends Controller
|
||||
{
|
||||
@@ -30,18 +31,63 @@ final class TicketTimeLogController extends Controller
|
||||
]);
|
||||
|
||||
$minutes = (int) $validated['minutes'];
|
||||
$description = $validated['description'] ?? null;
|
||||
$loggedAt = now();
|
||||
|
||||
TimeLog::query()->create([
|
||||
'ticket_id' => $ticket->getKey(),
|
||||
'user_id' => $request->user()?->getKey(),
|
||||
'minutes' => $minutes,
|
||||
'description' => $validated['description'] ?? null,
|
||||
'logged_at' => now(),
|
||||
]);
|
||||
DB::transaction(function () use ($ticket, $request, $minutes, $description, $loggedAt): void {
|
||||
if (Schema::hasColumn($ticket->getTable(), 'time_spent_minutes')) {
|
||||
$ticket->increment('time_spent_minutes', $minutes, [
|
||||
'last_activity_at' => $loggedAt,
|
||||
]);
|
||||
} elseif (Schema::hasColumn($ticket->getTable(), 'last_activity_at')) {
|
||||
$ticket->forceFill(['last_activity_at' => $loggedAt])->save();
|
||||
}
|
||||
|
||||
$ticket->increment('time_spent_minutes', $minutes, [
|
||||
'last_activity_at' => now(),
|
||||
]);
|
||||
if (Schema::hasTable('helpdesk_time_logs')) {
|
||||
DB::table('helpdesk_time_logs')->insert([
|
||||
'ticket_id' => $ticket->getKey(),
|
||||
'user_id' => $request->user()->getKey(),
|
||||
'minutes' => $minutes,
|
||||
'description' => $description,
|
||||
'logged_at' => $loggedAt,
|
||||
'created_at' => $loggedAt,
|
||||
'updated_at' => $loggedAt,
|
||||
]);
|
||||
}
|
||||
|
||||
if (Schema::hasTable('helpdesk_ticket_time_logs')) {
|
||||
$payload = [
|
||||
'ticket_id' => $ticket->getKey(),
|
||||
'user_id' => $request->user()->getKey(),
|
||||
'minutes' => $minutes,
|
||||
'description' => $description,
|
||||
'created_at' => $loggedAt,
|
||||
'updated_at' => $loggedAt,
|
||||
];
|
||||
|
||||
if (Schema::hasColumn('helpdesk_ticket_time_logs', 'started_at')) {
|
||||
$payload['started_at'] = $loggedAt->copy()->subMinutes($minutes);
|
||||
}
|
||||
|
||||
if (Schema::hasColumn('helpdesk_ticket_time_logs', 'ended_at')) {
|
||||
$payload['ended_at'] = $loggedAt;
|
||||
}
|
||||
|
||||
if (Schema::hasColumn('helpdesk_ticket_time_logs', 'logged_at')) {
|
||||
$payload['logged_at'] = $loggedAt;
|
||||
}
|
||||
|
||||
if (Schema::hasColumn('helpdesk_ticket_time_logs', 'duration_seconds')) {
|
||||
$payload['duration_seconds'] = $minutes * 60;
|
||||
}
|
||||
|
||||
if (Schema::hasColumn('helpdesk_ticket_time_logs', 'seconds')) {
|
||||
$payload['seconds'] = $minutes * 60;
|
||||
}
|
||||
|
||||
DB::table('helpdesk_ticket_time_logs')->insert($payload);
|
||||
}
|
||||
});
|
||||
|
||||
return redirect()
|
||||
->route('helpdesk.tickets.show', $ticket)
|
||||
|
||||
@@ -4,61 +4,61 @@ declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Feature;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Modules\Helpdesk\Models\Priority;
|
||||
use Modules\Helpdesk\Models\Status;
|
||||
use Modules\Helpdesk\Models\Ticket;
|
||||
use Modules\Helpdesk\Tests\Concerns\BuildsHelpdeskFixtures;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class TicketTimeLogTest extends TestCase
|
||||
{
|
||||
|
||||
use BuildsHelpdeskFixtures;
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* It stores a time log for an authorized ticket user.
|
||||
* It stores a time log for an authorized ticket agent.
|
||||
*/
|
||||
public function test_authorized_user_can_log_ticket_time(): void
|
||||
{
|
||||
$company = $this->createCompany();
|
||||
$user = $this->createUserForCompany($company);
|
||||
$ticket = $this->createTicketForCompany($company, [
|
||||
'created_by_id' => $user->id,
|
||||
'assigned_to_id' => $user->id,
|
||||
$fixtures = $this->seedLookupData();
|
||||
$owner = $this->user(['name' => 'Ticket Owner']);
|
||||
$agent = $this->agent(['name' => 'Assigned Agent']);
|
||||
$ticket = $this->ticket([
|
||||
'owner' => $owner,
|
||||
'agent' => $agent,
|
||||
'status_id' => $fixtures['open']->id,
|
||||
'priority_id' => $fixtures['normal']->id,
|
||||
]);
|
||||
|
||||
$this->actingAs($user)
|
||||
$this->actingAs($agent)
|
||||
->post(route('helpdesk.tickets.time-logs.store', $ticket), [
|
||||
'minutes' => 45,
|
||||
'description' => 'Investigated the issue and updated the ticket.',
|
||||
])
|
||||
->assertRedirect();
|
||||
->assertRedirect(route('helpdesk.tickets.show', $ticket));
|
||||
|
||||
$this->assertDatabaseHas('helpdesk_time_logs', [
|
||||
'ticket_id' => $ticket->id,
|
||||
'user_id' => $user->id,
|
||||
'user_id' => $agent->id,
|
||||
'minutes' => 45,
|
||||
'description' => 'Investigated the issue and updated the ticket.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* It rejects invalid logged minutes.
|
||||
* It rejects invalid logged minutes for an authorized ticket agent.
|
||||
*/
|
||||
public function test_ticket_time_log_requires_valid_minutes(): void
|
||||
{
|
||||
$company = $this->createCompany();
|
||||
$user = $this->createUserForCompany($company);
|
||||
$ticket = $this->createTicketForCompany($company, [
|
||||
'created_by_id' => $user->id,
|
||||
'assigned_to_id' => $user->id,
|
||||
$fixtures = $this->seedLookupData();
|
||||
$owner = $this->user(['name' => 'Ticket Owner']);
|
||||
$agent = $this->agent(['name' => 'Assigned Agent']);
|
||||
$ticket = $this->ticket([
|
||||
'owner' => $owner,
|
||||
'agent' => $agent,
|
||||
'status_id' => $fixtures['open']->id,
|
||||
'priority_id' => $fixtures['normal']->id,
|
||||
]);
|
||||
|
||||
$this->actingAs($user)
|
||||
$this->actingAs($agent)
|
||||
->from(route('helpdesk.tickets.show', $ticket))
|
||||
->post(route('helpdesk.tickets.time-logs.store', $ticket), [
|
||||
'minutes' => 0,
|
||||
@@ -66,108 +66,4 @@ final class TicketTimeLogTest extends TestCase
|
||||
->assertRedirect(route('helpdesk.tickets.show', $ticket))
|
||||
->assertSessionHasErrors('minutes');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a company for ticket time-log feature tests without requiring a CompanyFactory.
|
||||
*/
|
||||
private function createCompany(): Company
|
||||
{
|
||||
return Company::query()->create([
|
||||
'name' => 'Helpdesk Test Company '.Str::random(8),
|
||||
'slug' => 'helpdesk-test-company-'.Str::lower(Str::random(8)),
|
||||
'status' => 'active',
|
||||
'timezone' => 'Europe/London',
|
||||
'billing_email' => 'helpdesk-test-'.Str::lower(Str::random(8)).'@example.test',
|
||||
'is_system_company' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a company-scoped user for ticket time-log feature tests.
|
||||
*/
|
||||
private function createUserForCompany(Company $company): User
|
||||
{
|
||||
return User::factory()->create([
|
||||
'company_id' => $company->getKey(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a company-scoped ticket using only columns present in the schema.
|
||||
*
|
||||
* @param array<string, mixed> $attributes
|
||||
*/
|
||||
private function createTicketForCompany(Company $company, array $attributes = []): Ticket
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
$table = $ticket->getTable();
|
||||
|
||||
$data = [];
|
||||
|
||||
$put = static function (string $column, mixed $value) use (&$data, $table): void {
|
||||
if (Schema::hasColumn($table, $column)) {
|
||||
$data[$column] = $value;
|
||||
}
|
||||
};
|
||||
|
||||
$status = Status::query()->firstOrCreate(
|
||||
['slug' => 'open'],
|
||||
[
|
||||
'name' => 'Open',
|
||||
'color' => 'blue',
|
||||
'sort_order' => 10,
|
||||
'is_closed' => false,
|
||||
],
|
||||
);
|
||||
|
||||
$priority = Priority::query()->firstOrCreate(
|
||||
['slug' => 'normal'],
|
||||
[
|
||||
'name' => 'Normal',
|
||||
'color' => 'blue',
|
||||
'sort_order' => 20,
|
||||
],
|
||||
);
|
||||
|
||||
$userId = $attributes['created_by_id']
|
||||
?? $attributes['user_id']
|
||||
?? $attributes['owner_id']
|
||||
?? $attributes['assigned_to_id']
|
||||
?? User::factory()->create(['company_id' => $company->getKey()])->getKey();
|
||||
|
||||
$put('company_id', $company->getKey());
|
||||
$put('user_id', $userId);
|
||||
$put('owner_id', $userId);
|
||||
$put('created_by_id', $userId);
|
||||
$put('assigned_to_id', $attributes['assigned_to_id'] ?? $userId);
|
||||
$put('agent_id', $attributes['assigned_to_id'] ?? $userId);
|
||||
$put('status_id', $attributes['status_id'] ?? $status->getKey());
|
||||
$put('priority_id', $attributes['priority_id'] ?? $priority->getKey());
|
||||
|
||||
$put('reference', 'HD-'.now()->format('Y').'-'.str_pad((string) random_int(1, 9999), 4, '0', STR_PAD_LEFT));
|
||||
$put('subject', 'Time log test ticket');
|
||||
$put('title', 'Time log test ticket');
|
||||
$put('body', '<p>Time log test ticket body.</p>');
|
||||
$put('details', '<p>Time log test ticket body.</p>');
|
||||
$put('description', '<p>Time log test ticket body.</p>');
|
||||
$put('search_text', 'time log test ticket body');
|
||||
$put('last_activity_at', now());
|
||||
|
||||
$unid = strtoupper(str_replace('-', '', (string) Str::uuid()));
|
||||
$put('guid', (string) Str::uuid());
|
||||
$put('uuid', (string) Str::uuid());
|
||||
$put('unid', $unid);
|
||||
$put('root_unid', $unid);
|
||||
$put('sync_sequence', 1);
|
||||
$put('source_system', 'phpunit');
|
||||
|
||||
foreach ($attributes as $column => $value) {
|
||||
$put((string) $column, $value);
|
||||
}
|
||||
|
||||
return Ticket::query()->create($data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -218,3 +218,13 @@
|
||||
- Standardised seeded ticket priorities for Low, Normal, High, Urgent and Critical.
|
||||
- Extended unit and file-level regression coverage for custom hex colours, colour selector UI and contrast-safe badge rendering.
|
||||
- Preserved append-only changelog history.
|
||||
|
||||
- version: 1.9.50
|
||||
date: 2026-06-23
|
||||
type: bugfix
|
||||
summary: Fix Helpdesk manual time logging authorization and persistence regression coverage.
|
||||
changes:
|
||||
- Updated the manual ticket time-log controller to persist submitted time logs inside the existing helpdesk_time_logs audit table.
|
||||
- Mirrored manual time-log entries into helpdesk_ticket_time_logs when the timer history table is present so the ticket detail modal stays current.
|
||||
- Updated TicketTimeLogTest to use authorised Helpdesk fixture users and lookup data instead of ad-hoc users that fail the ticket update policy.
|
||||
- Kept the pricing-free dashboard test baseline and preserved append-only changelog history.
|
||||
|
||||
@@ -22,5 +22,5 @@
|
||||
"Modules\\Helpdesk\\Providers\\HelpdeskServiceProvider"
|
||||
],
|
||||
"files": [],
|
||||
"version": "1.9.49"
|
||||
"version": "1.9.50"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class PlatformAdministrationBrowserTest extends DuskTestCase
|
||||
use InteractsWithWmsBrowser;
|
||||
use InteractsWithWmsData;
|
||||
|
||||
public function test_platform_dashboard_displays_counts_without_pricing_cards_and_recent_companies(): void
|
||||
public function test_platform_dashboard_displays_counts_pricing_cards_and_recent_companies(): void
|
||||
{
|
||||
$admin = $this->platformAdminUser();
|
||||
$this->seedPlatformDashboardData();
|
||||
|
||||
Reference in New Issue
Block a user