Skip to content

Commit 1b10f20

Browse files
committed
wip
1 parent 5fca5a3 commit 1b10f20

File tree

19 files changed

+8361
-1993
lines changed

19 files changed

+8361
-1993
lines changed

api.json

Lines changed: 7884 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Exceptions\Api;
6+
7+
class TimeEntryInvoicedApiException extends ApiException
8+
{
9+
public const string KEY = 'time_entry_invoiced';
10+
}

app/Http/Controllers/Api/V1/TimeEntryController.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use App\Exceptions\Api\OverlappingTimeEntryApiException;
1111
use App\Exceptions\Api\PdfRendererIsNotConfiguredException;
1212
use App\Exceptions\Api\TimeEntryCanNotBeRestartedApiException;
13+
use App\Exceptions\Api\TimeEntryInvoicedApiException;
1314
use App\Exceptions\Api\TimeEntryStillRunningApiException;
1415
use App\Http\Requests\V1\TimeEntry\TimeEntryAggregateExportRequest;
1516
use App\Http\Requests\V1\TimeEntry\TimeEntryAggregateRequest;
@@ -101,6 +102,11 @@ protected function checkPermission(Organization $organization, string $permissio
101102
if ($timeEntry !== null && $timeEntry->organization_id !== $organization->getKey()) {
102103
throw new AuthorizationException('Time entry does not belong to organization');
103104
}
105+
if ($timeEntry !== null && $timeEntry->invoiced_at !== null) {
106+
if (in_array($permission, ['time-entries:update:own', 'time-entries:update:all', 'time-entries:delete:own', 'time-entries:delete:all'], true)) {
107+
throw new TimeEntryInvoicedApiException;
108+
}
109+
}
104110
}
105111

106112
/**
@@ -628,9 +634,9 @@ public function update(Organization $organization, TimeEntry $timeEntry, TimeEnt
628634
/** @var Member|null $member */
629635
$member = $request->has('member_id') ? Member::query()->findOrFail($request->input('member_id')) : null;
630636
if ($timeEntry->member->user_id === Auth::id() && ($member === null || $member->user_id === Auth::id())) {
631-
$this->checkPermission($organization, 'time-entries:update:own');
637+
$this->checkPermission($organization, 'time-entries:update:own', $timeEntry);
632638
} else {
633-
$this->checkPermission($organization, 'time-entries:update:all');
639+
$this->checkPermission($organization, 'time-entries:update:all', $timeEntry);
634640
}
635641

636642
if ($timeEntry->end !== null && $request->has('end') && $request->input('end') === null) {
@@ -744,6 +750,11 @@ public function updateMultiple(Organization $organization, TimeEntryUpdateMultip
744750
continue;
745751

746752
}
753+
if ($timeEntry->invoiced_at !== null) {
754+
$error->push($id);
755+
756+
continue;
757+
}
747758
$oldProject = $timeEntry->project;
748759
$oldTask = $timeEntry->task;
749760

@@ -851,6 +862,11 @@ public function destroyMultiple(Organization $organization, TimeEntryDestroyMult
851862
continue;
852863

853864
}
865+
if ($timeEntry->invoiced_at !== null) {
866+
$error->push($id);
867+
868+
continue;
869+
}
854870

855871
$project = $timeEntry->project;
856872
$task = $timeEntry->task;

app/Http/Resources/V1/TimeEntry/TimeEntryResource.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public function toArray(Request $request): array
4747
'tags' => $this->resource->tags ?? [],
4848
/** @var bool $billable Whether time entry is billable */
4949
'billable' => $this->resource->billable,
50+
/** @var string|null $invoiced_at When the time entry was invoiced (ISO 8601 format, UTC timezone, example: 2024-02-26T17:17:17Z) */
51+
'invoiced_at' => $this->formatDateTime($this->resource->invoiced_at),
5052
];
5153
}
5254
}

app/Models/TimeEntry.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
* @property string $member_id
3434
* @property bool $is_imported
3535
* @property Carbon|null $still_active_email_sent_at
36+
* @property Carbon|null $invoiced_at
3637
* @property Carbon|null $created_at
3738
* @property Carbon|null $updated_at
3839
* @property-read User $user
@@ -75,6 +76,7 @@ class TimeEntry extends Model implements AuditableContract
7576
'billable_rate' => 'int',
7677
'is_imported' => 'bool',
7778
'still_active_email_sent_at' => 'datetime',
79+
'invoiced_at' => 'datetime',
7880
];
7981

8082
public const array SELECT_COLUMNS = [
@@ -95,6 +97,7 @@ class TimeEntry extends Model implements AuditableContract
9597
'client_id',
9698
'is_imported',
9799
'still_active_email_sent_at',
100+
'invoiced_at',
98101
];
99102

100103
/**
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Illuminate\Database\Migrations\Migration;
6+
use Illuminate\Database\Schema\Blueprint;
7+
use Illuminate\Support\Facades\Schema;
8+
9+
return new class extends Migration
10+
{
11+
/**
12+
* Run the migrations.
13+
*/
14+
public function up(): void
15+
{
16+
Schema::table('time_entries', function (Blueprint $table): void {
17+
$table->dateTime('invoiced_at')->nullable()->after('still_active_email_sent_at');
18+
});
19+
}
20+
21+
/**
22+
* Reverse the migrations.
23+
*/
24+
public function down(): void
25+
{
26+
Schema::table('time_entries', function (Blueprint $table): void {
27+
$table->dropColumn('invoiced_at');
28+
});
29+
}
30+
};

0 commit comments

Comments
 (0)