Compare commits

7 Commits

Author SHA1 Message Date
Sholahuddin Al Ayubi
0b28760f41 feat: Implement user-branch relationship and update user management views
- Added a many-to-many relationship between users and branches in User model.
- Updated user creation and editing views to support multiple branch selection.
- Modified user index view to display associated branches.
- Created UserBranch model to manage user-branch associations.
- Added migration for user_branches table with foreign key constraints.
- Implemented seeder to populate user_branches based on existing user branch data.

Run this command:
- php artisan migrate
- php artisan module:seed Usermanagement --class=UserBranchesSeeder
2025-12-11 17:20:34 +07:00
Sholahuddin Al Ayubi
f3872e0665 feat(usermanagement): fix permission seeder structure and ensure module column populated correctly
- Updated PermissionGroupSeeder to seed consistent permission group definitions
- Updated PermissionsSeeder to generate proper permission records with correct module mapping
- Ensured permission creation uses dynamically generated module value based on group name
- Fixed undefined array key "module" issue by restructuring data() output
- Applied permission assignment to all roles for each generated permission
- Improved overall seeder stability and idempotency

To apply the updates, run the following commands:

php artisan module:seed Usermanagement --class="PermissionGroupSeeder"
php artisan module:seed Usermanagement --class="PermissionsSeeder"
2025-12-03 18:28:51 +07:00
Sholahuddin Al Ayubi
4270f152d2 feat(usermanagement): update UsersSeeder to generate users for all branches including branchdirector and soadmindokumen roles
Perubahan utama:
- Menambahkan proses pembuatan user untuk role `branchdirector` di semua branch (branchLuar, branchDalam, dan KPNO).
- Menambahkan proses pembuatan user untuk role `soadmindokumen` di semua branch (branchLuar, branchDalam, dan KPNO).
- Menjamin seluruh role dari RolesSeeder dibuat sebelum user di-generate.
- Menyelaraskan pola pembuatan email dan nama user per branch.
- Mempertahankan user default branch_id = 1 tanpa suffix branch.
- Membersihkan struktur dan alur seeder agar lebih maintainable.

Instruksi setelah perubahan:
1. php artisan config:clear
2. php artisan module:migrate-reset Usermanagement
3. php artisan module:migrate Usermanagement
4. php artisan module:seed Adk --class="UserSeeder"
5. php artisan module:seed Usermanagement --class="UsersSeeder"

Catatan tambahan:
- Urutan approval role kini mengikuti hierarki:
  1. soadmindokumen
  2. admindokumen
  3. legal
  4. branchdirector
2025-12-03 15:14:29 +07:00
Sholahuddin Al Ayubi
9539c2572f feat(usermanagement): add appointment_cabangs relationship to User model 2025-11-18 11:09:21 +07:00
Sholahuddin Al Ayubi
ff94434032 refactor(usermanagement): simplify UsersSeeder by removing hardcoded branch arrays and enhancing user creation logic 2025-11-11 13:29:37 +07:00
Sholahuddin Al Ayubi
7313f64a70 refactor(usermanagement): enhance UsersSeeder by removing RolesSeeder dependency and improving user creation logic 2025-11-10 17:44:41 +07:00
Sholahuddin Al Ayubi
a4aab54735 refactor(usermanagement): streamline UsersSeeder by utilizing RolesSeeder and improving user creation logic 2025-10-31 16:53:50 +07:00
11 changed files with 916 additions and 610 deletions

View File

