refactor: remove photo attachment functionality from employee advances module

This commit is contained in:
Yoga Pangestu 2026-06-14 13:48:48 +07:00
parent c40e9e5e87
commit e6ed485718
6 changed files with 7 additions and 99 deletions

View File

@ -4,14 +4,11 @@
use App\Enums\EmployeeAdvanceStatus; use App\Enums\EmployeeAdvanceStatus;
use App\Enums\Permission; use App\Enums\Permission;
use App\Http\Requests\Concerns\ValidatesMediaUploads;
use App\Models\EmployeeAdvance; use App\Models\EmployeeAdvance;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class EmployeeAdvanceRequest extends FormRequest class EmployeeAdvanceRequest extends FormRequest
{ {
use ValidatesMediaUploads;
public function authorize(): bool public function authorize(): bool
{ {
$user = $this->user(); $user = $this->user();
@ -44,8 +41,7 @@ public function rules(): array
return [ return [
'amount' => ['required', 'integer', 'min:1'], 'amount' => ['required', 'integer', 'min:1'],
'description' => ['required', 'string', 'max:100'], 'description' => ['required', 'string', 'max:100'],
'due_date' => ['required', 'date','after_or_equal:today'], 'due_date' => ['required', 'date', 'after_or_equal:today'],
...$this->photoRules(),
]; ];
} }
@ -58,7 +54,6 @@ public function attributes(): array
'amount' => 'jumlah', 'amount' => 'jumlah',
'description' => 'keterangan', 'description' => 'keterangan',
'due_date' => 'jatuh tempo', 'due_date' => 'jatuh tempo',
...$this->photoUploadAttributes('foto bukti'),
]; ];
} }
} }

View File

@ -3,7 +3,6 @@
namespace App\Models; namespace App\Models;
use App\Enums\EmployeeAdvanceStatus; use App\Enums\EmployeeAdvanceStatus;
use App\Models\Concerns\HasModuleMedia;
use App\Models\Concerns\HasRejection; use App\Models\Concerns\HasRejection;
use App\Models\Concerns\InteractsWithActivityLog; use App\Models\Concerns\InteractsWithActivityLog;
use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Appends;
@ -12,7 +11,6 @@
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Spatie\MediaLibrary\HasMedia;
#[Guarded(['id'])] #[Guarded(['id'])]
#[Appends([ #[Appends([
@ -27,10 +25,9 @@
'can_verify', 'can_verify',
'can_pay', 'can_pay',
])] ])]
class EmployeeAdvance extends Model implements HasMedia class EmployeeAdvance extends Model
{ {
use HasFactory; use HasFactory;
use HasModuleMedia;
use HasRejection; use HasRejection;
use InteractsWithActivityLog; use InteractsWithActivityLog;
@ -140,14 +137,4 @@ public function statusLabel(): Attribute
get: fn () => $this->status?->label(), get: fn () => $this->status?->label(),
); );
} }
public static function mediaModuleName(): string
{
return 'employee-advance';
}
public function registerMediaCollections(): void
{
$this->addMediaCollection('photos');
}
} }

View File

