mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 00:02:15 +01:00
fix admin panel time entry save and update, fixes #997
This commit is contained in:
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\TimeEntryResource\Pages;
|
use App\Filament\Resources\TimeEntryResource\Pages;
|
||||||
|
use App\Models\Member;
|
||||||
use App\Models\TimeEntry;
|
use App\Models\TimeEntry;
|
||||||
use Filament\Forms\Components\DateTimePicker;
|
use Filament\Forms\Components\DateTimePicker;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
@@ -16,6 +17,7 @@ use Filament\Tables;
|
|||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Filters\SelectFilter;
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
class TimeEntryResource extends Resource
|
class TimeEntryResource extends Resource
|
||||||
{
|
{
|
||||||
@@ -51,15 +53,23 @@ class TimeEntryResource extends Resource
|
|||||||
->rules([
|
->rules([
|
||||||
'after_or_equal:start',
|
'after_or_equal:start',
|
||||||
]),
|
]),
|
||||||
Select::make('user_id')
|
Select::make('member_id')
|
||||||
->relationship(name: 'user', titleAttribute: 'email')
|
->relationship(
|
||||||
->searchable(['name', 'email'])
|
name: 'member',
|
||||||
|
titleAttribute: 'id',
|
||||||
|
modifyQueryUsing: fn (Builder $query) => $query->with(['user', 'organization'])
|
||||||
|
)
|
||||||
|
->getOptionLabelFromRecordUsing(fn (Member $record): string => $record->user->email.' ('.$record->organization->name.')')
|
||||||
|
->searchable()
|
||||||
->required(),
|
->required(),
|
||||||
Select::make('project_id')
|
Select::make('project_id')
|
||||||
->relationship(name: 'project', titleAttribute: 'name')
|
->relationship(name: 'project', titleAttribute: 'name')
|
||||||
->searchable(['name'])
|
->searchable(['name'])
|
||||||
->nullable(),
|
->nullable(),
|
||||||
// TODO
|
Select::make('task_id')
|
||||||
|
->relationship(name: 'task', titleAttribute: 'name')
|
||||||
|
->searchable(['name'])
|
||||||
|
->nullable(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,28 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\TimeEntryResource\Pages;
|
namespace App\Filament\Resources\TimeEntryResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\TimeEntryResource;
|
use App\Filament\Resources\TimeEntryResource;
|
||||||
|
use App\Models\Member;
|
||||||
use Filament\Resources\Pages\CreateRecord;
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
class CreateTimeEntry extends CreateRecord
|
class CreateTimeEntry extends CreateRecord
|
||||||
{
|
{
|
||||||
protected static string $resource = TimeEntryResource::class;
|
protected static string $resource = TimeEntryResource::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $data
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
protected function mutateFormDataBeforeCreate(array $data): array
|
||||||
|
{
|
||||||
|
if (isset($data['member_id'])) {
|
||||||
|
/** @var Member|null $member */
|
||||||
|
$member = Member::query()->find($data['member_id']);
|
||||||
|
if ($member !== null) {
|
||||||
|
$data['user_id'] = $member->user_id;
|
||||||
|
$data['organization_id'] = $member->organization_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\TimeEntryResource\Pages;
|
namespace App\Filament\Resources\TimeEntryResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\TimeEntryResource;
|
use App\Filament\Resources\TimeEntryResource;
|
||||||
|
use App\Models\Member;
|
||||||
use Filament\Actions;
|
use Filament\Actions;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
@@ -19,4 +20,22 @@ class EditTimeEntry extends EditRecord
|
|||||||
->icon('heroicon-m-trash'),
|
->icon('heroicon-m-trash'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $data
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
protected function mutateFormDataBeforeSave(array $data): array
|
||||||
|
{
|
||||||
|
if (isset($data['member_id'])) {
|
||||||
|
/** @var Member|null $member */
|
||||||
|
$member = Member::query()->find($data['member_id']);
|
||||||
|
if ($member !== null) {
|
||||||
|
$data['user_id'] = $member->user_id;
|
||||||
|
$data['organization_id'] = $member->organization_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ declare(strict_types=1);
|
|||||||
namespace Tests\Unit\Filament\Resources;
|
namespace Tests\Unit\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\TimeEntryResource;
|
use App\Filament\Resources\TimeEntryResource;
|
||||||
|
use App\Models\Member;
|
||||||
|
use App\Models\Organization;
|
||||||
use App\Models\TimeEntry;
|
use App\Models\TimeEntry;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
@@ -50,4 +52,149 @@ class TimeEntryResourceTest extends FilamentTestCase
|
|||||||
// Assert
|
// Assert
|
||||||
$response->assertSuccessful();
|
$response->assertSuccessful();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_can_see_create_page_of_time_entry(): void
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
$response = Livewire::test(TimeEntryResource\Pages\CreateTimeEntry::class);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertSuccessful();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_can_create_time_entry(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$organization = Organization::factory()->create();
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$member = Member::factory()
|
||||||
|
->forOrganization($organization)
|
||||||
|
->forUser($user)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = Livewire::test(TimeEntryResource\Pages\CreateTimeEntry::class)
|
||||||
|
->fillForm([
|
||||||
|
'description' => 'Test time entry',
|
||||||
|
'billable' => true,
|
||||||
|
'start' => '2024-01-01 08:00:00',
|
||||||
|
'end' => '2024-01-01 10:00:00',
|
||||||
|
'member_id' => $member->getKey(),
|
||||||
|
])
|
||||||
|
->call('create')
|
||||||
|
->assertHasNoFormErrors();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertSuccessful();
|
||||||
|
$timeEntry = TimeEntry::where('description', 'Test time entry')->first();
|
||||||
|
$this->assertNotNull($timeEntry);
|
||||||
|
$this->assertSame($member->getKey(), $timeEntry->member_id);
|
||||||
|
$this->assertSame($user->getKey(), $timeEntry->user_id);
|
||||||
|
$this->assertSame($organization->getKey(), $timeEntry->organization_id);
|
||||||
|
$this->assertTrue($timeEntry->billable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_can_create_time_entry_and_derives_user_and_organization_from_member(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$organization = Organization::factory()->create();
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$member = Member::factory()
|
||||||
|
->forOrganization($organization)
|
||||||
|
->forUser($user)
|
||||||
|
->create();
|
||||||
|
$otherUser = User::factory()->create();
|
||||||
|
$otherOrganization = Organization::factory()->create();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = Livewire::test(TimeEntryResource\Pages\CreateTimeEntry::class)
|
||||||
|
->fillForm([
|
||||||
|
'description' => 'Derived fields test',
|
||||||
|
'billable' => false,
|
||||||
|
'start' => '2024-03-01 09:00:00',
|
||||||
|
'end' => '2024-03-01 11:00:00',
|
||||||
|
'member_id' => $member->getKey(),
|
||||||
|
'user_id' => $otherUser->getKey(),
|
||||||
|
'organization_id' => $otherOrganization->getKey(),
|
||||||
|
])
|
||||||
|
->call('create')
|
||||||
|
->assertHasNoFormErrors();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertSuccessful();
|
||||||
|
$timeEntry = TimeEntry::where('description', 'Derived fields test')->first();
|
||||||
|
$this->assertNotNull($timeEntry);
|
||||||
|
$this->assertSame($user->getKey(), $timeEntry->user_id);
|
||||||
|
$this->assertSame($organization->getKey(), $timeEntry->organization_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_can_update_time_entry(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$organization = Organization::factory()->create();
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$member = Member::factory()
|
||||||
|
->forOrganization($organization)
|
||||||
|
->forUser($user)
|
||||||
|
->create();
|
||||||
|
$timeEntry = TimeEntry::factory()->forMember($member)->create();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = Livewire::test(TimeEntryResource\Pages\EditTimeEntry::class, ['record' => $timeEntry->getKey()])
|
||||||
|
->fillForm([
|
||||||
|
'description' => 'Updated description',
|
||||||
|
'billable' => true,
|
||||||
|
'start' => '2024-02-01 08:00:00',
|
||||||
|
'end' => '2024-02-01 12:00:00',
|
||||||
|
'member_id' => $member->getKey(),
|
||||||
|
])
|
||||||
|
->call('save')
|
||||||
|
->assertHasNoFormErrors();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertSuccessful();
|
||||||
|
$timeEntry->refresh();
|
||||||
|
$this->assertSame('Updated description', $timeEntry->description);
|
||||||
|
$this->assertTrue($timeEntry->billable);
|
||||||
|
$this->assertSame($user->getKey(), $timeEntry->user_id);
|
||||||
|
$this->assertSame($organization->getKey(), $timeEntry->organization_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_update_time_entry_derives_user_and_organization_from_new_member(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$organization = Organization::factory()->create();
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$member = Member::factory()
|
||||||
|
->forOrganization($organization)
|
||||||
|
->forUser($user)
|
||||||
|
->create();
|
||||||
|
$timeEntry = TimeEntry::factory()->create();
|
||||||
|
|
||||||
|
$newOrganization = Organization::factory()->create();
|
||||||
|
$newUser = User::factory()->create();
|
||||||
|
$newMember = Member::factory()
|
||||||
|
->forOrganization($newOrganization)
|
||||||
|
->forUser($newUser)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = Livewire::test(TimeEntryResource\Pages\EditTimeEntry::class, ['record' => $timeEntry->getKey()])
|
||||||
|
->fillForm([
|
||||||
|
'description' => 'Reassigned entry',
|
||||||
|
'billable' => false,
|
||||||
|
'start' => '2024-02-01 08:00:00',
|
||||||
|
'end' => '2024-02-01 12:00:00',
|
||||||
|
'member_id' => $newMember->getKey(),
|
||||||
|
])
|
||||||
|
->call('save')
|
||||||
|
->assertHasNoFormErrors();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertSuccessful();
|
||||||
|
$timeEntry->refresh();
|
||||||
|
$this->assertSame($newMember->getKey(), $timeEntry->member_id);
|
||||||
|
$this->assertSame($newUser->getKey(), $timeEntry->user_id);
|
||||||
|
$this->assertSame($newOrganization->getKey(), $timeEntry->organization_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user