@@ -1,377 +1,389 @@
<?php
namespace Modules\Usermanagement\Http\Controllers;
namespace Modules\Usermanagement\Http\Controllers;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Basicdata\Models\Branch;
use Modules\Usermanagement\Exports\UsersExport;
use Modules\Usermanagement\Http\Requests\User as UserRequest;
use Modules\Usermanagement\Models\Role;
use Modules\Usermanagement\Models\User;
use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Basicdata\Models\Branch;
use Modules\Usermanagement\Exports\UsersExport;
use Modules\Usermanagement\Http\Requests\User as UserRequest;
use Modules\Usermanagement\Models\Role;
use Modules\Usermanagement\Models\User;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
/**
* Class UsersController
*
* This controller is responsible for managing user within the application.
*
* @package Modules\Usermanagement\Http\Controllers
*/
class UsersController extends Controller
{
/**
* @var \Illuminate\Contracts\Auth\Authenticatable|null
*/
protected $user;
/**
* Class UsersController
* UsersController constructor.
*
* This controller is responsible for managing user within the application.
*
* @package Modules\Usermanagement\Http\Controllers
* Initializes the user property with the authenticated user.
*/
class UsersController extends Controller
public function __construct()
{
/**
* @var \Illuminate\Contracts\Auth\Authenticatable|null
*/
protected $user;
// Mengatur middleware auth
$this->middleware('auth');
/**
* UsersController constructor.
*
* Initializes the user property with the authenticated user.
*/
public function __construct()
{
// Mengatur middleware auth
$this->middleware('auth');
// Mengatur user setelah middleware auth dijalankan
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
return $next($request);
});
}
// Mengatur user setelah middleware auth dijalankan
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
return $next($request);
/**
* Display a listing of the resource.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
if (is_null($this->user) || !$this->user->can('usermanagement.read')) {
abort(403, 'Sorry! You are not allowed to view users.');
}
return view('usermanagement::users.index');
}
/**
* Process support datatables ajax request.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function dataForDatatables(Request $request)
{
if (is_null($this->user) || !$this->user->can('usermanagement.read')) {
return response()->json([
'message' => 'Sorry! You are not allowed to view users.',
'success' => false
]);
}
$query = User::query()->with(['branches', 'roles']);
if (!$this->user->hasRole('administrator')) {
$query->whereHas('roles', function ($q) {
$q->where('name', '!=', 'administrator');
});
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
if (is_null($this->user) || !$this->user->can('usermanagement.read')) {
abort(403, 'Sorry! You are not allowed to view users.');
}
return view('usermanagement::users.index');
}
/**
* Process support datatables ajax request.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function dataForDatatables(Request $request)
{
if (is_null($this->user) || !$this->user->can('usermanagement.read')) {
return response()->json(['message' => 'Sorry! You are not allowed to view users.','success' => false]);
}
// Retrieve data from the database
$query = User::query();
if(!$this->user->hasRole('administrator')){
$query->whereHas('roles', function($q){
$q->where('name', '!=', 'administrator');
});
}
// Apply search filter if provided
if ($request->has('search') && !empty($request->get('search'))) {
$search = $request->get('search');
$query->whereAny(['name', 'email'], 'like', '%'.$search.'%');
}
// Apply sorting if provided
if ($request->has('sortOrder') && !empty($request->get('sortOrder'))) {
$order = $request->get('sortOrder');
$column = $request->get('sortField');
$query->orderBy($column, $order);
}
// Get the total count of records
$totalRecords = $query->count();
// Apply pagination if provided
if ($request->has('page') && $request->has('size')) {
$page = $request->get('page');
$size = $request->get('size');
$offset = ($page - 1) * $size; // Calculate the offset
$query->skip($offset)->take($size);
}
// Get the filtered count of records
$filteredRecords = $query->count();
// Get the data for the current page
$data = $query->with(['branch', 'roles'])->get();
// 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'),
'recordsTotal' => $totalRecords,
'recordsFiltered' => $filteredRecords,
'pageCount' => $pageCount,
'page' => $currentPage,
'totalCount' => $totalRecords,
'data' => $data,
]);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($id)
{
if (is_null($this->user) || !$this->user->can('usermanagement.update')) {
abort(403, 'Sorry! You are not allowed to edit users.');
}
$user = User::find($id);
$roles = Role::all();
if(!$this->user->hasRole('administrator')){
$roles = $roles->where('name', '!=', 'administrator');
}
$branches = Branch::all();
return view('usermanagement::users.create', compact('user', 'roles', 'branches'));
}
/**
* Remove the specified resource from storage.
*
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($id)
{
if (is_null($this->user) || !$this->user->can('usermanagement.delete')) {
return response()->json(['message' => 'Sorry! You are not allowed to delete users.','success' => false]);
}
$user = User::find($id);
$user->delete();
return response()->json(['message' => 'User deleted successfully.', 'success' => true]);
}
/**
* Restore the specified resource from storage.
*
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function restore($id)
{
if (is_null($this->user) || !$this->user->can('usermanagement.restore')) {
abort(403, 'Sorry! You are not allowed to restore users.');
}
$user = User::withTrashed()->find($id);
$user->restore();
return redirect()->route('users.index')->with('success', 'User restored successfully.');
}
/**
* Store a newly created resource in storage.
*
* This function handles the creation of a new user in the application. It validates the incoming request data,
* creates a new user record in the database, and redirects the user to the users index page with a success message.
*
* @param \Modules\Usermanagement\Http\Requests\User $request The incoming request containing the user data.
*
* @return \Illuminate\Http\RedirectResponse Redirects to the users index page with a success message upon successful creation.
* @return \Illuminate\Http\RedirectResponse Redirects to the users create page upon validation failure.
*/
public function store(UserRequest $request)
{
if (is_null($this->user) || !$this->user->can('usermanagement.create')) {
abort(403, 'Sorry! You are not allowed to create a user.');
}
$validated = $request->validated();
if ($validated) {
$user = User::create($validated);
if ($user) {
if ($request->roles) {
$user->assignRole($request->roles);
}
return redirect()->route('users.index')->with('success', 'User created successfully.');
}
}
return redirect()->route('users.create');
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
if (is_null($this->user) || !$this->user->can('usermanagement.create')) {
abort(403, 'Sorry! You are not allowed to create a user.');
}
$roles = Role::all();
if(!$this->user->hasRole('administrator')){
$roles = $roles->where('name', '!=', 'administrator');
}
$branches = Branch::all();
return view('usermanagement::users.create', compact('roles', 'branches'));
}
public function export(Request $request)
{
if (is_null($this->user) || !$this->user->can('usermanagement.export')) {
abort(403, 'Sorry! You are not allowed to export users.');
}
// Get search parameter from request
if ($request->has('search') && !empty($request->get('search'))) {
$search = $request->get('search');
return Excel::download(new UsersExport($search), 'users.xlsx');
$query->where(function ($q) use ($search) {
$q->where('name', 'like', '%' . $search . '%')
->orWhere('email', 'like', '%' . $search . '%')
->orWhereHas('branches', function ($qb) use ($search) {
$qb->where('name', 'like', '%' . $search . '%');
});
});
}
public function profile()
{
$user = Auth::user();
return view('usermanagement::users.profile', compact('user'));
if ($request->has('sortOrder') && !empty($request->get('sortOrder'))) {
$order = $request->get('sortOrder');
$column = $request->get('sortField');
$query->orderBy($column, $order);
}
public function updateProfile(Request $request)
{
$user = Auth::user();
$totalRecords = $query->count();
$validatedData = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users,email,' . $user->id,
'sign' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
]);
$page = $request->get('page', 1);
$size = $request->get('size', 10);
$offset = ($page - 1) * $size;
$user->name = $validatedData['name'];
$user->email = $validatedData['email'];
$user->nik = $validatedData['nik'];
$query->skip($offset)->take($size);
if ($request->hasFile('sign')) {
// Delete old e-sign if exists
if ($user->sign) {
Storage::disk('public')->delete('signatures/' . $user->id . '/' . $user->sign);
}
$filteredRecords = $query->count();
$sign = $request->file('sign');
$signName = time() . '.' . $sign->getClientOriginalExtension();
$users = $query->get()->map(function ($user) {
$user->branch_names = $user->branches->pluck('name')->join(', ');
return $user;
});
// Make sure the directory exists
Storage::disk('public')->makeDirectory('signatures/' . $user->id);
// Store the file
$sign->storeAs('signatures/' . $user->id, $signName, 'public');
$user->sign = $signName;
}
$user->save();
return redirect()->route('users.profile')->with('success', 'Profile updated successfully.');
}
public function changePassword(Request $request)
{
$validator = Validator::make($request->all(), [
'current_password' => 'required',
'password' => 'required|string|min:8|confirmed',
], [
'password_confirmation' => 'The new password confirmation does not match.',
]);
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}
$user = Auth::user();
if (!Hash::check($request->current_password, $user->password)) {
return back()->withErrors(['current_password' => 'The current password is incorrect.']);
}
$user->password = Hash::make($request->password);
$user->save();
return redirect()->route('users.profile')->with('success', 'Password changed successfully.');
}
/**
* Update the specified resource in storage.
*
* @param \Modules\Usermanagement\Http\Requests\User $request
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(UserRequest $request, $id)
{
if (is_null($this->user) || !$this->user->can('usermanagement.update')) {
abort(403, 'Sorry! You are not allowed to update users.');
}
$validated = $request->validated();
if ($validated) {
try {
$user = User::find($id);
if ($request->hasFile('sign')) {
$sign = $request->file('sign');
$signName = time() . '.' . $sign->getClientOriginalExtension();
$sign->storeAs(
'public/signatures/' . $user->id . '/',
$signName,
);
$validated['sign'] = $signName;
}
$user->update($validated);
if ($request->roles) {
$user->roles()->detach();
$user->assignRole($request->roles);
}
} catch (Exception $e) {
return redirect()->back()->withErrors(['error' => 'Failed to update user. Please try again.']);
}
}
return redirect()->route('users.index')->with('success', 'User updated successfully.');
}
$pageCount = ceil($totalRecords / $size);
return response()->json([
'draw' => $request->get('draw'),
'recordsTotal' => $totalRecords,
'recordsFiltered' => $filteredRecords,
'pageCount' => $pageCount,
'page' => $page,
'totalCount' => $totalRecords,
'data' => $users,
]);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($id)
{
if (is_null($this->user) || !$this->user->can('usermanagement.update')) {
abort(403, 'Sorry! You are not allowed to edit users.');
}
$user = User::find($id);
$roles = Role::all();
if (!$this->user->hasRole('administrator')) {
$roles = $roles->where('name', '!=', 'administrator');
}
$branches = Branch::all();
return view('usermanagement::users.create', compact('user', 'roles', 'branches'));
}
/**
* Remove the specified resource from storage.
*
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($id)
{
if (is_null($this->user) || !$this->user->can('usermanagement.delete')) {
return response()->json(['message' => 'Sorry! You are not allowed to delete users.', 'success' => false]);
}
$user = User::find($id);
$user->delete();
return response()->json(['message' => 'User deleted successfully.', 'success' => true]);
}
/**
* Restore the specified resource from storage.
*
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function restore($id)
{
if (is_null($this->user) || !$this->user->can('usermanagement.restore')) {
abort(403, 'Sorry! You are not allowed to restore users.');
}
$user = User::withTrashed()->find($id);
$user->restore();
return redirect()->route('users.index')->with('success', 'User restored successfully.');
}
/**
* Store a newly created resource in storage.
*
* This function handles the creation of a new user in the application. It validates the incoming request data,
* creates a new user record in the database, and redirects the user to the users index page with a success message.
*
* @param \Modules\Usermanagement\Http\Requests\User $request The incoming request containing the user data.
*
* @return \Illuminate\Http\RedirectResponse Redirects to the users index page with a success message upon successful creation.
* @return \Illuminate\Http\RedirectResponse Redirects to the users create page upon validation failure.
*/
public function store(UserRequest $request)
{
if (is_null($this->user) || !$this->user->can('usermanagement.create')) {
abort(403, 'Sorry! You are not allowed to create a user.');
}
$validated = $request->validated();
if ($validated) {
$user = User::create($validated);
if ($user) {
if ($request->roles) {
$user->assignRole($request->roles);
}
return redirect()->route('users.index')->with('success', 'User created successfully.');
}
}
return redirect()->route('users.create');
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
if (is_null($this->user) || !$this->user->can('usermanagement.create')) {
abort(403, 'Sorry! You are not allowed to create a user.');
}
$roles = Role::all();
if (!$this->user->hasRole('administrator')) {
$roles = $roles->where('name', '!=', 'administrator');
}
$branches = Branch::all();
return view('usermanagement::users.create', compact('roles', 'branches'));
}
public function export(Request $request)
{
if (is_null($this->user) || !$this->user->can('usermanagement.export')) {
abort(403, 'Sorry! You are not allowed to export users.');
}
// Get search parameter from request
$search = $request->get('search');
return Excel::download(new UsersExport($search), 'users.xlsx');
}
public function profile()
{
$user = Auth::user();
return view('usermanagement::users.profile', compact('user'));
}
public function updateProfile(Request $request)
{
$user = Auth::user();
$validatedData = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users,email,' . $user->id,
'sign' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
]);
$user->name = $validatedData['name'];
$user->email = $validatedData['email'];
$user->nik = $validatedData['nik'];
if ($request->hasFile('sign')) {
// Delete old e-sign if exists
if ($user->sign) {
Storage::disk('public')->delete('signatures/' . $user->id . '/' . $user->sign);
}
$sign = $request->file('sign');
$signName = time() . '.' . $sign->getClientOriginalExtension();
// Make sure the directory exists
Storage::disk('public')->makeDirectory('signatures/' . $user->id);
// Store the file
$sign->storeAs('signatures/' . $user->id, $signName, 'public');
$user->sign = $signName;
}
$user->save();
return redirect()->route('users.profile')->with('success', 'Profile updated successfully.');
}
public function changePassword(Request $request)
{
$validator = Validator::make($request->all(), [
'current_password' => 'required',
'password' => 'required|string|min:8|confirmed',
], [
'password_confirmation' => 'The new password confirmation does not match.',
]);
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}
$user = Auth::user();
if (!Hash::check($request->current_password, $user->password)) {
return back()->withErrors(['current_password' => 'The current password is incorrect.']);
}
$user->password = Hash::make($request->password);
$user->save();
return redirect()->route('users.profile')->with('success', 'Password changed successfully.');
}
/**
* Update the specified resource in storage.
*
* @param \Modules\Usermanagement\Http\Requests\User $request
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(UserRequest $request, $id)
{
if (is_null($this->user) || !$this->user->can('usermanagement.update')) {
abort(403, 'Sorry! You are not allowed to update users.');
}
$validated = $request->validated();
if ($validated) {
try {
$user = User::findOrFail($id);
// Handle file upload e-sign
if ($request->hasFile('sign')) {
$sign = $request->file('sign');
$signName = time() . '.' . $sign->getClientOriginalExtension();
// Simpan file ke storage
$sign->storeAs(
'public/signatures/' . $user->id . '/',
$signName
);
$validated['sign'] = $signName;
}
// Update data user
$user->update($validated);
// Update roles
if ($request->roles) {
$user->roles()->detach();
$user->assignRole($request->roles);
}
$user->branches()->sync($request->input('branches', []));
$branchIds = $user->branches()->pluck('branches.id')->toArray();
} catch (Exception $e) {
Log::error('Failed to update user: ' . $e->getMessage());
return redirect()->back()->withErrors(['error' => 'Failed to update user. Please try again.']);
}
}
return redirect()->route('users.index')->with('success', 'User updated successfully.');
}
}

View File

@@ -73,8 +73,8 @@ class User extends Authenticatable
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'id' => 'string',
'password' => 'hashed',
'id' => 'string',
];
}
@@ -102,4 +102,14 @@ class User extends Authenticatable
{
return $this->hasMany(Appointment::class, 'admin_id');
}
public function appointment_cabangs()
{
return $this->hasMany(Appointment::class, 'admin_id');
}
public function branches()
{
return $this->belongsToMany(Branch::class, 'user_branches', 'user_id', 'branch_id');
}
}

34
app/Models/UserBranch.php Normal file
View File

@@ -0,0 +1,34 @@
<?php
namespace Modules\Usermanagement\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Modules\Usermanagement\Models\User;
use Modules\Basicdata\Models\Branch;
// use Modules\Usermanagement\Database\Factories\UserBranchFactory;
class UserBranch extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*/
protected $table = 'user_branches';
protected $fillable = [
'user_id',
'branch_id',
];
public function user()
{
return $this->belongsTo(User::class);
}
public function branch()
{
return $this->belongsTo(Branch::class);
}
}