@ -6,9 +6,7 @@
use App\Models\EmployeeAdvance; use App\Models\EmployeeAdvance;
use App\Models\User; use App\Models\User;
use App\Services\Concerns\ResolvesAuthEmployee; use App\Services\Concerns\ResolvesAuthEmployee;
use App\Services\Media\MediaService;
use App\Services\System\PushNotificationService; use App\Services\System\PushNotificationService;
use App\Support\Media\MediaPresenter;
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -22,7 +20,6 @@ class EmployeeAdvanceService
public function __construct( public function __construct(
private readonly CashService $cashService, private readonly CashService $cashService,
private readonly MediaService $mediaService,
private readonly PushNotificationService $pushNotificationService, private readonly PushNotificationService $pushNotificationService,
) {} ) {}
@ -52,7 +49,7 @@ public function outstandingSummary(): array
public function paginateForIndex(array $tableQuery): LengthAwarePaginator public function paginateForIndex(array $tableQuery): LengthAwarePaginator
{ {
$query = EmployeeAdvance::query() $query = EmployeeAdvance::query()
->with(['employee.user.profile', 'rejection', 'media']) ->with(['employee.user.profile', 'rejection'])
->when($tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void { ->when($tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void {
$search = $tableQuery['search']; $search = $tableQuery['search'];
$query->where(function (Builder $query) use ($search): void { $query->where(function (Builder $query) use ($search): void {
@ -67,15 +64,7 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
return $query return $query
->paginate(10) ->paginate(10)
->withQueryString() ->withQueryString();
->through(function (EmployeeAdvance $employeeAdvance) {
$employeeAdvance->setAttribute(
'photos',
MediaPresenter::collection($employeeAdvance, 'photos'),
);
return $employeeAdvance;
});
} }
/** /**
@ -86,17 +75,13 @@ public function create(array $validated, User $user): void
$employee = $this->resolveAuthEmployee($user); $employee = $this->resolveAuthEmployee($user);
$employeeAdvance = DB::transaction(function () use ($validated, $employee) { $employeeAdvance = DB::transaction(function () use ($validated, $employee) {
$employeeAdvance = EmployeeAdvance::create([ return EmployeeAdvance::create([
'employee_id' => $employee->id, 'employee_id' => $employee->id,
'amount' => (int) $validated['amount'], 'amount' => (int) $validated['amount'],
'description' => $validated['description'], 'description' => $validated['description'],
'due_date' => $validated['due_date'], 'due_date' => $validated['due_date'],
'status' => EmployeeAdvanceStatus::PENDING, 'status' => EmployeeAdvanceStatus::PENDING,
]); ]);
$this->syncPhotos($employeeAdvance, $validated);
return $employeeAdvance;
}); });
$this->pushNotificationService->sendToRoles( $this->pushNotificationService->sendToRoles(
@ -120,8 +105,6 @@ public function update(EmployeeAdvance $employeeAdvance, array $validated, User
$employeeAdvance->description = $validated['description']; $employeeAdvance->description = $validated['description'];
$employeeAdvance->due_date = $validated['due_date']; $employeeAdvance->due_date = $validated['due_date'];
$employeeAdvance->save(); $employeeAdvance->save();
$this->syncPhotos($employeeAdvance, $validated);
}); });
} }
@ -130,7 +113,6 @@ public function delete(EmployeeAdvance $employeeAdvance, User $user): void
$this->ensureOwnedBySubmitter($employeeAdvance, $user); $this->ensureOwnedBySubmitter($employeeAdvance, $user);
$this->ensurePending($employeeAdvance, 'Kasbon hanya dapat dihapus saat status menunggu.'); $this->ensurePending($employeeAdvance, 'Kasbon hanya dapat dihapus saat status menunggu.');
$employeeAdvance->clearMediaCollection('photos');
$employeeAdvance->delete(); $employeeAdvance->delete();
} }
@ -229,22 +211,6 @@ public function pay(EmployeeAdvance $employeeAdvance, User $user): void
}); });
} }
/**
* @param array<string, mixed> $validated
*/
private function syncPhotos(EmployeeAdvance $employeeAdvance, array $validated): void
{
$this->mediaService->syncCollection(
$employeeAdvance,
'photos',
$validated['photos'] ?? null,
$validated['remove_media_ids'] ?? null,
self::MAX_PHOTOS,
required: true,
errorKey: 'photos',
);
}
private function ensureOwnedBySubmitter(EmployeeAdvance $employeeAdvance, User $user): void private function ensureOwnedBySubmitter(EmployeeAdvance $employeeAdvance, User $user): void
{ {
if ($user->employee?->id !== $employeeAdvance->employee_id) { if ($user->employee?->id !== $employeeAdvance->employee_id) {

View File

@ -1,9 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { useForm } from '@inertiajs/vue3'; import { useForm } from '@inertiajs/vue3';
import { Save } from '@lucide/vue'; import { Save } from '@lucide/vue';
import { computed, ref } from 'vue'; import { computed } from 'vue';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import ImageUploadField from '@/components/form/image-upload-field/ImageUploadField.vue';
import { RupiahInput } from '@/components/form/rupiah-input'; import { RupiahInput } from '@/components/form/rupiah-input';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { DatePicker } from '@/components/ui/date-picker'; import { DatePicker } from '@/components/ui/date-picker';
@ -27,7 +26,6 @@ import { FIELD_LIMITS } from '@/lib/field-limits';
import { formErrors } from '@/lib/form'; import { formErrors } from '@/lib/form';
import { parseRupiah } from '@/lib/rupiah'; import { parseRupiah } from '@/lib/rupiah';
import type { EmployeeAdvanceFormData, EmployeeAdvanceListItem } from '@/types/employee-advance'; import type { EmployeeAdvanceFormData, EmployeeAdvanceListItem } from '@/types/employee-advance';
import { appendPhotosToFormData } from '@/types/media';
const open = defineModel<boolean>('open', { default: false }); const open = defineModel<boolean>('open', { default: false });
@ -37,30 +35,14 @@ const props = defineProps<{
const isEditing = computed(() => props.employeeAdvance != null); const isEditing = computed(() => props.employeeAdvance != null);
const existingPhotoId = ref<number | null>(null);
const currentPhotoUrl = computed(() => props.employeeAdvance?.photos?.[0]?.url ?? null);
const form = useForm<EmployeeAdvanceFormData>({ const form = useForm<EmployeeAdvanceFormData>({
amount: '', amount: '',
description: '', description: '',
due_date: '', due_date: '',
photos: [],
remove_media_ids: [],
});
const photoFile = computed<File | null>({
get: () => form.photos[0] ?? null,
set: (file) => {
form.photos = file ? [file] : [];
},
}); });
function resetForm() { function resetForm() {
form.reset(); form.reset();
form.photos = [];
form.remove_media_ids = [];
existingPhotoId.value = null;
form.clearErrors(); form.clearErrors();
} }
@ -74,7 +56,6 @@ function populateForm(employeeAdvance: EmployeeAdvanceListItem | null | undefine
form.amount = String(employeeAdvance.amount); form.amount = String(employeeAdvance.amount);
form.description = employeeAdvance.description; form.description = employeeAdvance.description;
form.due_date = employeeAdvance.due_date_input; form.due_date = employeeAdvance.due_date_input;
existingPhotoId.value = employeeAdvance.photos?.[0]?.id ?? null;
} }
useFormDialog({ useFormDialog({
@ -95,14 +76,6 @@ function buildFormData(forUpdate: boolean): FormData {
formData.append('description', form.description); formData.append('description', form.description);
formData.append('due_date', form.due_date); formData.append('due_date', form.due_date);
const removeMediaIds = [...form.remove_media_ids];
if (form.photos.length > 0 && existingPhotoId.value !== null) {
removeMediaIds.push(existingPhotoId.value);
}
appendPhotosToFormData(formData, form.photos, removeMediaIds);
return formData; return formData;
} }
@ -157,8 +130,6 @@ function submit() {
<DatePicker id="employee-advance-due-date" v-model="form.due_date" /> <DatePicker id="employee-advance-due-date" v-model="form.due_date" />
<FieldError :errors="formErrors(form, 'due_date')" /> <FieldError :errors="formErrors(form, 'due_date')" />
</Field> </Field>
<ImageUploadField id="employee-advance-photos" v-model="photoFile" label="Foto Bukti" required
:current-url="currentPhotoUrl" :errors="formErrors(form, 'photos')" />
</FieldSet> </FieldSet>
</FieldGroup> </FieldGroup>

View File

@ -2,7 +2,6 @@ import type { ColumnDef } from '@tanstack/vue-table';
import { h } from 'vue'; import { h } from 'vue';
import DataTableActions from '@/components/admin/finance/employee-advances/data-table-actions.vue'; import DataTableActions from '@/components/admin/finance/employee-advances/data-table-actions.vue';
import { DataTableColumnHeader } from '@/components/data-table'; import { DataTableColumnHeader } from '@/components/data-table';
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import type { EmployeeAdvanceListItem } from '@/types/employee-advance'; import type { EmployeeAdvanceListItem } from '@/types/employee-advance';
@ -45,12 +44,7 @@ export function createColumns(
enableSorting: true, enableSorting: true,
header: () => h(DataTableColumnHeader, { title: 'Keterangan', column: 'description' }), header: () => h(DataTableColumnHeader, { title: 'Keterangan', column: 'description' }),
}, },
{
id: 'photos',
enableSorting: false,
header: () => 'Foto',
cell: ({ row }) => h(MediaThumbnailCell, { items: row.original.photos ?? [] }),
},
{ {
accessorKey: 'due_date_formatted', accessorKey: 'due_date_formatted',
enableSorting: true, enableSorting: true,

View File

@ -1,5 +1,3 @@
import type { MediaItem } from '@/types/media';
export type EmployeeAdvanceListItem = { export type EmployeeAdvanceListItem = {
id: number; id: number;
employee_id: number; employee_id: number;
@ -16,15 +14,12 @@ export type EmployeeAdvanceListItem = {
is_editable: boolean; is_editable: boolean;
can_verify: boolean; can_verify: boolean;
can_pay: boolean; can_pay: boolean;
photos?: MediaItem[];
}; };
export type EmployeeAdvanceFormData = { export type EmployeeAdvanceFormData = {
amount: string; amount: string;
description: string; description: string;
due_date: string; due_date: string;
photos: File[];
remove_media_ids: number[];
}; };
export type PaginatedEmployeeAdvances = { export type PaginatedEmployeeAdvances = {