feat: enhance raw material management by adding validation for updates and deletions, and improve error handling in the UI
This commit is contained in:
parent
6fa9224983
commit
cc3cd03e41
@ -9,6 +9,7 @@
|
||||
use App\Services\Admin\Master\RawMaterial\RawMaterialService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
@ -55,13 +56,15 @@ public function edit(RawMaterial $rawMaterial): Response
|
||||
|
||||
public function update(RawMaterialRequest $request, RawMaterial $rawMaterial): RedirectResponse
|
||||
{
|
||||
return $this->handleAction(
|
||||
fn () => $this->service->update($rawMaterial, $request->validated()),
|
||||
'Bahan baku berhasil diperbarui.',
|
||||
'admin.master.raw-materials.index',
|
||||
'admin.master.raw-materials.edit',
|
||||
['rawMaterial' => $rawMaterial]
|
||||
);
|
||||
try {
|
||||
$this->service->update($rawMaterial, $request->validated());
|
||||
} catch (ValidationException $e) {
|
||||
return back()->withErrors($e->errors());
|
||||
}
|
||||
|
||||
Inertia::flash('toast', ['type' => 'success', 'message' => 'Bahan baku berhasil diperbarui.']);
|
||||
|
||||
return to_route('admin.master.raw-materials.index');
|
||||
}
|
||||
|
||||
public function destroy(RawMaterial $rawMaterial): RedirectResponse
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class RawMaterialService
|
||||
{
|
||||
@ -108,7 +109,7 @@ public function store(array $data): RawMaterial
|
||||
});
|
||||
|
||||
NotificationService::notify(
|
||||
roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
|
||||
roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
|
||||
title: 'Bahan Baku Baru',
|
||||
body: "Bahan baku \"{$rawMaterial->name}\" berhasil ditambahkan".' oleh '.auth()->user()->full_name.'.',
|
||||
url: route('admin.master.raw-materials.index', ['highlight' => $rawMaterial->id]),
|
||||
@ -161,12 +162,30 @@ public function update(RawMaterial $rawMaterial, array $data): RawMaterial
|
||||
->filter()
|
||||
->toArray();
|
||||
|
||||
$rawMaterial->rawMaterialPrices()
|
||||
$variantsToDelete = $rawMaterial->rawMaterialPrices()
|
||||
->whereNotIn('id', $existingVariantIds)
|
||||
->each(function (RawMaterialPrice $price) {
|
||||
$price->clearMediaCollection('images');
|
||||
$price->delete();
|
||||
});
|
||||
->get();
|
||||
|
||||
foreach ($variantsToDelete as $price) {
|
||||
if ($price->stock > 0) {
|
||||
throw ValidationException::withMessages([
|
||||
'variants' => "Varian \"{$price->variant}\" tidak dapat dihapus karena masih memiliki stok.",
|
||||
]);
|
||||
}
|
||||
|
||||
$activeCuttingUsage = $price->cuttingMaterials()
|
||||
->whereHas('cutting', fn ($q) => $q->inProgress())
|
||||
->exists();
|
||||
|
||||
if ($activeCuttingUsage) {
|
||||
throw ValidationException::withMessages([
|
||||
'variants' => "Varian \"{$price->variant}\" tidak dapat dihapus karena masih digunakan di cutting yang sedang diproses.",
|
||||
]);
|
||||
}
|
||||
|
||||
$price->clearMediaCollection('images');
|
||||
$price->delete();
|
||||
}
|
||||
|
||||
$existingPricesMap = RawMaterialPrice::whereIn('id', $existingVariantIds)
|
||||
->with('media')
|
||||
@ -228,7 +247,7 @@ public function update(RawMaterial $rawMaterial, array $data): RawMaterial
|
||||
});
|
||||
|
||||
NotificationService::notify(
|
||||
roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
|
||||
roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
|
||||
title: 'Bahan Baku Diperbarui',
|
||||
body: "Bahan baku \"{$rawMaterial->name}\" berhasil diperbarui".' oleh '.auth()->user()->full_name.'.',
|
||||
url: route('admin.master.raw-materials.index', ['highlight' => $rawMaterial->id]),
|
||||
@ -239,8 +258,27 @@ public function update(RawMaterial $rawMaterial, array $data): RawMaterial
|
||||
|
||||
public function destroy(RawMaterial $rawMaterial): bool
|
||||
{
|
||||
foreach ($rawMaterial->rawMaterialPrices as $price) {
|
||||
if ($price->stock > 0) {
|
||||
throw ValidationException::withMessages([
|
||||
'raw_material' => "Bahan baku \"{$rawMaterial->name}\" tidak dapat dihapus karena varian \"{$price->variant}\" masih memiliki stok.",
|
||||
]);
|
||||
}
|
||||
|
||||
$activeCuttingUsage = $price->cuttingMaterials()
|
||||
->whereHas('cutting', fn ($q) => $q->inProgress())
|
||||
->exists();
|
||||
|
||||
if ($activeCuttingUsage) {
|
||||
throw ValidationException::withMessages([
|
||||
'raw_material' => "Bahan baku \"{$rawMaterial->name}\" tidak dapat dihapus karena varian \"{$price->variant}\" masih digunakan di cutting yang sedang diproses.",
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$result = DB::transaction(function () use ($rawMaterial) {
|
||||
$rawMaterial->rawMaterialPrices->each(function (RawMaterialPrice $price) {
|
||||
$price->clearMediaCollection('images');
|
||||
$price->delete();
|
||||
});
|
||||
|
||||
@ -248,7 +286,7 @@ public function destroy(RawMaterial $rawMaterial): bool
|
||||
});
|
||||
|
||||
NotificationService::notify(
|
||||
roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
|
||||
roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
|
||||
title: 'Bahan Baku Dihapus',
|
||||
body: "Bahan baku \"{$rawMaterial->name}\" berhasil dihapus".' oleh '.auth()->user()->full_name.'.',
|
||||
url: route('admin.master.raw-materials.index'),
|
||||
@ -266,7 +304,7 @@ public function toggleStatus(RawMaterial $rawMaterial): void
|
||||
$status = $rawMaterial->is_active ? 'diaktifkan' : 'dinonaktifkan';
|
||||
|
||||
NotificationService::notify(
|
||||
roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
|
||||
roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
|
||||
title: 'Status Bahan Baku Diubah',
|
||||
body: "Bahan baku \"{$rawMaterial->name}\" berhasil {$status}".' oleh '.auth()->user()->full_name.'.',
|
||||
url: route('admin.master.raw-materials.index', ['highlight' => $rawMaterial->id]),
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
use App\Services\NotificationService;
|
||||
use App\Services\S3PresignedService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class RawMaterialVariantService
|
||||
{
|
||||
@ -85,7 +86,7 @@ public function update(RawMaterialPrice $variant, array $data): RawMaterialPrice
|
||||
});
|
||||
|
||||
NotificationService::notify(
|
||||
roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
|
||||
roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
|
||||
title: 'Varian Diperbarui',
|
||||
body: "Varian \"{$variant->variant}\" berhasil diperbarui".' oleh '.auth()->user()->full_name.'.',
|
||||
url: route('admin.master.raw-materials.index', ['highlight' => $variant->raw_material_id]),
|
||||
@ -96,12 +97,28 @@ public function update(RawMaterialPrice $variant, array $data): RawMaterialPrice
|
||||
|
||||
public function destroy(RawMaterial $rawMaterial, RawMaterialPrice $variant): bool
|
||||
{
|
||||
if ($variant->stock > 0) {
|
||||
throw ValidationException::withMessages([
|
||||
'variant' => 'Varian tidak dapat dihapus karena masih memiliki stok.',
|
||||
]);
|
||||
}
|
||||
|
||||
$activeCuttingUsage = $variant->cuttingMaterials()
|
||||
->whereHas('cutting', fn ($q) => $q->inProgress())
|
||||
->exists();
|
||||
|
||||
if ($activeCuttingUsage) {
|
||||
throw ValidationException::withMessages([
|
||||
'variant' => 'Varian tidak dapat dihapus karena masih digunakan di cutting yang sedang diproses.',
|
||||
]);
|
||||
}
|
||||
|
||||
$result = DB::transaction(function () use ($variant) {
|
||||
return $variant->delete();
|
||||
});
|
||||
|
||||
NotificationService::notify(
|
||||
roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
|
||||
roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
|
||||
title: 'Varian Dihapus',
|
||||
body: "Varian \"{$variant->variant}\" dari bahan baku \"{$rawMaterial->name}\" berhasil dihapus".' oleh '.auth()->user()->full_name.'.',
|
||||
url: route('admin.master.raw-materials.index'),
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
Plus,
|
||||
Trash2,
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { ConfirmDialog } from '@/components/dialogs';
|
||||
import { FileUpload } from '@/components/inputs';
|
||||
@ -134,6 +134,23 @@ export default function RawMaterialEdit({ rawMaterial }: Props) {
|
||||
const [deleteVariantIndex, setDeleteVariantIndex] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
const [removedVariants, setRemovedVariants] = useState<VariantState[]>([]);
|
||||
const removedVariantsRef = useRef(removedVariants);
|
||||
removedVariantsRef.current = removedVariants;
|
||||
|
||||
const { errors: pageErrors } = usePage<{ errors?: Record<string, string[]> }>().props;
|
||||
|
||||
useEffect(() => {
|
||||
if (pageErrors && Object.keys(pageErrors).length > 0) {
|
||||
const removed = removedVariantsRef.current;
|
||||
if (removed.length > 0) {
|
||||
setVariants((prev) => [...prev, ...removed]);
|
||||
setRemovedVariants([]);
|
||||
}
|
||||
const firstError = Object.values(pageErrors).flat().find(Boolean);
|
||||
toast.error(firstError ?? 'Ada data yang belum sesuai, silakan periksa kembali input Anda.');
|
||||
}
|
||||
}, [pageErrors]);
|
||||
|
||||
const confirmRemoveVariant = useCallback((index: number) => {
|
||||
setDeleteVariantIndex(index);
|
||||
@ -222,9 +239,6 @@ export default function RawMaterialEdit({ rawMaterial }: Props) {
|
||||
...data,
|
||||
...getPayload(),
|
||||
})}
|
||||
onError={() => {
|
||||
toast.error('Ada data yang belum sesuai, silakan periksa kembali input Anda.');
|
||||
}}
|
||||
>
|
||||
{({ errors, processing }) => (
|
||||
<>
|
||||
@ -555,6 +569,10 @@ export default function RawMaterialEdit({ rawMaterial }: Props) {
|
||||
confirmLabel="Hapus"
|
||||
onConfirm={() => {
|
||||
if (deleteVariantIndex !== null) {
|
||||
const removed = variants[deleteVariantIndex];
|
||||
if (removed.id !== null) {
|
||||
setRemovedVariants((prev) => [...prev, removed]);
|
||||
}
|
||||
removeVariant(deleteVariantIndex);
|
||||
}
|
||||
|
||||
|
||||
@ -141,6 +141,7 @@ export default function RawMaterialIndex({ rawMaterials, rawMaterialNames, filte
|
||||
|
||||
router.delete(destroy.url(deleting.id), {
|
||||
onSuccess: () => setDeleting(null),
|
||||
onError: () => setDeleting(null),
|
||||
});
|
||||
}
|
||||
|
||||
@ -156,6 +157,7 @@ export default function RawMaterialIndex({ rawMaterials, rawMaterialNames, filte
|
||||
}),
|
||||
{
|
||||
onSuccess: () => setDeletingVariant(null),
|
||||
onError: () => setDeletingVariant(null),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user