View File

@@ -4,8 +4,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
return new class extends Migration {
/**
* Run the migrations.
*/
@@ -46,8 +45,12 @@ return new class extends Migration
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
Schema::disableForeignKeyConstraints();
Schema::dropIfExists('sessions');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('users');
Schema::enableForeignKeyConstraints();
}
};

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('user_branches', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('branch_id');
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('branch_id')->references('id')->on('branches')->onDelete('cascade');
$table->unique(['user_id', 'branch_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('user_branches');
}
};

View File

@@ -28,6 +28,10 @@
public function data()
{
return [
['name' => 'adk'],
['name' => 'basicdata'],
['name' => 'location'],
['name' => 'logs'],
['name' => 'usermanagement']
];
}

View File

@@ -1,65 +1,63 @@
<?php
namespace Modules\Usermanagement\Database\Seeders;
namespace Modules\Usermanagement\Database\Seeders;
use Illuminate\Database\Seeder;
use Modules\Usermanagement\Models\PermissionGroup;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Illuminate\Database\Seeder;
use Modules\Usermanagement\Models\PermissionGroup;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
class PermissionsSeeder extends Seeder
class PermissionsSeeder extends Seeder
{
public function run()
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$data = $this->data();
$data = $this->data();
foreach ($data as $value) {
$permission = Permission::updateOrCreate([
'name' => $value['name'],
'guard_name' => 'web' // or 'api
], [
'permission_group_id' => $value['group']
]);
foreach ($data as $value) {
$roles = Role::all();
foreach ($roles as $role) {
$role->givePermissionTo($permission);
}
$permission = Permission::updateOrCreate([
'name' => $value['name'],
'guard_name' => 'web',
], [
'permission_group_id' => $value['group_id'],
'module' => $value['module'],
]);
foreach (Role::all() as $role) {
$role->givePermissionTo($permission);
}
}
public function data()
{
$data = [];
// list of model permission
$groups = PermissionGroup::all();
foreach ($groups as $group) {
foreach ($this->crudActions($group->name) as $action) {
$data[] = ['name' => $action, 'group' => $group->id];
}
}
return $data;
}
public function crudActions($name)
{
$actions = [];
// list of permission actions
$crud = ['create', 'read', 'update', 'delete','export', 'authorize', 'report','restore'];
foreach ($crud as $value) {
$actions[] = $name . '.' . $value;
}
return $actions;
}
}
public function data()
{
$data = [];
$groups = PermissionGroup::all();
foreach ($groups as $group) {
foreach ($this->crudActions($group->name) as $action) {
$data[] = [
'name' => $action,
'group_id' => $group->id,
'module' => $group->name,
];
}
}
return $data;
}
public function crudActions($name)
{
$actions = ['create', 'read', 'update', 'delete', 'export', 'authorize', 'report', 'restore'];
$result = [];
foreach ($actions as $value) {
$result[] = $name . '.' . $value;
}
return $result;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Modules\Usermanagement\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Modules\Usermanagement\Models\User;
class UserBranchesSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$users = User::all();
foreach ($users as $user) {
if ($user->branch_id) {
$exists = DB::table('user_branches')
->where('user_id', $user->id)
->where('branch_id', $user->branch_id)
->exists();
if (!$exists) {
DB::table('user_branches')->insert([
'user_id' => $user->id,
'branch_id' => $user->branch_id,
'created_at' => now(),
'updated_at' => now(),
]);
}
}
}
$this->command->info('User branches seeded successfully.');
}
}

View File

@@ -1,35 +1,196 @@
<?php
namespace Modules\Usermanagement\Database\Seeders;
namespace Modules\Usermanagement\Database\Seeders;
use Faker\Generator;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use Modules\Usermanagement\Models\User;
use Spatie\Permission\Models\Role;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use Modules\Usermanagement\Models\User;
use Modules\Usermanagement\Database\Seeders\RolesSeeder;
use Spatie\Permission\Models\Role;
class UsersSeeder extends Seeder
class UsersSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$roleSeeder = new RolesSeeder();
$rolesData = $roleSeeder->data();
/**
* Run the database seeds.
*
* @return void
* ==================================================
* STEP 0: Pastikan semua roles dari RolesSeeder sudah dibuat di tabel roles
* ==================================================
*/
public function run(Generator $faker)
{
$roles = Role::all();
foreach ($rolesData as $roleData) {
Role::firstOrCreate(
['name' => $roleData['name']],
['guard_name' => 'web']
);
}
foreach ($roles as $role) {
$user = User::create([
'name' => $role->name,
'email' => $role->name . '@ag.co.id',
'password' => Hash::make('bagbag'),
'branch_id' => 1,
'nik' => '000000',
/**
* ==================================================
* Helper function untuk membuat user
* ==================================================
*/
$createUser = function ($roleName, $branchId = null, $includeBranchInEmail = true, $includeBranchInName = true) {
$email = $roleName . ($includeBranchInEmail && $branchId ? $branchId : '') . '@ag.co.id';
$name = ucfirst($roleName);
if ($includeBranchInName && $branchId) {
$name .= ' ' . $branchId;
}
$user = User::firstOrCreate(
['email' => $email],
[
'name' => $name,
'password' => Hash::make('bagbag'),
'branch_id' => $branchId,
'nik' => rand(100000, 999999),
'email_verified_at' => now(),
]);
]
);
$role = Role::where('name', $roleName)->first();
if ($role) {
$user->assignRole($role);
}
};
/**
* ==================================================
* STEP 1: Buat user per role (branch_id = 1)
* ==================================================
* - Tanpa angka "1" di email
* - Tanpa angka "1" di nama
*/
foreach ($rolesData as $roleData) {
$roleName = $roleData['name'];
$createUser($roleName, 1, false, false); // tanpa 1 di email & nama
}
/**
* ==================================================
* STEP 2: Jalankan logic lama (user per cabang)
* ==================================================
*/
$branchLuar = [
24,
25,
29,
35,
37,
41,
42,
45,
46,
50,
71,
74,
77,
82,
84,
85,
88,
90,
91,
93,
97,
107,
108,
111,
112,
113,
114,
115
];
$branchDalam = [
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
14,
15,
17,
18,
22,
23,
53,
55,
58,
60,
61,
66,
70,
95,
96,
98,
100,
105
];
$kpno = 6;
// LEGAL
foreach ($branchLuar as $branchId) {
$createUser('legal', $branchId);
}
$createUser('legal', $kpno);
// SPV LEGAL
$createUser('spvlegal', $kpno);
// USER CABANG
foreach (array_merge($branchLuar, $branchDalam) as $branchId) {
$createUser('cabang', $branchId);
}
// ADMIN DOKUMEN
foreach ($branchLuar as $branchId) {
$createUser('admindokumen', $branchId);
}
$createUser('admindokumen', $kpno);
// ADMIN KREDIT
$createUser('adminkredit', $kpno);
// AUDITOR
foreach ($branchLuar as $branchId) {
$createUser('auditor', $branchId);
}
$createUser('auditor', $kpno);
/**
* ==================================================
* NEW STEP: BRANCHDIRECTOR UNTUK SEMUA BRANCH
* ==================================================
*/
foreach (array_merge($branchLuar, $branchDalam) as $branchId) {
$createUser('branchdirector', $branchId);
}
$createUser('branchdirector', $kpno);
/**
* ==================================================
* NEW STEP: SO ADMIN DOKUMEN UNTUK SEMUA BRANCH
* ==================================================
*/
foreach (array_merge($branchLuar, $branchDalam) as $branchId) {
$createUser('soadmindokumen', $branchId);
}
// tetap buat juga untuk KPNO
$createUser('soadmindokumen', $kpno);
}
}

View File

@@ -6,188 +6,197 @@
@section('content')
<div class="w-full grid gap-5 lg:gap-7.5 mx-auto">
@if(isset($user->id))
@if (isset($user->id))
<form action="{{ route('users.update', $user->id) }}" method="POST" enctype="multipart/form-data">
<input type="hidden" name="id" value="{{ $user->id }}">
@method('PUT')
@else
<form method="POST" action="{{ route('users.store') }}">
@endif
@csrf
<div class="card pb-2.5">
<div class="card-header" id="basic_settings">
<h3 class="card-title">
{{ isset($user->id) ? 'Edit' : 'Add' }} User
</h3>
<div class="flex items-center gap-2">
<label class="switch switch-sm">
<span class="switch-label">
Public Profile
</span>
<input checked="" name="check" type="checkbox" value="1">
</label>
</div>
</div>
<div class="card-body grid gap-5">
@else
<form method="POST" action="{{ route('users.store') }}">
@endif
@csrf
<div class="card pb-2.5">
<div class="card-header" id="basic_settings">
<h3 class="card-title">
{{ isset($user->id) ? 'Edit' : 'Add' }} User
</h3>
<div class="flex items-center gap-2">
<label class="switch switch-sm">
<span class="switch-label">
Public Profile
</span>
<input checked="" name="check" type="checkbox" value="1">
</label>
</div>
</div>
<div class="card-body grid gap-5">
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Name
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="input @error('name') border-danger @enderror" type="text" name="name" value="{{ $user->name ?? '' }}">
@error('name')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Email
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="w-full input @error('email') border-danger @enderror" type="email" name="email" value="{{ $user->email ?? '' }}">
@error('email')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
NIK
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="w-full input @error('nik') border-danger @enderror" type="number" name="nik" value="{{ $user->nik ?? '' }}">
@error('nik')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Branch
</label>
<div class="flex flex-wrap items-baseline w-full">
<select class="input tomselect w-full @error('branch_id') border-danger @enderror" name="branch_id" id="branch_id">
<option value="">Pilih Branch</option>
@if(isset($branches))
@foreach($branches as $row)
@if(isset($user))
<option value="{{ $row->id }}" {{ isset($user->branch_id) && $user->branch_id == $row->id?'selected' : '' }}>
{{ $row->name }}
</option>
@else
<option value="{{ $row->id }}">
{{ $row->name }}
</option>
@endif
@endforeach
@endif
</select>
@error('branch_id')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
@if(isset($user->id))
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
E-Sign
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="file-input" type="file" name="sign" value="">
</div>
</div>
@endif
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Password
</label>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Name
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="input @error('name') border-danger @enderror" type="text" name="name"
value="{{ $user->name ?? '' }}">
@error('name')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Email
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="w-full input @error('email') border-danger @enderror" type="email" name="email"
value="{{ $user->email ?? '' }}">
@error('email')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
NIK
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="w-full input @error('nik') border-danger @enderror" type="number" name="nik"
value="{{ $user->nik ?? '' }}">
@error('nik')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Branch
</label>
<div class="flex flex-wrap items-baseline w-full">
<select class="input tomselect w-full @error('branches') border-danger @enderror" name="branches[]"
id="branches" multiple>
<option value="">-- Select Branch --</option>
@foreach ($branches as $branch)
<option value="{{ $branch->id }}"
{{ isset($user) && $user->branches->pluck('id')->contains($branch->id) ? 'selected' : '' }}>
{{ $branch->name }}
</option>
@endforeach
</select>
@error('branches')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
@if (isset($user->id))
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
E-Sign
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="file-input" type="file" name="sign" value="">
</div>
</div>
@endif
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Password
</label>
<div class="flex flex-wrap items-baseline w-full">
<div class="input @error('password') border-danger @enderror" data-toggle-password="true" data-toggle-password-permanent="true">
<input placeholder="Password" type="password" name="password"/>
<div class="btn btn-icon" data-toggle-password-trigger="true">
<i class="ki-outline ki-eye toggle-password-active:hidden"></i>
<i class="ki-outline ki-eye-slash hidden toggle-password-active:block"></i>
</div>
</div>
@error('password')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Password Confirmation
</label>
<div class="flex flex-wrap items-baseline w-full">
<div class="input @error('password_confirmation') border-danger @enderror" data-toggle-password="true" data-toggle-password-permanent="true">
<input placeholder="Password Confirmation" type="password" name="password_confirmation"/>
<div class="btn btn-icon" data-toggle-password-trigger="true">
<i class="ki-outline ki-eye toggle-password-active:hidden"></i>
<i class="ki-outline ki-eye-slash hidden toggle-password-active:block"></i>
</div>
</div>
@error('password_confirmation')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Role
</label>
<div class="flex flex-wrap items-baseline w-full">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5 py-5 lg:py-7.5 w-full">
@foreach($roles as $role)
<div class="rounded-xl border p-4 flex items-center justify-between gap-2.5">
<div class="flex items-center gap-3.5">
<div class="relative size-[45px] shrink-0">
<svg class="w-full h-full stroke-gray-300 fill-gray-100" fill="none" height="48" viewBox="0 0 44 48" width="44" xmlns="http://www.w3.org/2000/svg">
<path d="M16 2.4641C19.7128 0.320509 24.2872 0.320508 28 2.4641L37.6506 8.0359C41.3634 10.1795 43.6506 14.141 43.6506
18.4282V29.5718C43.6506 33.859 41.3634 37.8205 37.6506 39.9641L28 45.5359C24.2872 47.6795 19.7128 47.6795 16 45.5359L6.34937
39.9641C2.63655 37.8205 0.349365 33.859 0.349365 29.5718V18.4282C0.349365 14.141 2.63655 10.1795 6.34937 8.0359L16 2.4641Z" fill="">
</path>
<path d="M16.25 2.89711C19.8081 0.842838 24.1919 0.842837 27.75 2.89711L37.4006 8.46891C40.9587 10.5232 43.1506 14.3196 43.1506
18.4282V29.5718C43.1506 33.6804 40.9587 37.4768 37.4006 39.5311L27.75 45.1029C24.1919 47.1572 19.8081 47.1572 16.25 45.1029L6.59937
39.5311C3.04125 37.4768 0.849365 33.6803 0.849365 29.5718V18.4282C0.849365 14.3196 3.04125 10.5232 6.59937 8.46891L16.25 2.89711Z" stroke="">
</path>
</svg>
<div class="absolute leading-none left-2/4 top-2/4 -translate-y-2/4 -translate-x-2/4">
<i class="ki-filled ki-category text-lg text-gray-500">
</i>
</div>
</div>
<div class="flex flex-col gap-1">
<span class="flex items-center gap-1.5 leading-none font-medium text-sm text-gray-900">
{{ $role->name }}
</span>
<span class="text-2sm text-gray-700">
</span>
</div>
</div>
<div class="switch switch-sm">
@if(isset($user))
<input {{ in_array($role->name,$user->roles->pluck("name")->toArray()) ? 'checked' : '' }} name="roles" type="radio" value="{{ $role->name }}">
@else
<input name="roles" type="radio" value="{{ $role->name }}">
@endif
</div>
</div>
@endforeach
</div>
</div>
</div>
<div class="flex justify-end">
<button type="submit" class="btn btn-primary">
Save
</button>
</div>
<div class="flex flex-wrap items-baseline w-full">
<div class="input @error('password') border-danger @enderror" data-toggle-password="true"
data-toggle-password-permanent="true">
<input placeholder="Password" type="password" name="password" />
<div class="btn btn-icon" data-toggle-password-trigger="true">
<i class="ki-outline ki-eye toggle-password-active:hidden"></i>
<i class="ki-outline ki-eye-slash hidden toggle-password-active:block"></i>
</div>
</div>
</form>
@error('password')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Password Confirmation
</label>
<div class="flex flex-wrap items-baseline w-full">
<div class="input @error('password_confirmation') border-danger @enderror"
data-toggle-password="true" data-toggle-password-permanent="true">
<input placeholder="Password Confirmation" type="password" name="password_confirmation" />
<div class="btn btn-icon" data-toggle-password-trigger="true">
<i class="ki-outline ki-eye toggle-password-active:hidden"></i>
<i class="ki-outline ki-eye-slash hidden toggle-password-active:block"></i>
</div>
</div>
@error('password_confirmation')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Role
</label>
<div class="flex flex-wrap items-baseline w-full">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5 py-5 lg:py-7.5 w-full">
@foreach ($roles as $role)
<div class="rounded-xl border p-4 flex items-center justify-between gap-2.5">
<div class="flex items-center gap-3.5">
<div class="relative size-[45px] shrink-0">
<svg class="w-full h-full stroke-gray-300 fill-gray-100" fill="none"
height="48" viewBox="0 0 44 48" width="44"
xmlns="http://www.w3.org/2000/svg">
<path
d="M16 2.4641C19.7128 0.320509 24.2872 0.320508 28 2.4641L37.6506 8.0359C41.3634 10.1795 43.6506 14.141 43.6506
18.4282V29.5718C43.6506 33.859 41.3634 37.8205 37.6506 39.9641L28 45.5359C24.2872 47.6795 19.7128 47.6795 16 45.5359L6.34937
39.9641C2.63655 37.8205 0.349365 33.859 0.349365 29.5718V18.4282C0.349365 14.141 2.63655 10.1795 6.34937 8.0359L16 2.4641Z"
fill="">
</path>
<path
d="M16.25 2.89711C19.8081 0.842838 24.1919 0.842837 27.75 2.89711L37.4006 8.46891C40.9587 10.5232 43.1506 14.3196 43.1506
18.4282V29.5718C43.1506 33.6804 40.9587 37.4768 37.4006 39.5311L27.75 45.1029C24.1919 47.1572 19.8081 47.1572 16.25 45.1029L6.59937
39.5311C3.04125 37.4768 0.849365 33.6803 0.849365 29.5718V18.4282C0.849365 14.3196 3.04125 10.5232 6.59937 8.46891L16.25 2.89711Z"
stroke="">
</path>
</svg>
<div
class="absolute leading-none left-2/4 top-2/4 -translate-y-2/4 -translate-x-2/4">
<i class="ki-filled ki-category text-lg text-gray-500">
</i>
</div>
</div>
<div class="flex flex-col gap-1">
<span
class="flex items-center gap-1.5 leading-none font-medium text-sm text-gray-900">
{{ $role->name }}
</span>
<span class="text-2sm text-gray-700">
</span>
</div>
</div>
<div class="switch switch-sm">
@if (isset($user))
<input
{{ in_array($role->name, $user->roles->pluck('name')->toArray()) ? 'checked' : '' }}
name="roles" type="radio" value="{{ $role->name }}">
@else
<input name="roles" type="radio" value="{{ $role->name }}">
@endif
</div>
</div>
@endforeach
</div>
</div>
</div>
<div class="flex justify-end">
<button type="submit" class="btn btn-primary">
Save
</button>
</div>
</div>
</div>
</form>
</div>
@endsection

View File

@@ -160,7 +160,10 @@
branch: {
title: 'Branch',
render: (item, data) => {
return data.branch?.name || '-';
if (data.branches && data.branches.length > 0) {
return data.branches.map(b => b.name).join(', ');
}
return '-';
},
},
role: {