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 App\Services\Admin\Master\RawMaterial\RawMaterialService;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
use Inertia\Response;
|
use Inertia\Response;
|
||||||
|
|
||||||
@ -55,13 +56,15 @@ public function edit(RawMaterial $rawMaterial): Response
|
|||||||
|
|
||||||
public function update(RawMaterialRequest $request, RawMaterial $rawMaterial): RedirectResponse
|
public function update(RawMaterialRequest $request, RawMaterial $rawMaterial): RedirectResponse
|
||||||
{
|
{
|
||||||
return $this->handleAction(
|
try {
|
||||||
fn () => $this->service->update($rawMaterial, $request->validated()),
|
$this->service->update($rawMaterial, $request->validated());
|
||||||
'Bahan baku berhasil diperbarui.',
|
} catch (ValidationException $e) {
|
||||||
'admin.master.raw-materials.index',
|
return back()->withErrors($e->errors());
|
||||||
'admin.master.raw-materials.edit',
|
}
|
||||||
['rawMaterial' => $rawMaterial]
|
|
||||||
);
|
Inertia::flash('toast', ['type' => 'success', 'message' => 'Bahan baku berhasil diperbarui.']);
|
||||||
|
|
||||||
|
return to_route('admin.master.raw-materials.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function destroy(RawMaterial $rawMaterial): RedirectResponse
|
public function destroy(RawMaterial $rawMaterial): RedirectResponse
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
class RawMaterialService
|
class RawMaterialService
|
||||||
{
|
{
|
||||||
@ -108,7 +109,7 @@ public function store(array $data): RawMaterial
|
|||||||
});
|
});
|
||||||
|
|
||||||
NotificationService::notify(
|
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',
|
title: 'Bahan Baku Baru',
|
||||||
body: "Bahan baku \"{$rawMaterial->name}\" berhasil ditambahkan".' oleh '.auth()->user()->full_name.'.',
|
body: "Bahan baku \"{$rawMaterial->name}\" berhasil ditambahkan".' oleh '.auth()->user()->full_name.'.',
|
||||||
url: route('admin.master.raw-materials.index', ['highlight' => $rawMaterial->id]),
|
url: route('admin.master.raw-materials.index', ['highlight' => $rawMaterial->id]),
|
||||||
@ -161,12 +162,30 @@ public function update(RawMaterial $rawMaterial, array $data): RawMaterial
|
|||||||
->filter()
|
->filter()
|
||||||
->toArray();
|
->toArray();
|
||||||
|
|
||||||
$rawMaterial->rawMaterialPrices()
|
$variantsToDelete = $rawMaterial->rawMaterialPrices()
|
||||||
->whereNotIn('id', $existingVariantIds)
|
->whereNotIn('id', $existingVariantIds)
|
||||||
->each(function (RawMaterialPrice $price) {
|
->get();
|
||||||
$price->clearMediaCollection('images');
|
|
||||||
$price->delete();
|
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)
|
$existingPricesMap = RawMaterialPrice::whereIn('id', $existingVariantIds)
|
||||||
->with('media')
|
->with('media')
|
||||||
@ -228,7 +247,7 @@ public function update(RawMaterial $rawMaterial, array $data): RawMaterial
|
|||||||
});
|
});
|
||||||
|
|
||||||
NotificationService::notify(
|
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',
|
title: 'Bahan Baku Diperbarui',
|
||||||
body: "Bahan baku \"{$rawMaterial->name}\" berhasil diperbarui".' oleh '.auth()->user()->full_name.'.',
|
body: "Bahan baku \"{$rawMaterial->name}\" berhasil diperbarui".' oleh '.auth()->user()->full_name.'.',
|
||||||
url: route('admin.master.raw-materials.index', ['highlight' => $rawMaterial->id]),
|
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
|
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) {
|
$result = DB::transaction(function () use ($rawMaterial) {
|
||||||
$rawMaterial->rawMaterialPrices->each(function (RawMaterialPrice $price) {
|
$rawMaterial->rawMaterialPrices->each(function (RawMaterialPrice $price) {
|
||||||
|
$price->clearMediaCollection('images');
|
||||||
$price->delete();
|
$price->delete();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -248,7 +286,7 @@ public function destroy(RawMaterial $rawMaterial): bool
|
|||||||
});
|
});
|
||||||
|
|
||||||
NotificationService::notify(
|
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',
|
title: 'Bahan Baku Dihapus',
|
||||||
body: "Bahan baku \"{$rawMaterial->name}\" berhasil dihapus".' oleh '.auth()->user()->full_name.'.',
|
body: "Bahan baku \"{$rawMaterial->name}\" berhasil dihapus".' oleh '.auth()->user()->full_name.'.',
|
||||||
url: route('admin.master.raw-materials.index'),
|
url: route('admin.master.raw-materials.index'),
|
||||||
@ -266,7 +304,7 @@ public function toggleStatus(RawMaterial $rawMaterial): void
|
|||||||
$status = $rawMaterial->is_active ? 'diaktifkan' : 'dinonaktifkan';
|
$status = $rawMaterial->is_active ? 'diaktifkan' : 'dinonaktifkan';
|
||||||
|
|
||||||
NotificationService::notify(
|
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',
|
title: 'Status Bahan Baku Diubah',
|
||||||
body: "Bahan baku \"{$rawMaterial->name}\" berhasil {$status}".' oleh '.auth()->user()->full_name.'.',
|
body: "Bahan baku \"{$rawMaterial->name}\" berhasil {$status}".' oleh '.auth()->user()->full_name.'.',
|
||||||
url: route('admin.master.raw-materials.index', ['highlight' => $rawMaterial->id]),
|
url: route('admin.master.raw-materials.index', ['highlight' => $rawMaterial->id]),
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
use App\Services\NotificationService;
|
use App\Services\NotificationService;
|
||||||
use App\Services\S3PresignedService;
|
use App\Services\S3PresignedService;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
class RawMaterialVariantService
|
class RawMaterialVariantService
|
||||||
{
|
{
|
||||||
@ -85,7 +86,7 @@ public function update(RawMaterialPrice $variant, array $data): RawMaterialPrice
|
|||||||
});
|
});
|
||||||
|
|
||||||
NotificationService::notify(
|
NotificationService::notify(
|
||||||
roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
|
roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
|
||||||
title: 'Varian Diperbarui',
|
title: 'Varian Diperbarui',
|
||||||
body: "Varian \"{$variant->variant}\" berhasil diperbarui".' oleh '.auth()->user()->full_name.'.',
|
body: "Varian \"{$variant->variant}\" berhasil diperbarui".' oleh '.auth()->user()->full_name.'.',
|
||||||
url: route('admin.master.raw-materials.index', ['highlight' => $variant->raw_material_id]),
|
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
|
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) {
|
$result = DB::transaction(function () use ($variant) {
|
||||||
return $variant->delete();
|
return $variant->delete();
|
||||||
});
|
});
|
||||||
|
|
||||||
NotificationService::notify(
|
NotificationService::notify(
|
||||||
roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
|
roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
|
||||||
title: 'Varian Dihapus',
|
title: 'Varian Dihapus',
|
||||||
body: "Varian \"{$variant->variant}\" dari bahan baku \"{$rawMaterial->name}\" berhasil dihapus".' oleh '.auth()->user()->full_name.'.',
|
body: "Varian \"{$variant->variant}\" dari bahan baku \"{$rawMaterial->name}\" berhasil dihapus".' oleh '.auth()->user()->full_name.'.',
|
||||||
url: route('admin.master.raw-materials.index'),
|
url: route('admin.master.raw-materials.index'),
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
Plus,
|
Plus,
|
||||||
Trash2,
|
Trash2,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useCallback, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { ConfirmDialog } from '@/components/dialogs';
|
import { ConfirmDialog } from '@/components/dialogs';
|
||||||
import { FileUpload } from '@/components/inputs';
|
import { FileUpload } from '@/components/inputs';
|
||||||
@ -134,6 +134,23 @@ export default function RawMaterialEdit({ rawMaterial }: Props) {
|
|||||||
const [deleteVariantIndex, setDeleteVariantIndex] = useState<number | null>(
|
const [deleteVariantIndex, setDeleteVariantIndex] = useState<number | null>(
|
||||||
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) => {
|
const confirmRemoveVariant = useCallback((index: number) => {
|
||||||
setDeleteVariantIndex(index);
|
setDeleteVariantIndex(index);
|
||||||
@ -222,9 +239,6 @@ export default function RawMaterialEdit({ rawMaterial }: Props) {
|
|||||||
...data,
|
...data,
|
||||||
...getPayload(),
|
...getPayload(),
|
||||||
})}
|
})}
|
||||||
onError={() => {
|
|
||||||
toast.error('Ada data yang belum sesuai, silakan periksa kembali input Anda.');
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{({ errors, processing }) => (
|
{({ errors, processing }) => (
|
||||||
<>
|
<>
|
||||||
@ -555,6 +569,10 @@ export default function RawMaterialEdit({ rawMaterial }: Props) {
|
|||||||
confirmLabel="Hapus"
|
confirmLabel="Hapus"
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
if (deleteVariantIndex !== null) {
|
if (deleteVariantIndex !== null) {
|
||||||
|
const removed = variants[deleteVariantIndex];
|
||||||
|
if (removed.id !== null) {
|
||||||
|
setRemovedVariants((prev) => [...prev, removed]);
|
||||||
|
}
|
||||||
removeVariant(deleteVariantIndex);
|
removeVariant(deleteVariantIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -141,6 +141,7 @@ export default function RawMaterialIndex({ rawMaterials, rawMaterialNames, filte
|
|||||||
|
|
||||||
router.delete(destroy.url(deleting.id), {
|
router.delete(destroy.url(deleting.id), {
|
||||||
onSuccess: () => setDeleting(null),
|
onSuccess: () => setDeleting(null),
|
||||||
|
onError: () => setDeleting(null),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,6 +157,7 @@ export default function RawMaterialIndex({ rawMaterials, rawMaterialNames, filte
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
onSuccess: () => setDeletingVariant(null),
|
onSuccess: () => setDeletingVariant(null),
|
||||||
|
onError: () => setDeletingVariant(null),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user