diff --git a/app/Http/Controllers/BranchController.php b/app/Http/Controllers/BranchController.php
index ca07f0f..65860b7 100644
--- a/app/Http/Controllers/BranchController.php
+++ b/app/Http/Controllers/BranchController.php
@@ -14,7 +14,8 @@
{
protected $user;
- public function __construct(){
+ public function __construct()
+ {
$this->user = auth()->user();
}
@@ -58,8 +59,8 @@
if (is_null($this->user) || !$this->user->can('basic-data.create')) {
abort(403, 'Sorry! You are not allowed to create branches.');
}
-
- return view('basicdata::branch.create');
+ $branches = Branch::all();
+ return view('basicdata::branch.create', compact('branches'));
}
public function edit($id)
@@ -69,8 +70,9 @@
abort(403, 'Sorry! You are not allowed to update branches.');
}
- $branch = Branch::find($id);
- return view('basicdata::branch.create', compact('branch'));
+ $branch = Branch::findOrFail($id);
+ $branches = Branch::all();
+ return view('basicdata::branch.create', compact('branch', 'branches'));
}
public function update(BranchRequest $request, $id)
@@ -82,6 +84,14 @@
$validate = $request->validated();
+ // Tambahkan validasi manual untuk memeriksa parent_id
+ if (isset($validate['parent_id']) && $validate['parent_id'] == $id) {
+ return redirect()
+ ->back()
+ ->withInput()
+ ->withErrors(['parent_id' => 'Cabang tidak dapat menjadi induk dari dirinya sendiri.']);
+ }
+
if ($validate) {
try {
// Update in database
@@ -102,12 +112,25 @@
{
// Check if the authenticated user has the required permission to delete branches
if (is_null($this->user) || !$this->user->can('basic-data.delete')) {
- return response()->json(['success' => false, 'message' => 'Sorry! You are not allowed to delete branches.'], 403);
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Sorry! You are not allowed to delete branches.'
+ ], 403);
}
try {
- // Delete from database
+ // Find the branch
$branch = Branch::find($id);
+
+ // Check if the branch has children
+ if ($branch->children()->exists()) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Cabang dengan anak cabang tidak dapat dihapus.'
+ ], 422);
+ }
+
+ // Delete from database
$branch->delete();
return response()->json(['success' => true, 'message' => 'Branch deleted successfully']);
@@ -120,10 +143,26 @@
{
// Check if the authenticated user has the required permission to delete branches
if (is_null($this->user) || !$this->user->can('basic-data.delete')) {
- return response()->json(['success' => false, 'message' => 'Sorry! You are not allowed to delete branches.'], 403);
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Sorry! You are not allowed to delete branches.'
+ ], 403);
}
$ids = $request->input('ids');
+
+ // Check if any of the branches have children
+ $branchesWithChildren = Branch::whereIn('id', $ids)
+ ->whereHas('children')
+ ->get();
+
+ if ($branchesWithChildren->count() > 0) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Beberapa cabang memiliki anak cabang dan tidak dapat dihapus.'
+ ], 422);
+ }
+
Branch::whereIn('id', $ids)->delete();
return response()->json(['success' => true, 'message' => 'Branches deleted successfully']);
}
@@ -132,7 +171,10 @@
{
// Check if the authenticated user has the required permission to view branches
if (is_null($this->user) || !$this->user->can('basic-data.read')) {
- return response()->json(['success' => false, 'message' => 'Sorry! You are not allowed to view branches.'], 403);
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Sorry! You are not allowed to view branches.'
+ ], 403);
}
// Retrieve data from the database
@@ -172,12 +214,22 @@
// Get the data for the current page
$data = $query->get();
+ $data = $data->map(function ($item) {
+ return [
+ 'id' => $item->id,
+ 'code' => $item->code,
+ 'name' => $item->name,
+ 'parent_id' => $item->parent?->name ?? null,
+ ];
+ });
+
// Calculate the page count
$pageCount = ceil($totalRecords / $request->get('size'));
// Calculate the current page number
$currentPage = 0 + 1;
+
// Return the response data as a JSON object
return response()->json([
'draw' => $request->get('draw'),
diff --git a/app/Http/Requests/BranchRequest.php b/app/Http/Requests/BranchRequest.php
index b6a7a45..e5050fd 100644
--- a/app/Http/Requests/BranchRequest.php
+++ b/app/Http/Requests/BranchRequest.php
@@ -15,6 +15,15 @@
{
$rules = [
'name' => 'required|string|max:255',
+ 'parent_id' => [
+ 'nullable',
+ 'exists:branches,id',
+ function ($attribute, $value, $fail) {
+ if ($value == $this->route('branch')) {
+ $fail('Cabang tidak dapat menjadi induk dari dirinya sendiri.');
+ }
+ },
+ ],
'status' => 'nullable|boolean',
'authorized_at' => 'nullable|datetime',
'authorized_status' => 'nullable|string|max:1',
diff --git a/app/Models/Branch.php b/app/Models/Branch.php
index 6f0a771..367a2ce 100644
--- a/app/Models/Branch.php
+++ b/app/Models/Branch.php
@@ -6,5 +6,21 @@
class Branch extends Base
{
protected $table = 'branches';
- protected $fillable = ['code', 'name', 'status', 'authorized_at', 'authorized_status', 'authorized_by'];
+ protected $fillable = ['code', 'name', 'status', 'authorized_at', 'authorized_status', 'authorized_by', 'parent_id'];
+
+ /**
+ * Get the parent branch of this branch
+ */
+ public function parent()
+ {
+ return $this->belongsTo(Branch::class, 'parent_id');
+ }
+
+ /**
+ * Get the child branches of this branch
+ */
+ public function children()
+ {
+ return $this->hasMany(Branch::class, 'parent_id');
+ }
}
diff --git a/database/migrations/2025_05_18_074721_add_parent_id_to_branches_table.php b/database/migrations/2025_05_18_074721_add_parent_id_to_branches_table.php
new file mode 100644
index 0000000..5b67079
--- /dev/null
+++ b/database/migrations/2025_05_18_074721_add_parent_id_to_branches_table.php
@@ -0,0 +1,30 @@
+unsignedBigInteger('parent_id')->nullable()->after('name');
+ $table->foreign('parent_id')->references('id')->on('branches')->onDelete('set null');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('branches', function (Blueprint $table) {
+ $table->dropForeign(['parent_id']);
+ $table->dropColumn('parent_id');
+ });
+ }
+};
diff --git a/resources/views/branch/create.blade.php b/resources/views/branch/create.blade.php
index 93dcfa5..07a68f1 100644
--- a/resources/views/branch/create.blade.php
+++ b/resources/views/branch/create.blade.php
@@ -46,6 +46,26 @@
@enderror
+
+
+
+
+ @error('parent_id')
+ {{ $message }}
+ @enderror
+
+
@if(isset($branch->id))
@can('basic-data.update')
diff --git a/resources/views/branch/index.blade.php b/resources/views/branch/index.blade.php
index af36573..f8ceee8 100644
--- a/resources/views/branch/index.blade.php
+++ b/resources/views/branch/index.blade.php
@@ -47,6 +47,10 @@
Cabang
+
+ Cabang Induk
+
+ |
Action |
@@ -168,6 +172,9 @@
name: {
title: 'Cabang',
},
+ parent_id: {
+ title: 'Cabang Induk',
+ },
actions: {
title: 'Status',
render: (item, data) => {
diff --git a/tests/Feature/BranchControllerTest.php b/tests/Feature/BranchControllerTest.php
index d6c4eb7..f6cd373 100644
--- a/tests/Feature/BranchControllerTest.php
+++ b/tests/Feature/BranchControllerTest.php
@@ -18,6 +18,7 @@ class BranchControllerTest extends TestCase
protected $user;
protected $adminRole;
protected $branch;
+ protected $parentBranch;
protected function setUp(): void
{
@@ -64,6 +65,12 @@ class BranchControllerTest extends TestCase
$this->user = User::factory()->create();
$this->user->assignRole($this->adminRole);
+ // Create a parent branch for testing
+ $this->parentBranch = Branch::create([
+ 'code' => 'PARENT',
+ 'name' => 'Parent Branch'
+ ]);
+
// Create a branch for testing
$this->branch = Branch::create([
'code' => 'TEST',
@@ -137,6 +144,28 @@ class BranchControllerTest extends TestCase
$this->assertDatabaseHas('branches', $branchData);
}
+ #[Test]
+ public function user_with_permission_can_store_branch_with_parent()
+ {
+ $branchData = [
+ 'code' => 'CHILD',
+ 'name' => 'Child Branch',
+ 'parent_id' => $this->parentBranch->id
+ ];
+
+ $response = $this->actingAs($this->user)
+ ->post(route('basicdata.branch.store'), $branchData);
+
+ $response->assertRedirect(route('basicdata.branch.index'));
+ $this->assertDatabaseHas('branches', $branchData);
+
+ // Verify the relationship
+ $childBranch = Branch::where('code', 'CHILD')->first();
+ $this->assertEquals($this->parentBranch->id, $childBranch->parent_id);
+ $this->assertTrue($childBranch->parent()->exists());
+ $this->assertEquals($this->parentBranch->id, $childBranch->parent->id);
+ }
+
#[Test]
public function user_without_permission_cannot_store_branch()
{
@@ -201,6 +230,53 @@ class BranchControllerTest extends TestCase
$this->assertDatabaseHas('branches', $updatedData);
}
+ #[Test]
+ public function user_with_permission_can_update_branch_with_parent()
+ {
+ $updatedData = [
+ 'code' => 'UPD',
+ 'name' => 'Updated Branch',
+ 'parent_id' => $this->parentBranch->id
+ ];
+
+ $response = $this->actingAs($this->user)
+ ->put(route('basicdata.branch.update', $this->branch->id), $updatedData);
+
+ $response->assertRedirect(route('basicdata.branch.index'));
+ $this->assertDatabaseHas('branches', $updatedData);
+
+ // Verify the relationship
+ $this->branch->refresh();
+ $this->assertEquals($this->parentBranch->id, $this->branch->parent_id);
+ $this->assertTrue($this->branch->parent()->exists());
+ $this->assertEquals($this->parentBranch->id, $this->branch->parent->id);
+ }
+
+ #[Test]
+ public function user_with_permission_can_remove_parent_from_branch()
+ {
+ // First set a parent
+ $this->branch->update(['parent_id' => $this->parentBranch->id]);
+ $this->assertEquals($this->parentBranch->id, $this->branch->parent_id);
+
+ // Then remove it
+ $updatedData = [
+ 'code' => 'UPD',
+ 'name' => 'Updated Branch',
+ 'parent_id' => null
+ ];
+
+ $response = $this->actingAs($this->user)
+ ->put(route('basicdata.branch.update', $this->branch->id), $updatedData);
+
+ $response->assertRedirect(route('basicdata.branch.index'));
+
+ // Verify the relationship is removed
+ $this->branch->refresh();
+ $this->assertNull($this->branch->parent_id);
+ $this->assertFalse($this->branch->parent()->exists());
+ }
+
#[Test]
public function user_without_permission_cannot_update_branch()
{
@@ -277,4 +353,126 @@ class BranchControllerTest extends TestCase
$response->assertStatus(403);
}
+
+ #[Test]
+ public function branch_cannot_be_its_own_parent()
+ {
+ $updatedData = [
+ 'code' => 'SELF',
+ 'name' => 'Self-Referencing Branch',
+ 'parent_id' => $this->branch->id
+ ];
+
+ $response = $this->actingAs($this->user)
+ ->from(route('basicdata.branch.edit', $this->branch->id)) // Tambahkan ini untuk mengetahui URL sebelumnya
+ ->put(route('basicdata.branch.update', $this->branch->id), $updatedData);
+
+ // Pastikan redirect back dengan kesalahan
+ $response->assertRedirect();
+ $response->assertSessionHasErrors('parent_id');
+
+ // Periksa bahwa parent_id tidak berubah
+ $this->branch->refresh();
+ $this->assertNull($this->branch->parent_id);
+ }
+
+ #[Test]
+ public function cannot_delete_branch_with_children()
+ {
+ // Create a child branch
+ $childBranch = Branch::create([
+ 'code' => 'CHILD',
+ 'name' => 'Child Branch',
+ 'parent_id' => $this->parentBranch->id
+ ]);
+
+ // Verify the relationship is established
+ $this->assertEquals($this->parentBranch->id, $childBranch->parent_id);
+ $this->assertTrue($this->parentBranch->children()->exists());
+
+ // Try to delete the parent branch
+ $response = $this->actingAs($this->user)
+ ->delete(route('basicdata.branch.destroy', $this->parentBranch->id));
+
+ // Assert that the request fails with the expected message
+ $response->assertJson([
+ 'success' => false,
+ 'message' => 'Cabang dengan anak cabang tidak dapat dihapus.'
+ ]);
+ $response->assertStatus(422); // Unprocessable Entity
+
+ // Verify the parent branch was not deleted
+ $this->assertDatabaseHas('branches', [
+ 'id' => $this->parentBranch->id,
+ 'deleted_at' => null
+ ]);
+ }
+
+ #[Test]
+ public function cannot_delete_multiple_branches_if_any_has_children()
+ {
+ // Create a child branch
+ $childBranch = Branch::create([
+ 'code' => 'CHILD',
+ 'name' => 'Child Branch',
+ 'parent_id' => $this->parentBranch->id
+ ]);
+
+ // Create another branch without children
+ $anotherBranch = Branch::create([
+ 'code' => 'ANOTHER',
+ 'name' => 'Another Branch'
+ ]);
+
+ // Try to delete both the parent branch and another branch
+ $response = $this->actingAs($this->user)
+ ->post(route('basicdata.branch.deleteMultiple'), [
+ 'ids' => [$this->parentBranch->id, $anotherBranch->id]
+ ]);
+
+ // Assert that the request fails with the expected message
+ $response->assertJson([
+ 'success' => false,
+ 'message' => 'Beberapa cabang memiliki anak cabang dan tidak dapat dihapus.'
+ ]);
+ $response->assertStatus(422); // Unprocessable Entity
+
+ // Verify neither branch was deleted
+ $this->assertDatabaseHas('branches', [
+ 'id' => $this->parentBranch->id,
+ 'deleted_at' => null
+ ]);
+ $this->assertDatabaseHas('branches', [
+ 'id' => $anotherBranch->id,
+ 'deleted_at' => null
+ ]);
+ }
+
+ #[Test]
+ public function branch_has_correct_children_relationship()
+ {
+ // Create a child branch
+ $childBranch = Branch::create([
+ 'code' => 'CHILD1',
+ 'name' => 'Child Branch 1',
+ 'parent_id' => $this->parentBranch->id
+ ]);
+
+ // Create another child branch
+ $anotherChildBranch = Branch::create([
+ 'code' => 'CHILD2',
+ 'name' => 'Child Branch 2',
+ 'parent_id' => $this->parentBranch->id
+ ]);
+
+ // Refresh parent branch
+ $this->parentBranch->refresh();
+
+ // Assert that the parent has two children
+ $this->assertEquals(2, $this->parentBranch->children()->count());
+
+ // Assert that the children collection contains the two child branches
+ $this->assertTrue($this->parentBranch->children->contains($childBranch));
+ $this->assertTrue($this->parentBranch->children->contains($anotherChildBranch));
+ }
}