diff --git a/app/Enums/CuttingStatus.php b/app/Enums/CuttingStatus.php index fd739a1..81152da 100644 --- a/app/Enums/CuttingStatus.php +++ b/app/Enums/CuttingStatus.php @@ -46,7 +46,7 @@ public function canTransitionTo(self $status): bool { return match ($this) { self::IN_PROGRESS => $status === self::COMPLETED, - self::COMPLETED => in_array($status, [self::PENDING_VERIFICATION, self::REJECTED, self::IN_PROGRESS], true), + self::COMPLETED => in_array($status, [self::PENDING_VERIFICATION, self::REJECTED, self::IN_PROGRESS, self::VERIFIED], true), self::PENDING_VERIFICATION => in_array($status, [self::VERIFIED, self::REJECTED, self::COMPLETED], true), self::REJECTED => $status === self::IN_PROGRESS, default => false, @@ -80,7 +80,15 @@ public function availableActions(): array 'icon_only' => false, ], ], - self::COMPLETED => [], + self::COMPLETED => [ + [ + 'status' => self::VERIFIED->value, + 'label' => 'Verifikasi', + 'destructive' => false, + 'permission' => Permission::CUTTINGS_VERIFY->value, + 'icon_only' => false, + ], + ], self::PENDING_VERIFICATION => [], self::REJECTED => [ [ diff --git a/app/Http/Requests/Admin/Manage/CuttingStatusTransitionRequest.php b/app/Http/Requests/Admin/Manage/CuttingStatusTransitionRequest.php index c438409..8490f72 100644 --- a/app/Http/Requests/Admin/Manage/CuttingStatusTransitionRequest.php +++ b/app/Http/Requests/Admin/Manage/CuttingStatusTransitionRequest.php @@ -5,6 +5,7 @@ use App\Enums\CuttingStatus; use App\Enums\Permission; use App\Enums\PriceType; +use App\Enums\Role; use App\Models\Cutting; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; @@ -83,6 +84,14 @@ public function withValidator(Validator $validator): void $validator->errors()->add('status', 'Status cutting tidak dapat diubah.'); } + // Only owner/developer can verify directly from COMPLETED status + if ($cutting->status === CuttingStatus::COMPLETED && $status === CuttingStatus::VERIFIED) { + $user = $this->user(); + if (! $user || ! $user->hasRole([Role::OWNER->value, Role::DEVELOPER->value])) { + $validator->errors()->add('status', 'Hanya owner yang dapat langsung memverifikasi cutting.'); + } + } + if ($status === CuttingStatus::REJECTED && trim((string) $this->input('reason')) === '') { $validator->errors()->add('reason', 'Alasan penolakan wajib diisi.'); } diff --git a/app/Services/Manage/CuttingService.php b/app/Services/Manage/CuttingService.php index 53e1fb8..73fb555 100644 --- a/app/Services/Manage/CuttingService.php +++ b/app/Services/Manage/CuttingService.php @@ -3,6 +3,7 @@ namespace App\Services\Manage; use App\Enums\CuttingStatus; +use App\Enums\Role; use App\Models\Cutting; use App\Models\CuttingMaterial; use App\Models\CuttingResult; @@ -58,12 +59,7 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi ->paginate(25) ->withQueryString() ->through(function (Cutting $cutting) use ($user) { - $actions = collect($cutting->status->availableActions()) - ->filter(fn (array $action) => $user->can($action['permission'])) - ->values() - ->all(); - - $cutting->setAttribute('available_actions', $actions); + $cutting->setAttribute('available_actions', $this->filterActionsForUser($cutting, $user)); $cutting->setAttribute('is_editable', $cutting->status->isEditable()); $this->appendCostPreview($cutting); @@ -85,12 +81,7 @@ public function getInProgressCuttings(User $user): Collection ->latest() ->get() ->each(function (Cutting $cutting) use ($user): void { - $actions = collect($cutting->status->availableActions()) - ->filter(fn (array $action) => $user->can($action['permission'])) - ->values() - ->all(); - - $cutting->setAttribute('available_actions', $actions); + $cutting->setAttribute('available_actions', $this->filterActionsForUser($cutting, $user)); $cutting->setAttribute('is_editable', $cutting->status->isEditable()); $this->appendCostPreview($cutting); }); @@ -110,12 +101,7 @@ public function getCompletedCuttings(User $user): Collection ->latest() ->get() ->each(function (Cutting $cutting) use ($user): void { - $actions = collect($cutting->status->availableActions()) - ->filter(fn (array $action) => $user->can($action['permission'])) - ->values() - ->all(); - - $cutting->setAttribute('available_actions', $actions); + $cutting->setAttribute('available_actions', $this->filterActionsForUser($cutting, $user)); $cutting->setAttribute('is_editable', $cutting->status->isEditable()); $this->appendCostPreview($cutting); }); @@ -891,6 +877,25 @@ private function formatMaterialUsageSummary(Cutting $cutting): string ->join(', '); } + /** + * @return list + */ + private function filterActionsForUser(Cutting $cutting, User $user): array + { + return collect($cutting->status->availableActions()) + ->filter(fn (array $action) => $user->can($action['permission'])) + ->filter(function (array $action) use ($cutting, $user): bool { + // Only owner/developer can verify directly from COMPLETED status + if ($cutting->status === CuttingStatus::COMPLETED && $action['status'] === CuttingStatus::VERIFIED->value) { + return $user->hasRole([Role::OWNER->value, Role::DEVELOPER->value]); + } + + return true; + }) + ->values() + ->all(); + } + private function appendCostPreview(Cutting $cutting): void { $cutting->setAttribute('total_result_pieces', (int) $cutting->results->sum('cutting_result')); diff --git a/app/Services/Manage/PurchaseService.php b/app/Services/Manage/PurchaseService.php index 6d4df3b..186a915 100644 --- a/app/Services/Manage/PurchaseService.php +++ b/app/Services/Manage/PurchaseService.php @@ -4,6 +4,7 @@ use App\Enums\OwnerVerificationAction; use App\Enums\OwnerVerificationStatus; +use App\Enums\Permission; use App\Models\OwnerVerificationRequest; use App\Models\Purchase; use App\Models\PurchaseItem; @@ -201,8 +202,10 @@ public function removeDraftItem(User $user, RawMaterialPrice $rawMaterialPrice): public function create(array $validated, User $user): Purchase { + $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value); + try { - $purchase = DB::transaction(function () use ($validated, $user): Purchase { + $purchase = DB::transaction(function () use ($validated, $user, $isOwner): Purchase { $draftItems = $this->draftItemsQuery($user) ->lockForUpdate() @@ -239,24 +242,30 @@ public function create(array $validated, User $user): Purchase $purchase->load(['supplier', 'items.rawMaterialPrice.rawMaterial:id,name']); - OwnerVerificationRequest::create([ - 'action' => OwnerVerificationAction::CREATE, - 'status' => OwnerVerificationStatus::PENDING, - 'subject_type' => Purchase::class, - 'subject_id' => $purchase->id, - 'submitted_by_id' => $user->id, - 'payload' => [ - 'old' => null, - 'new' => $this->snapshotPurchase($purchase), - ], - ]); + if ($isOwner) { + foreach ($purchase->items as $item) { + $this->incrementStock($item); + } + } else { + OwnerVerificationRequest::create([ + 'action' => OwnerVerificationAction::CREATE, + 'status' => OwnerVerificationStatus::PENDING, + 'subject_type' => Purchase::class, + 'subject_id' => $purchase->id, + 'submitted_by_id' => $user->id, + 'payload' => [ + 'old' => null, + 'new' => $this->snapshotPurchase($purchase), + ], + ]); + } return $purchase; }); } catch (ValidationException $e) { throw $e; } catch (\Throwable $e) { - Log::error('Gagal mengajukan pembelian: '.$e->getMessage(), [ + Log::error('Gagal membuat pembelian: '.$e->getMessage(), [ 'trace' => $e->getTraceAsString(), ]); @@ -265,43 +274,55 @@ public function create(array $validated, User $user): Purchase ]); } - $this->notifyForPendingRequest( - $user, - 'Tambah Belanja', - "Pengajuan belanja dari supplier {$purchase->supplier->name} senilai {$purchase->total_formatted} menunggu verifikasi owner.", - route('admin.manage.purchases.index', ['search' => $purchase->supplier->name]), - $purchase->supplier->name, - ); + if (! $isOwner) { + $this->notifyForPendingRequest( + $user, + 'Tambah Belanja', + "Pengajuan belanja dari supplier {$purchase->supplier->name} senilai {$purchase->total_formatted} menunggu verifikasi owner.", + route('admin.manage.purchases.index', ['search' => $purchase->supplier->name]), + $purchase->supplier->name, + ); + } return $purchase; } public function update(Purchase $purchase, array $validated, User $user): void { + $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value); $purchase->load(['supplier', 'items.rawMaterialPrice.rawMaterial:id,name']); try { - DB::transaction(function () use ($purchase, $validated, $user): void { - $verificationRequest = OwnerVerificationRequest::create([ - 'action' => OwnerVerificationAction::UPDATE, - 'status' => OwnerVerificationStatus::PENDING, - 'subject_type' => Purchase::class, - 'subject_id' => $purchase->id, - 'submitted_by_id' => $user->id, - 'payload' => [ - 'old' => $this->snapshotPurchase($purchase), - 'new' => $this->buildPayloadFromValidated($validated), - ], - ]); + DB::transaction(function () use ($purchase, $validated, $user, $isOwner): void { + if ($isOwner) { + $payload = $this->buildPayloadFromValidated($validated); + $this->applyPayloadToPurchase($purchase, $payload); - if (($validated['photos'] ?? null) !== null || ($validated['remove_media_ids'] ?? []) !== []) { - $this->syncRequestPhotos($verificationRequest, $validated); + if (($validated['photos'] ?? null) !== null || ($validated['remove_media_ids'] ?? []) !== []) { + $this->syncPhotos($purchase, $validated); + } + } else { + $verificationRequest = OwnerVerificationRequest::create([ + 'action' => OwnerVerificationAction::UPDATE, + 'status' => OwnerVerificationStatus::PENDING, + 'subject_type' => Purchase::class, + 'subject_id' => $purchase->id, + 'submitted_by_id' => $user->id, + 'payload' => [ + 'old' => $this->snapshotPurchase($purchase), + 'new' => $this->buildPayloadFromValidated($validated), + ], + ]); + + if (($validated['photos'] ?? null) !== null || ($validated['remove_media_ids'] ?? []) !== []) { + $this->syncRequestPhotos($verificationRequest, $validated); + } } }); } catch (ValidationException $e) { throw $e; } catch (\Throwable $e) { - Log::error('Gagal mengajukan perubahan belanja: '.$e->getMessage(), [ + Log::error('Gagal memperbarui pembelian: '.$e->getMessage(), [ 'trace' => $e->getTraceAsString(), ]); @@ -310,19 +331,29 @@ public function update(Purchase $purchase, array $validated, User $user): void ]); } - $purchase->load('supplier'); + if (! $isOwner) { + $purchase->load('supplier'); - $this->notifyForPendingRequest( - $user, - 'Ubah Belanja', - "Pengajuan ubah belanja dari supplier {$purchase->supplier->name} menunggu verifikasi owner.", - route('admin.manage.purchases.index', ['search' => $purchase->supplier->name]), - $purchase->supplier->name, - ); + $this->notifyForPendingRequest( + $user, + 'Ubah Belanja', + "Pengajuan ubah belanja dari supplier {$purchase->supplier->name} menunggu verifikasi owner.", + route('admin.manage.purchases.index', ['search' => $purchase->supplier->name]), + $purchase->supplier->name, + ); + } } public function delete(Purchase $purchase, User $user): void { + $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value); + + if ($isOwner) { + $this->executeDelete($purchase); + + return; + } + $purchase->load(['supplier', 'items.rawMaterialPrice.rawMaterial:id,name']); try { diff --git a/app/Services/Manage/StockRetailService.php b/app/Services/Manage/StockRetailService.php index 9fab8dd..ffc6c51 100644 --- a/app/Services/Manage/StockRetailService.php +++ b/app/Services/Manage/StockRetailService.php @@ -4,6 +4,7 @@ use App\Enums\OwnerVerificationAction; use App\Enums\OwnerVerificationStatus; +use App\Enums\Permission; use App\Models\OwnerVerificationRequest; use App\Models\ProductVariant; use App\Models\StockRetailHistory; @@ -19,7 +20,7 @@ public function __construct( private readonly PushNotificationService $pushNotificationService, ) {} - public function transfer(int $variantId, int $quantity, User $user, ?string $notes = null): OwnerVerificationRequest + public function transfer(int $variantId, int $quantity, User $user, ?string $notes = null): ?OwnerVerificationRequest { if ($quantity <= 0) { throw ValidationException::withMessages([ @@ -35,6 +36,14 @@ public function transfer(int $variantId, int $quantity, User $user, ?string $not ]); } + $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value); + + if ($isOwner) { + $this->executeTransfer($variant, $quantity, $user, $notes); + + return null; + } + $existingPending = OwnerVerificationRequest::query() ->where('subject_type', ProductVariant::class) ->where('subject_id', $variant->id) @@ -111,9 +120,20 @@ public function applyStockRetailTransfer(OwnerVerificationRequest $request): voi return; } - DB::transaction(function () use ($request, $quantity, $newData): void { + $variant = ProductVariant::query()->find($request->subject_id); + + if (! $variant) { + return; + } + + $this->executeTransfer($variant, $quantity, $request->submittedBy, $newData['notes'] ?? null); + } + + private function executeTransfer(ProductVariant $variant, int $quantity, User $user, ?string $notes = null): void + { + DB::transaction(function () use ($variant, $quantity, $user, $notes): void { $variant = ProductVariant::query() - ->whereKey($request->subject_id) + ->whereKey($variant->id) ->lockForUpdate() ->firstOrFail(); @@ -125,13 +145,13 @@ public function applyStockRetailTransfer(OwnerVerificationRequest $request): voi StockRetailHistory::create([ 'product_variant_id' => $variant->id, - 'user_id' => $request->submitted_by_id, + 'user_id' => $user->id, 'quantity' => $quantity, 'stock_before' => $stockBefore, 'stock_retail_before' => $stockRetailBefore, 'stock_after' => $stockBefore - $quantity, 'stock_retail_after' => $stockRetailBefore + $quantity, - 'notes' => $newData['notes'] ?? null, + 'notes' => $notes, 'created_at' => now(), ]); }); diff --git a/app/Services/Master/ProductService.php b/app/Services/Master/ProductService.php index a8e360d..10a1c9d 100644 --- a/app/Services/Master/ProductService.php +++ b/app/Services/Master/ProductService.php @@ -4,6 +4,7 @@ use App\Enums\OwnerVerificationAction; use App\Enums\OwnerVerificationStatus; +use App\Enums\Permission; use App\Models\Category; use App\Models\OwnerVerificationRequest; use App\Models\Product; @@ -115,12 +116,14 @@ public function findForEdit(Product $product): Product public function create(array $validated, User $user): void { + $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value); + try { - DB::transaction(function () use ($validated, $user): void { + DB::transaction(function () use ($validated, $user, $isOwner): void { $product = Product::create([ 'name' => $validated['name'], 'description' => $validated['description'] ?? null, - 'is_active' => false, + 'is_active' => $isOwner, ]); $product->categories()->sync($validated['category_ids'] ?? []); @@ -135,64 +138,24 @@ public function create(array $validated, User $user): void $this->syncVariantImages($variant, $variantData, $index); } - OwnerVerificationRequest::create([ - 'action' => OwnerVerificationAction::CREATE, - 'status' => OwnerVerificationStatus::PENDING, - 'subject_type' => Product::class, - 'subject_id' => $product->id, - 'submitted_by_id' => $user->id, - 'payload' => [ - 'old' => null, - 'new' => $this->snapshotProduct($product->fresh(['categories', 'variants'])), - ], - ]); - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal mengajukan pembuatan produk: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } - - $this->notifyForPendingRequest( - $user, - 'Tambah Produk', - "Pengajuan tambah produk '{$validated['name']}' menunggu verifikasi owner.", - route('admin.master.products.index', ['search' => $validated['name']]), - $validated['name'], - ); - } - - public function update(Product $product, array $validated, User $user): void - { - try { - DB::transaction(function () use ($validated, $product, $user): void { - $verificationRequest = OwnerVerificationRequest::create([ - 'action' => OwnerVerificationAction::UPDATE, - 'status' => OwnerVerificationStatus::PENDING, - 'subject_type' => Product::class, - 'subject_id' => $product->id, - 'submitted_by_id' => $user->id, - 'payload' => [ - 'old' => $this->snapshotProduct($product), - 'new' => $this->enrichPayload($this->buildPayloadFromValidated($validated)), - ], - ]); - - foreach ($validated['variants'] as $index => $variantData) { - $isNewVariant = empty($variantData['id']); - $this->syncRequestVariantImages($verificationRequest, $variantData, $index, required: $isNewVariant); + if (! $isOwner) { + OwnerVerificationRequest::create([ + 'action' => OwnerVerificationAction::CREATE, + 'status' => OwnerVerificationStatus::PENDING, + 'subject_type' => Product::class, + 'subject_id' => $product->id, + 'submitted_by_id' => $user->id, + 'payload' => [ + 'old' => null, + 'new' => $this->snapshotProduct($product->fresh(['categories', 'variants'])), + ], + ]); } }); } catch (ValidationException $e) { throw $e; } catch (\Throwable $e) { - Log::error('Gagal mengajukan perubahan produk: '.$e->getMessage(), [ + Log::error('Gagal membuat produk: '.$e->getMessage(), [ 'trace' => $e->getTraceAsString(), ]); @@ -201,17 +164,94 @@ public function update(Product $product, array $validated, User $user): void ]); } - $this->notifyForPendingRequest( - $user, - 'Ubah Produk', - "Pengajuan ubah produk '{$product->name}' menunggu verifikasi owner.", - route('admin.master.products.index', ['search' => $product->name]), - $product->name, - ); + if (! $isOwner) { + $this->notifyForPendingRequest( + $user, + 'Tambah Produk', + "Pengajuan tambah produk '{$validated['name']}' menunggu verifikasi owner.", + route('admin.master.products.index', ['search' => $validated['name']]), + $validated['name'], + ); + } + } + + public function update(Product $product, array $validated, User $user): void + { + $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value); + + try { + DB::transaction(function () use ($validated, $product, $user, $isOwner): void { + if ($isOwner) { + $payload = $this->enrichPayload($this->buildPayloadFromValidated($validated)); + $this->applyPayloadToProduct($product, $payload); + + foreach ($validated['variants'] as $index => $variantData) { + if (! empty($variantData['id'])) { + $variant = $product->variants()->find($variantData['id']); + if ($variant) { + $this->syncVariantImages($variant, $variantData, $index); + } + } else { + $variant = $product->variants()->create([ + 'name' => $variantData['name'], + 'stock' => $variantData['stock'], + 'stock_retail' => $variantData['stock_retail'], + ]); + $this->syncVariantImages($variant, $variantData, $index); + } + } + } else { + $verificationRequest = OwnerVerificationRequest::create([ + 'action' => OwnerVerificationAction::UPDATE, + 'status' => OwnerVerificationStatus::PENDING, + 'subject_type' => Product::class, + 'subject_id' => $product->id, + 'submitted_by_id' => $user->id, + 'payload' => [ + 'old' => $this->snapshotProduct($product), + 'new' => $this->enrichPayload($this->buildPayloadFromValidated($validated)), + ], + ]); + + foreach ($validated['variants'] as $index => $variantData) { + $isNewVariant = empty($variantData['id']); + $this->syncRequestVariantImages($verificationRequest, $variantData, $index, required: $isNewVariant); + } + } + }); + } catch (ValidationException $e) { + throw $e; + } catch (\Throwable $e) { + Log::error('Gagal memperbarui produk: '.$e->getMessage(), [ + 'trace' => $e->getTraceAsString(), + ]); + + throw ValidationException::withMessages([ + 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', + ]); + } + + if (! $isOwner) { + $this->notifyForPendingRequest( + $user, + 'Ubah Produk', + "Pengajuan ubah produk '{$product->name}' menunggu verifikasi owner.", + route('admin.master.products.index', ['search' => $product->name]), + $product->name, + ); + } } public function delete(Product $product, User $user): void { + $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value); + + if ($isOwner) { + $this->applyDeleteSubject($product); + + return; + } + try { OwnerVerificationRequest::create([ 'action' => OwnerVerificationAction::DELETE, @@ -245,6 +285,16 @@ public function delete(Product $product, User $user): void public function toggleStatus(Product $product, array $validated, User $user): void { + $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value); + + if ($isOwner) { + $product->update([ + 'is_active' => (bool) $validated['is_active'], + ]); + + return; + } + try { OwnerVerificationRequest::create([ 'action' => OwnerVerificationAction::TOGGLE_STATUS, @@ -418,6 +468,11 @@ public function applyDelete(OwnerVerificationRequest $verificationRequest): void ]); } + $this->applyDeleteSubject($product); + } + + private function applyDeleteSubject(Product $product): void + { DB::transaction(function () use ($product): void { $product->variants()->each(function (ProductVariant $variant): void { $variant->clearMediaCollection('images'); diff --git a/app/Services/Master/RawMaterialService.php b/app/Services/Master/RawMaterialService.php index 8b36c61..906133e 100644 --- a/app/Services/Master/RawMaterialService.php +++ b/app/Services/Master/RawMaterialService.php @@ -4,6 +4,7 @@ use App\Enums\OwnerVerificationAction; use App\Enums\OwnerVerificationStatus; +use App\Enums\Permission; use App\Enums\RawMaterialUnit; use App\Models\OwnerVerificationRequest; use App\Models\RawMaterial; @@ -109,76 +110,38 @@ public function findForEdit(RawMaterial $rawMaterial): RawMaterial public function create(array $validated, User $user): void { + $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value); + try { - DB::transaction(function () use ($validated, $user): void { + DB::transaction(function () use ($validated, $user, $isOwner): void { $rawMaterial = RawMaterial::create([ 'name' => $validated['name'], 'unit' => $validated['unit'], - 'is_active' => false, + 'is_active' => $isOwner, ]); foreach ($validated['prices'] as $index => $priceData) { $this->createPrice($rawMaterial, $priceData, $index); } - OwnerVerificationRequest::create([ - 'action' => OwnerVerificationAction::CREATE, - 'status' => OwnerVerificationStatus::PENDING, - 'subject_type' => RawMaterial::class, - 'subject_id' => $rawMaterial->id, - 'submitted_by_id' => $user->id, - 'payload' => [ - 'old' => null, - 'new' => $this->snapshotRawMaterial($rawMaterial->fresh(['prices'])), - ], - ]); - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal mengajukan pembuatan bahan baku: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } - - $this->notifyForPendingRequest( - $user, - 'Tambah Bahan Baku', - "Pengajuan tambah bahan baku '{$validated['name']}' menunggu verifikasi owner.", - route('admin.master.raw_materials.index', ['search' => $validated['name']]), - $validated['name'], - ); - } - - public function update(RawMaterial $rawMaterial, array $validated, User $user): void - { - try { - DB::transaction(function () use ($validated, $rawMaterial, $user): void { - $verificationRequest = OwnerVerificationRequest::create([ - 'action' => OwnerVerificationAction::UPDATE, - 'status' => OwnerVerificationStatus::PENDING, - 'subject_type' => RawMaterial::class, - 'subject_id' => $rawMaterial->id, - 'submitted_by_id' => $user->id, - 'payload' => [ - 'old' => $this->snapshotRawMaterial($rawMaterial), - 'new' => $this->enrichPayload($this->buildPayloadFromValidated($validated)), - ], - ]); - - foreach ($validated['prices'] as $index => $priceData) { - $isNewPrice = empty($priceData['id']); - $this->syncRequestPriceImages($verificationRequest, $priceData, $index, required: $isNewPrice); + if (! $isOwner) { + OwnerVerificationRequest::create([ + 'action' => OwnerVerificationAction::CREATE, + 'status' => OwnerVerificationStatus::PENDING, + 'subject_type' => RawMaterial::class, + 'subject_id' => $rawMaterial->id, + 'submitted_by_id' => $user->id, + 'payload' => [ + 'old' => null, + 'new' => $this->snapshotRawMaterial($rawMaterial->fresh(['prices'])), + ], + ]); } }); } catch (ValidationException $e) { throw $e; } catch (\Throwable $e) { - Log::error('Gagal mengajukan perubahan bahan baku: '.$e->getMessage(), [ + Log::error('Gagal membuat bahan baku: '.$e->getMessage(), [ 'trace' => $e->getTraceAsString(), ]); @@ -187,17 +150,89 @@ public function update(RawMaterial $rawMaterial, array $validated, User $user): ]); } - $this->notifyForPendingRequest( - $user, - 'Ubah Bahan Baku', - "Pengajuan ubah bahan baku '{$rawMaterial->name}' menunggu verifikasi owner.", - route('admin.master.raw_materials.index', ['search' => $rawMaterial->name]), - $rawMaterial->name, - ); + if (! $isOwner) { + $this->notifyForPendingRequest( + $user, + 'Tambah Bahan Baku', + "Pengajuan tambah bahan baku '{$validated['name']}' menunggu verifikasi owner.", + route('admin.master.raw_materials.index', ['search' => $validated['name']]), + $validated['name'], + ); + } + } + + public function update(RawMaterial $rawMaterial, array $validated, User $user): void + { + $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value); + + try { + DB::transaction(function () use ($validated, $rawMaterial, $user, $isOwner): void { + if ($isOwner) { + $payload = $this->enrichPayload($this->buildPayloadFromValidated($validated)); + $this->applyPayloadToRawMaterial($rawMaterial, $payload); + + foreach ($validated['prices'] as $index => $priceData) { + if (! empty($priceData['id'])) { + $price = $rawMaterial->prices()->find($priceData['id']); + if ($price) { + $this->syncPriceImages($price, $priceData, $index); + } + } else { + $this->createPrice($rawMaterial, $priceData, $index); + } + } + } else { + $verificationRequest = OwnerVerificationRequest::create([ + 'action' => OwnerVerificationAction::UPDATE, + 'status' => OwnerVerificationStatus::PENDING, + 'subject_type' => RawMaterial::class, + 'subject_id' => $rawMaterial->id, + 'submitted_by_id' => $user->id, + 'payload' => [ + 'old' => $this->snapshotRawMaterial($rawMaterial), + 'new' => $this->enrichPayload($this->buildPayloadFromValidated($validated)), + ], + ]); + + foreach ($validated['prices'] as $index => $priceData) { + $isNewPrice = empty($priceData['id']); + $this->syncRequestPriceImages($verificationRequest, $priceData, $index, required: $isNewPrice); + } + } + }); + } catch (ValidationException $e) { + throw $e; + } catch (\Throwable $e) { + Log::error('Gagal memperbarui bahan baku: '.$e->getMessage(), [ + 'trace' => $e->getTraceAsString(), + ]); + + throw ValidationException::withMessages([ + 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', + ]); + } + + if (! $isOwner) { + $this->notifyForPendingRequest( + $user, + 'Ubah Bahan Baku', + "Pengajuan ubah bahan baku '{$rawMaterial->name}' menunggu verifikasi owner.", + route('admin.master.raw_materials.index', ['search' => $rawMaterial->name]), + $rawMaterial->name, + ); + } } public function delete(RawMaterial $rawMaterial, User $user): void { + $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value); + + if ($isOwner) { + $this->applyDeleteSubject($rawMaterial); + + return; + } + try { OwnerVerificationRequest::create([ 'action' => OwnerVerificationAction::DELETE, @@ -231,6 +266,16 @@ public function delete(RawMaterial $rawMaterial, User $user): void public function toggleStatus(RawMaterial $rawMaterial, array $validated, User $user): void { + $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value); + + if ($isOwner) { + $rawMaterial->update([ + 'is_active' => (bool) $validated['is_active'], + ]); + + return; + } + try { OwnerVerificationRequest::create([ 'action' => OwnerVerificationAction::TOGGLE_STATUS, @@ -366,6 +411,11 @@ public function applyDelete(OwnerVerificationRequest $verificationRequest): void ]); } + $this->applyDeleteSubject($rawMaterial); + } + + private function applyDeleteSubject(RawMaterial $rawMaterial): void + { DB::transaction(function () use ($rawMaterial): void { $rawMaterial->prices()->each(function (RawMaterialPrice $price): void { $price->clearMediaCollection('images');