feat: implement permission-based messaging for purchase and stock verification actions, enhancing user feedback based on roles

This commit is contained in:
Yoga Pangestu 2026-07-10 11:26:06 +07:00
parent fa4f6d4737
commit b87203d6ab
5 changed files with 102 additions and 13 deletions

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Admin\Manage\Purchase; namespace App\Http\Controllers\Admin\Manage\Purchase;
use App\Enums\Permission;
use App\Http\Controllers\Concerns\FlashesEntityMessage; use App\Http\Controllers\Concerns\FlashesEntityMessage;
use App\Http\Controllers\Concerns\ParsesDataTableQuery; use App\Http\Controllers\Concerns\ParsesDataTableQuery;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
@ -46,7 +47,11 @@ public function store(PurchaseRequest $request): RedirectResponse
{ {
$this->purchaseService->create($request->validated(), $request->user()); $this->purchaseService->create($request->validated(), $request->user());
$this->flashSuccess('Belanja berhasil diajukan dan menunggu verifikasi owner.'); if ($request->user()->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) {
$this->flashCreated('Belanja');
} else {
$this->flashSuccess('Belanja berhasil diajukan dan menunggu verifikasi owner.');
}
return redirect()->route('admin.manage.purchases.index'); return redirect()->route('admin.manage.purchases.index');
} }
@ -64,7 +69,11 @@ public function update(PurchaseRequest $request, Purchase $purchase): RedirectRe
{ {
$this->purchaseService->update($purchase, $request->validated(), $request->user()); $this->purchaseService->update($purchase, $request->validated(), $request->user());
$this->flashSuccess('Perubahan belanja berhasil diajukan dan menunggu verifikasi owner.'); if ($request->user()->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) {
$this->flashUpdated('Belanja');
} else {
$this->flashSuccess('Perubahan belanja berhasil diajukan dan menunggu verifikasi owner.');
}
return redirect()->route('admin.manage.purchases.index'); return redirect()->route('admin.manage.purchases.index');
} }
@ -73,7 +82,11 @@ public function destroy(Request $request, Purchase $purchase): RedirectResponse
{ {
$this->purchaseService->delete($purchase, $request->user()); $this->purchaseService->delete($purchase, $request->user());
$this->flashSuccess('Penghapusan belanja berhasil diajukan dan menunggu verifikasi owner.'); if ($request->user()->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) {
$this->flashDeleted('Belanja');
} else {
$this->flashSuccess('Penghapusan belanja berhasil diajukan dan menunggu verifikasi owner.');
}
return redirect()->route('admin.manage.purchases.index'); return redirect()->route('admin.manage.purchases.index');
} }

View File

@ -26,7 +26,7 @@ public function transfer(RetailStockTransferRequest $request): JsonResponse
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'message' => 'Pengajuan transfer stok ecer berhasil dikirim dan menunggu verifikasi owner.', 'message' => 'Transfer stok ecer berhasil dilakukan.',
]); ]);
} }
} }

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Admin\Manage\Stock; namespace App\Http\Controllers\Admin\Manage\Stock;
use App\Enums\Permission;
use App\Http\Controllers\Concerns\FlashesEntityMessage; use App\Http\Controllers\Concerns\FlashesEntityMessage;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Manage\StockVerifyRequest; use App\Http\Requests\Admin\Manage\StockVerifyRequest;
@ -40,7 +41,11 @@ public function verify(StockVerifyRequest $request, Cutting $cutting): RedirectR
$request->validated('result_prices'), $request->validated('result_prices'),
); );
$this->flashSuccess('Verifikasi berhasil diajukan. Menunggu persetujuan owner.'); if ($request->user()->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) {
$this->flashSuccess('Verifikasi berhasil disimpan. Stok produk telah ditambahkan ke toko.');
} else {
$this->flashSuccess('Verifikasi berhasil diajukan. Menunggu persetujuan owner.');
}
return redirect()->route('admin.manage.stocks.index'); return redirect()->route('admin.manage.stocks.index');
} }

View File

