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)); + } }