refactor: remove photo attachment functionality from employee advances module
This commit is contained in:
parent
c40e9e5e87
commit
e6ed485718
@ -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'),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 = {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user