@ -3,6 +3,7 @@
namespace App\Services\Manage; namespace App\Services\Manage;
use App\Enums\CuttingStatus; use App\Enums\CuttingStatus;
use App\Enums\Permission;
use App\Models\Cutting; use App\Models\Cutting;
use App\Models\CuttingMaterial; use App\Models\CuttingMaterial;
use App\Models\CuttingResult; use App\Models\CuttingResult;
@ -85,8 +86,10 @@ public function submitVerification(
]); ]);
} }
$isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value);
try { try {
DB::transaction(function () use ($cutting, $user, $verificationNote, $results, $resultPrices): void { DB::transaction(function () use ($cutting, $user, $verificationNote, $results, $resultPrices, $isOwner): void {
$cutting->load(['materials.rawMaterialPrice', 'results']); $cutting->load(['materials.rawMaterialPrice', 'results']);
if ($results !== null) { if ($results !== null) {
@ -111,7 +114,15 @@ public function submitVerification(
} }
$cutting->submitted_by_id = $user->id; $cutting->submitted_by_id = $user->id;
$cutting->status = CuttingStatus::PENDING_VERIFICATION;
if ($isOwner) {
$this->applyProductStockOnVerify($cutting);
$this->applyResultPricesToProducts($cutting);
$cutting->status = CuttingStatus::VERIFIED;
} else {
$cutting->status = CuttingStatus::PENDING_VERIFICATION;
}
$cutting->save(); $cutting->save();
}); });
} catch (ValidationException $e) { } catch (ValidationException $e) {
@ -128,14 +139,26 @@ public function submitVerification(
$description = $cutting->description ?? '-'; $description = $cutting->description ?? '-';
$this->pushNotificationService->sendToRoles( if ($isOwner) {
'📦 Verifikasi Stok Menunggu Persetujuan', $this->pushNotificationService->sendToRoles(
"Cutting dengan deskripsi '{$description}' telah diajukan verifikasi dan menunggu persetujuan owner.", '📦 Stok Cutting Diverifikasi',
['owner', 'developer', 'direktur'], "Cutting dengan deskripsi '{$description}' telah disetujui owner dan stok produk telah ditambahkan ke toko.",
route('admin.manage.stocks.index'), ['owner', 'developer', 'direktur'],
); route('admin.manage.stocks.index'),
);
} else {
$this->pushNotificationService->sendToRoles(
'📦 Verifikasi Stok Menunggu Persetujuan',
"Cutting dengan deskripsi '{$description}' telah diajukan verifikasi dan menunggu persetujuan owner.",
['owner', 'developer', 'direktur'],
route('admin.manage.stocks.index'),
);
}
$this->cacheForgetByPattern('manage:stocks:*'); $this->cacheForgetByPattern('manage:stocks:*');
if ($isOwner) {
$this->cacheForgetByPattern('prices:*');
}
} }
public function approveVerification( public function approveVerification(

View File

@ -313,4 +313,52 @@ function createCompletedCuttingSetup(): array
]) ])
->assertForbidden(); ->assertForbidden();
}); });
test('owner can verify stock directly without pending verification', function () {
$user = createStockUserWithPermission(
PermissionEnum::STOCKS_VIEW,
PermissionEnum::CUTTINGS_VERIFY,
PermissionEnum::OWNER_VERIFICATIONS_VERIFY
);
$setup = createCompletedCuttingSetup();
$cutting = $setup['cutting'];
$payload = [
'verification_note' => 'Verified directly by owner',
'results' => [
[
'product_variant_id' => $setup['variant']->id,
'good' => 6,
'reject' => 1,
],
],
'result_prices' => [
[
'product_variant_id' => $setup['variant']->id,
'prices' => [
[
'type' => 'harga_modal',
'price' => 50000,
],
[
'type' => 'retail',
'price' => 75000,
],
],
],
],
];
$this->actingAs($user)
->post(route('admin.manage.stocks.verify', $cutting), $payload)
->assertRedirect(route('admin.manage.stocks.index'));
$cutting->refresh();
$this->assertEquals(CuttingStatus::VERIFIED, $cutting->status);
$setup['variant']->refresh();
// Base stock was 10. Added good stock is 6.
$this->assertEquals(16, $setup['variant']->stock);
});
}); });