From af1c98b38ba004083257706160cb8ee23b9e2d0b Mon Sep 17 00:00:00 2001 From: Wian Drs Date: Thu, 25 Jun 2026 17:11:48 +0700 Subject: [PATCH] update api ticket type --- .../Controllers/Audit/AuditLogController.php | 40 +++++++++ .../Ticket/TicketTypeController.php | 22 ++--- .../Requests/Audit/AuditLogFilterRequest.php | 55 +++++++++++++ .../TicketType/StoreTicketTypeRequest.php | 79 +++++++++++++++++- .../TicketType/UpdateTicketTypeRequest.php | 81 ++++++++++++++++++- app/Http/Resources/Audit/AuditLogResource.php | 39 +++++++++ .../TicketType/TicketTypeResource.php | 16 ++-- app/Models/AuditLog.php | 30 +++++++ app/Models/Ticket.php | 3 + app/Models/TicketIncidentType.php | 2 + app/Models/TicketType.php | 13 ++- app/Models/User.php | 2 + app/Services/Audit/AuditLogService.php | 70 ++++++++++++++++ app/Services/Ticket/TicketTypeService.php | 45 +++++++++-- app/Traits/Auditable.php | 45 +++++++++++ ...5513_alter_users_table_add_auth_fields.php | 42 ++++++++++ ...2026_06_25_075605_create_tenants_table.php | 44 ++++++++++ ...06_25_075641_create_user_tenants_table.php | 45 +++++++++++ ...6_06_25_075724_create_menu_lists_table.php | 48 +++++++++++ ..._06_25_075822_create_menu_groups_table.php | 35 ++++++++ ...5_075927_create_menu_group_menus_table.php | 54 +++++++++++++ ...5_080003_create_user_menu_groups_table.php | 48 +++++++++++ ...6_25_080042_create_user_profiles_table.php | 48 +++++++++++ ...5_080114_create_wilayah_provinsi_table.php | 33 ++++++++ ..._080141_create_wilayah_kabupaten_table.php | 37 +++++++++ ..._080231_create_wilayah_kecamatan_table.php | 37 +++++++++ ...06_25_080321_create_wilayah_desa_table.php | 37 +++++++++ ...6_06_25_085428_create_audit_logs_table.php | 71 ++++++++++++++++ routes/api.php | 10 +++ 29 files changed, 1097 insertions(+), 34 deletions(-) create mode 100644 app/Http/Controllers/Audit/AuditLogController.php create mode 100644 app/Http/Requests/Audit/AuditLogFilterRequest.php create mode 100644 app/Http/Resources/Audit/AuditLogResource.php create mode 100644 app/Models/AuditLog.php create mode 100644 app/Services/Audit/AuditLogService.php create mode 100644 app/Traits/Auditable.php create mode 100644 database/migrations/2026_06_25_075513_alter_users_table_add_auth_fields.php create mode 100644 database/migrations/2026_06_25_075605_create_tenants_table.php create mode 100644 database/migrations/2026_06_25_075641_create_user_tenants_table.php create mode 100644 database/migrations/2026_06_25_075724_create_menu_lists_table.php create mode 100644 database/migrations/2026_06_25_075822_create_menu_groups_table.php create mode 100644 database/migrations/2026_06_25_075927_create_menu_group_menus_table.php create mode 100644 database/migrations/2026_06_25_080003_create_user_menu_groups_table.php create mode 100644 database/migrations/2026_06_25_080042_create_user_profiles_table.php create mode 100644 database/migrations/2026_06_25_080114_create_wilayah_provinsi_table.php create mode 100644 database/migrations/2026_06_25_080141_create_wilayah_kabupaten_table.php create mode 100644 database/migrations/2026_06_25_080231_create_wilayah_kecamatan_table.php create mode 100644 database/migrations/2026_06_25_080321_create_wilayah_desa_table.php create mode 100644 database/migrations/2026_06_25_085428_create_audit_logs_table.php diff --git a/app/Http/Controllers/Audit/AuditLogController.php b/app/Http/Controllers/Audit/AuditLogController.php new file mode 100644 index 0000000..eb3b59b --- /dev/null +++ b/app/Http/Controllers/Audit/AuditLogController.php @@ -0,0 +1,40 @@ +service = $service; + } + + public function index(AuditLogFilterRequest $request) + { + $data = $this->service->list( + $request->validated() + ); + + return $this->success( + AuditLogResource::collection($data), + 'Data audit log berhasil diambil' + ); + } + + public function show($id) + { + $data = $this->service->detail($id); + + return $this->success( + new AuditLogResource($data), + 'Detail audit log berhasil diambil' + ); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Ticket/TicketTypeController.php b/app/Http/Controllers/Ticket/TicketTypeController.php index c7e32cb..7bfd968 100644 --- a/app/Http/Controllers/Ticket/TicketTypeController.php +++ b/app/Http/Controllers/Ticket/TicketTypeController.php @@ -18,21 +18,24 @@ class TicketTypeController extends ApiController public function index(Request $request) { - $data = TicketType::latest()->paginate( - $request->get('per_page', 10) + $data = $this->service->list( + $request->all() ); - return $this->success($data); + return $this->success( + TicketTypeResource::collection($data), + 'Data Ticket Type berhasil diambil' + ); } public function store(StoreTicketTypeRequest $request) { - $type = $this->service->create( + $data = $this->service->create( $request->validated() ); return $this->created( - new TicketTypeResource($type), + new TicketTypeResource($data), 'Ticket Type berhasil dibuat' ); } @@ -40,7 +43,8 @@ class TicketTypeController extends ApiController public function show(TicketType $ticket_type) { return $this->success( - new TicketTypeResource($ticket_type) + new TicketTypeResource($ticket_type), + 'Detail Ticket Type berhasil diambil' ); } @@ -48,13 +52,13 @@ class TicketTypeController extends ApiController UpdateTicketTypeRequest $request, TicketType $ticket_type ) { - $type = $this->service->update( + $data = $this->service->update( $ticket_type, $request->validated() ); return $this->updated( - new TicketTypeResource($type), + new TicketTypeResource($data), 'Ticket Type berhasil diupdate' ); } @@ -67,4 +71,4 @@ class TicketTypeController extends ApiController 'Ticket Type berhasil dihapus' ); } -} +} \ No newline at end of file diff --git a/app/Http/Requests/Audit/AuditLogFilterRequest.php b/app/Http/Requests/Audit/AuditLogFilterRequest.php new file mode 100644 index 0000000..3295005 --- /dev/null +++ b/app/Http/Requests/Audit/AuditLogFilterRequest.php @@ -0,0 +1,55 @@ +|string> + */ + public function rules(): array + { + return [ + + 'module' => [ + 'nullable', + 'string' + ], + + 'action' => [ + 'nullable', + 'string' + ], + + 'user_id' => [ + 'nullable', + 'integer' + ], + + 'tenant_id' => [ + 'nullable', + 'integer' + ], + + 'per_page' => [ + 'nullable', + 'integer', + 'min:1', + 'max:100' + ] + ]; + } +} diff --git a/app/Http/Requests/TicketType/StoreTicketTypeRequest.php b/app/Http/Requests/TicketType/StoreTicketTypeRequest.php index 6276644..f522618 100644 --- a/app/Http/Requests/TicketType/StoreTicketTypeRequest.php +++ b/app/Http/Requests/TicketType/StoreTicketTypeRequest.php @@ -23,10 +23,81 @@ class StoreTicketTypeRequest extends FormRequest public function rules(): array { return [ - 'code' => 'required|string|max:50|unique:ticket_types,code', - 'name' => 'required|string|max:100', - 'need_approval' => 'boolean', - 'sla_minutes' => 'nullable|integer' + + 'code' => [ + 'required', + 'string', + 'max:50', + 'unique:ticket_types,code' + ], + + 'name' => [ + 'required', + 'string', + 'max:100' + ], + + 'need_approval' => [ + 'nullable', + 'boolean' + ], + + 'sla_minutes' => [ + 'nullable', + 'integer', + 'min:1' + ], + + 'require_photo' => [ + 'nullable', + 'boolean' + ], + + 'require_material' => [ + 'nullable', + 'boolean' + ], + + 'need_customer' => [ + 'nullable', + 'boolean' + ], + + 'need_incident_type' => [ + 'nullable', + 'boolean' + ] ]; } + + protected function prepareForValidation(): void + { + $this->merge([ + + 'need_approval' => filter_var( + $this->need_approval, + FILTER_VALIDATE_BOOLEAN + ), + + 'require_photo' => filter_var( + $this->require_photo, + FILTER_VALIDATE_BOOLEAN + ), + + 'require_material' => filter_var( + $this->require_material, + FILTER_VALIDATE_BOOLEAN + ), + + 'need_customer' => filter_var( + $this->need_customer, + FILTER_VALIDATE_BOOLEAN + ), + + 'need_incident_type' => filter_var( + $this->need_incident_type, + FILTER_VALIDATE_BOOLEAN + ) + ]); + } } diff --git a/app/Http/Requests/TicketType/UpdateTicketTypeRequest.php b/app/Http/Requests/TicketType/UpdateTicketTypeRequest.php index 9bcad7b..d3eb19d 100644 --- a/app/Http/Requests/TicketType/UpdateTicketTypeRequest.php +++ b/app/Http/Requests/TicketType/UpdateTicketTypeRequest.php @@ -4,6 +4,7 @@ namespace App\Http\Requests\TicketType; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rule; class UpdateTicketTypeRequest extends FormRequest { @@ -23,10 +24,82 @@ class UpdateTicketTypeRequest extends FormRequest public function rules(): array { return [ - 'code' => 'sometimes|string|max:50|unique:ticket_types,code,' . $this->route('ticket_type'), - 'name' => 'sometimes|string|max:100', - 'need_approval' => 'sometimes|boolean', - 'sla_minutes' => 'nullable|integer' + + 'code' => [ + 'required', + 'string', + 'max:50', + Rule::unique('ticket_types', 'code') + ->ignore($this->ticket_type->id) + ], + + 'name' => [ + 'required', + 'string', + 'max:100' + ], + + 'need_approval' => [ + 'nullable', + 'boolean' + ], + + 'sla_minutes' => [ + 'nullable', + 'integer', + 'min:1' + ], + + 'require_photo' => [ + 'nullable', + 'boolean' + ], + + 'require_material' => [ + 'nullable', + 'boolean' + ], + + 'need_customer' => [ + 'nullable', + 'boolean' + ], + + 'need_incident_type' => [ + 'nullable', + 'boolean' + ] ]; } + + protected function prepareForValidation(): void + { + $this->merge([ + + 'need_approval' => filter_var( + $this->need_approval, + FILTER_VALIDATE_BOOLEAN + ), + + 'require_photo' => filter_var( + $this->require_photo, + FILTER_VALIDATE_BOOLEAN + ), + + 'require_material' => filter_var( + $this->require_material, + FILTER_VALIDATE_BOOLEAN + ), + + 'need_customer' => filter_var( + $this->need_customer, + FILTER_VALIDATE_BOOLEAN + ), + + 'need_incident_type' => filter_var( + $this->need_incident_type, + FILTER_VALIDATE_BOOLEAN + ) + ]); + } } diff --git a/app/Http/Resources/Audit/AuditLogResource.php b/app/Http/Resources/Audit/AuditLogResource.php new file mode 100644 index 0000000..9c43cc3 --- /dev/null +++ b/app/Http/Resources/Audit/AuditLogResource.php @@ -0,0 +1,39 @@ + $this->id, + + 'user_id' => $this->user_id, + + 'tenant_id' => $this->tenant_id, + + 'module' => $this->module, + + 'action' => $this->action, + + 'table_name' => $this->table_name, + + 'record_id' => $this->record_id, + + 'old_values' => $this->old_values, + + 'new_values' => $this->new_values, + + 'description' => $this->description, + + 'ip_address' => $this->ip_address, + + 'created_at' => $this->created_at, + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/TicketType/TicketTypeResource.php b/app/Http/Resources/TicketType/TicketTypeResource.php index 129fcf4..4d58d9c 100644 --- a/app/Http/Resources/TicketType/TicketTypeResource.php +++ b/app/Http/Resources/TicketType/TicketTypeResource.php @@ -15,13 +15,17 @@ class TicketTypeResource extends JsonResource public function toArray(Request $request): array { return [ - 'id' => $this->id, - 'code' => $this->code, - 'name' => $this->name, + 'id' => $this->id, + 'code' => $this->code, + 'name' => $this->name, 'need_approval' => $this->need_approval, - 'sla_minutes' => $this->sla_minutes, - 'created_at' => $this->created_at, - 'updated_at' => $this->updated_at, + 'sla_minutes' => $this->sla_minutes, + 'require_photo' => $this->require_photo, + 'require_material' => $this->require_material, + 'need_customer' => $this->need_customer, + 'need_incident_type' => $this->need_incident_type, + 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, ]; } } diff --git a/app/Models/AuditLog.php b/app/Models/AuditLog.php new file mode 100644 index 0000000..a57209c --- /dev/null +++ b/app/Models/AuditLog.php @@ -0,0 +1,30 @@ + 'array', + 'new_values' => 'array', + 'created_at' => 'datetime' + ]; +} \ No newline at end of file diff --git a/app/Models/Ticket.php b/app/Models/Ticket.php index c76ec8f..e777d82 100644 --- a/app/Models/Ticket.php +++ b/app/Models/Ticket.php @@ -2,10 +2,13 @@ namespace App\Models; +use App\Traits\Auditable; use Illuminate\Database\Eloquent\Model; class Ticket extends Model { + use Auditable; + protected $fillable = [ 'ticket_no', 'ticket_type_id', diff --git a/app/Models/TicketIncidentType.php b/app/Models/TicketIncidentType.php index 0669c22..c646244 100644 --- a/app/Models/TicketIncidentType.php +++ b/app/Models/TicketIncidentType.php @@ -2,12 +2,14 @@ namespace App\Models; +use App\Traits\Auditable; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class TicketIncidentType extends Model { use SoftDeletes; + use Auditable; protected $fillable = [ 'name', diff --git a/app/Models/TicketType.php b/app/Models/TicketType.php index c5b1ddc..1126dd2 100644 --- a/app/Models/TicketType.php +++ b/app/Models/TicketType.php @@ -2,10 +2,13 @@ namespace App\Models; +use App\Traits\Auditable; use Illuminate\Database\Eloquent\Model; class TicketType extends Model { + use Auditable; + protected $table = 'ticket_types'; protected $fillable = [ @@ -16,13 +19,15 @@ class TicketType extends Model 'require_photo', 'require_material', 'need_customer', + 'need_incident_type' ]; protected $casts = [ - 'need_approval' => 'boolean', - 'require_photo' => 'boolean', + 'need_approval' => 'boolean', + 'require_photo' => 'boolean', 'require_material' => 'boolean', - 'need_customer' => 'boolean', + 'need_customer' => 'boolean', + 'need_incident_type' => 'boolean' ]; /* @@ -38,4 +43,4 @@ class TicketType extends Model 'ticket_type_id' ); } -} \ No newline at end of file +} diff --git a/app/Models/User.php b/app/Models/User.php index 3e0dd8c..7e8bd40 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; +// use App\Traits\Auditable; class User extends Authenticatable @@ -14,6 +15,7 @@ class User extends Authenticatable /** @use HasFactory<\Database\Factories\UserFactory> */ use HasFactory, Notifiable; use HasApiTokens; + // use Audittable; /** * The attributes that are mass assignable. diff --git a/app/Services/Audit/AuditLogService.php b/app/Services/Audit/AuditLogService.php new file mode 100644 index 0000000..e4803fd --- /dev/null +++ b/app/Services/Audit/AuditLogService.php @@ -0,0 +1,70 @@ +where('module', $filters['module']); + } + + if (!empty($filters['action'])) { + $query->where('action', $filters['action']); + } + + if (!empty($filters['user_id'])) { + $query->where('user_id', $filters['user_id']); + } + + if (!empty($filters['tenant_id'])) { + $query->where('tenant_id', $filters['tenant_id']); + } + + return $query + ->latest('id') + ->paginate( + $filters['per_page'] ?? 20 + ); + } + + public function detail($id) + { + return AuditLog::findOrFail($id); + } + + public static function log( + string $module, + string $action, + ?string $tableName = null, + ?int $recordId = null, + ?array $oldValues = null, + ?array $newValues = null, + ?string $description = null + ): void { + + AuditLog::create([ + 'user_id' => auth()->id(), + 'tenant_id' => request()->header('X-Tenant-Id'), + + 'module' => $module, + 'action' => $action, + + 'table_name' => $tableName, + 'record_id' => $recordId, + + 'old_values' => $oldValues, + 'new_values' => $newValues, + + 'ip_address' => request()->ip(), + 'user_agent' => request()->userAgent(), + + 'description' => $description, + ]); + } +} \ No newline at end of file diff --git a/app/Services/Ticket/TicketTypeService.php b/app/Services/Ticket/TicketTypeService.php index 91fc34e..b24bbc5 100644 --- a/app/Services/Ticket/TicketTypeService.php +++ b/app/Services/Ticket/TicketTypeService.php @@ -6,19 +6,50 @@ use App\Models\TicketType; class TicketTypeService { + public function list(array $filters = []) + { + return TicketType::query() + ->when( + !empty($filters['search']), + fn($q) => $q->where(function ($x) use ($filters) { + + $x->where( + 'code', + 'ILIKE', + "%{$filters['search']}%" + ) + ->orWhere( + 'name', + 'ILIKE', + "%{$filters['search']}%" + ); + }) + ) + ->latest('id') + ->paginate( + $filters['per_page'] ?? 10 + ); + } + public function create(array $data): TicketType { return TicketType::create($data); } - public function update(TicketType $type, array $data): TicketType - { - $type->update($data); - return $type->fresh(); + public function update( + TicketType $ticketType, + array $data + ): TicketType { + + $ticketType->update($data); + + return $ticketType->fresh(); } - public function delete(TicketType $type): bool - { - return $type->delete(); + public function delete( + TicketType $ticketType + ): bool { + + return $ticketType->delete(); } } \ No newline at end of file diff --git a/app/Traits/Auditable.php b/app/Traits/Auditable.php new file mode 100644 index 0000000..3794303 --- /dev/null +++ b/app/Traits/Auditable.php @@ -0,0 +1,45 @@ +getTable(), + recordId: $model->id, + newValues: $model->toArray() + ); + }); + + static::updated(function ($model) { + + AuditLogService::log( + module: strtolower(class_basename($model)), + action: 'update', + tableName: $model->getTable(), + recordId: $model->id, + oldValues: $model->getOriginal(), + newValues: $model->getAttributes() + ); + }); + + static::deleted(function ($model) { + + AuditLogService::log( + module: strtolower(class_basename($model)), + action: 'delete', + tableName: $model->getTable(), + recordId: $model->id, + oldValues: $model->toArray() + ); + }); + } +} \ No newline at end of file diff --git a/database/migrations/2026_06_25_075513_alter_users_table_add_auth_fields.php b/database/migrations/2026_06_25_075513_alter_users_table_add_auth_fields.php new file mode 100644 index 0000000..04faea9 --- /dev/null +++ b/database/migrations/2026_06_25_075513_alter_users_table_add_auth_fields.php @@ -0,0 +1,42 @@ +string('username')->nullable()->unique()->after('name'); + } + + if (!Schema::hasColumn('users', 'status')) { + $table->enum('status', [ + 'active', + 'inactive', + 'suspended' + ])->default('active'); + } + + if (!Schema::hasColumn('users', 'last_login_at')) { + $table->timestamp('last_login_at')->nullable(); + } + }); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + + $table->dropColumn([ + 'username', + 'status', + 'last_login_at' + ]); + }); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_06_25_075605_create_tenants_table.php b/database/migrations/2026_06_25_075605_create_tenants_table.php new file mode 100644 index 0000000..f52ac46 --- /dev/null +++ b/database/migrations/2026_06_25_075605_create_tenants_table.php @@ -0,0 +1,44 @@ +id(); + + $table->string('tenant_code')->unique(); + + $table->string('tenant_name'); + + $table->string('phone')->nullable(); + + $table->string('email')->nullable(); + + $table->text('address')->nullable(); + + $table->enum('status', [ + 'active', + 'inactive' + ])->default('active'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('tenants'); + } +}; diff --git a/database/migrations/2026_06_25_075641_create_user_tenants_table.php b/database/migrations/2026_06_25_075641_create_user_tenants_table.php new file mode 100644 index 0000000..4b6e3b6 --- /dev/null +++ b/database/migrations/2026_06_25_075641_create_user_tenants_table.php @@ -0,0 +1,45 @@ +id(); + + $table->foreignId('user_id') + ->constrained() + ->cascadeOnDelete(); + + $table->foreignId('tenant_id') + ->constrained() + ->cascadeOnDelete(); + + $table->boolean('is_default') + ->default(false); + + $table->timestamps(); + + $table->unique([ + 'user_id', + 'tenant_id' + ]); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_tenants'); + } +}; diff --git a/database/migrations/2026_06_25_075724_create_menu_lists_table.php b/database/migrations/2026_06_25_075724_create_menu_lists_table.php new file mode 100644 index 0000000..6e25be2 --- /dev/null +++ b/database/migrations/2026_06_25_075724_create_menu_lists_table.php @@ -0,0 +1,48 @@ +id(); + + $table->foreignId('parent_id') + ->nullable() + ->constrained('menu_lists') + ->cascadeOnDelete(); + + $table->string('name'); + + $table->string('slug')->unique(); + + $table->string('url')->nullable(); + + $table->string('icon')->nullable(); + + $table->string('component')->nullable(); + + $table->integer('sort_order')->default(0); + + $table->boolean('is_active')->default(true); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('menu_lists'); + } +}; diff --git a/database/migrations/2026_06_25_075822_create_menu_groups_table.php b/database/migrations/2026_06_25_075822_create_menu_groups_table.php new file mode 100644 index 0000000..47be172 --- /dev/null +++ b/database/migrations/2026_06_25_075822_create_menu_groups_table.php @@ -0,0 +1,35 @@ +id(); + + $table->string('name')->unique(); + + $table->text('description')->nullable(); + + $table->boolean('is_active')->default(true); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('menu_groups'); + } +}; diff --git a/database/migrations/2026_06_25_075927_create_menu_group_menus_table.php b/database/migrations/2026_06_25_075927_create_menu_group_menus_table.php new file mode 100644 index 0000000..8f03d17 --- /dev/null +++ b/database/migrations/2026_06_25_075927_create_menu_group_menus_table.php @@ -0,0 +1,54 @@ +id(); + + $table->foreignId('menu_group_id') + ->constrained() + ->cascadeOnDelete(); + + $table->foreignId('menu_id') + ->constrained('menu_lists') + ->cascadeOnDelete(); + + $table->boolean('can_view')->default(false); + + $table->boolean('can_create')->default(false); + + $table->boolean('can_update')->default(false); + + $table->boolean('can_delete')->default(false); + + $table->boolean('can_approve')->default(false); + + $table->boolean('can_export')->default(false); + + $table->timestamps(); + + $table->unique([ + 'menu_group_id', + 'menu_id' + ]); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('menu_group_menus'); + } +}; diff --git a/database/migrations/2026_06_25_080003_create_user_menu_groups_table.php b/database/migrations/2026_06_25_080003_create_user_menu_groups_table.php new file mode 100644 index 0000000..f717006 --- /dev/null +++ b/database/migrations/2026_06_25_080003_create_user_menu_groups_table.php @@ -0,0 +1,48 @@ +id(); + + $table->foreignId('user_id') + ->constrained() + ->cascadeOnDelete(); + + $table->foreignId('menu_group_id') + ->constrained() + ->cascadeOnDelete(); + + $table->foreignId('tenant_id') + ->nullable() + ->constrained() + ->cascadeOnDelete(); + + $table->timestamps(); + + $table->unique([ + 'user_id', + 'menu_group_id', + 'tenant_id' + ]); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_menu_groups'); + } +}; diff --git a/database/migrations/2026_06_25_080042_create_user_profiles_table.php b/database/migrations/2026_06_25_080042_create_user_profiles_table.php new file mode 100644 index 0000000..1ec52f7 --- /dev/null +++ b/database/migrations/2026_06_25_080042_create_user_profiles_table.php @@ -0,0 +1,48 @@ +id(); + + $table->foreignId('user_id') + ->unique() + ->constrained() + ->cascadeOnDelete(); + + $table->string('nik', 30)->nullable(); + + $table->string('phone', 30)->nullable(); + + $table->text('address')->nullable(); + + $table->unsignedBigInteger('provinsi_id')->nullable(); + + $table->unsignedBigInteger('kabupaten_id')->nullable(); + + $table->unsignedBigInteger('kecamatan_id')->nullable(); + + $table->unsignedBigInteger('desa_id')->nullable(); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_profiles'); + } +}; diff --git a/database/migrations/2026_06_25_080114_create_wilayah_provinsi_table.php b/database/migrations/2026_06_25_080114_create_wilayah_provinsi_table.php new file mode 100644 index 0000000..63883c5 --- /dev/null +++ b/database/migrations/2026_06_25_080114_create_wilayah_provinsi_table.php @@ -0,0 +1,33 @@ +id(); + + $table->string('kode', 20)->unique(); + + $table->string('nama'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('wilayah_provinsi'); + } +}; diff --git a/database/migrations/2026_06_25_080141_create_wilayah_kabupaten_table.php b/database/migrations/2026_06_25_080141_create_wilayah_kabupaten_table.php new file mode 100644 index 0000000..ffce320 --- /dev/null +++ b/database/migrations/2026_06_25_080141_create_wilayah_kabupaten_table.php @@ -0,0 +1,37 @@ +id(); + + $table->foreignId('provinsi_id') + ->constrained('wilayah_provinsi') + ->cascadeOnDelete(); + + $table->string('kode', 20)->unique(); + + $table->string('nama'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('wilayah_kabupaten'); + } +}; diff --git a/database/migrations/2026_06_25_080231_create_wilayah_kecamatan_table.php b/database/migrations/2026_06_25_080231_create_wilayah_kecamatan_table.php new file mode 100644 index 0000000..01f8da1 --- /dev/null +++ b/database/migrations/2026_06_25_080231_create_wilayah_kecamatan_table.php @@ -0,0 +1,37 @@ +id(); + + $table->foreignId('kabupaten_id') + ->constrained('wilayah_kabupaten') + ->cascadeOnDelete(); + + $table->string('kode', 20)->unique(); + + $table->string('nama'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('wilayah_kecamatan'); + } +}; diff --git a/database/migrations/2026_06_25_080321_create_wilayah_desa_table.php b/database/migrations/2026_06_25_080321_create_wilayah_desa_table.php new file mode 100644 index 0000000..4dc73fc --- /dev/null +++ b/database/migrations/2026_06_25_080321_create_wilayah_desa_table.php @@ -0,0 +1,37 @@ +id(); + + $table->foreignId('kecamatan_id') + ->constrained('wilayah_kecamatan') + ->cascadeOnDelete(); + + $table->string('kode', 20)->unique(); + + $table->string('nama'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('wilayah_desa'); + } +}; diff --git a/database/migrations/2026_06_25_085428_create_audit_logs_table.php b/database/migrations/2026_06_25_085428_create_audit_logs_table.php new file mode 100644 index 0000000..2a1669f --- /dev/null +++ b/database/migrations/2026_06_25_085428_create_audit_logs_table.php @@ -0,0 +1,71 @@ +id(); + + $table->foreignId('user_id') + ->nullable() + ->constrained('users') + ->nullOnDelete(); + + $table->foreignId('tenant_id') + ->nullable() + ->constrained('tenants') + ->nullOnDelete(); + + $table->string('module', 100); + + $table->string('action', 50); + + $table->string('table_name', 100) + ->nullable(); + + $table->unsignedBigInteger('record_id') + ->nullable(); + + $table->json('old_values') + ->nullable(); + + $table->json('new_values') + ->nullable(); + + $table->string('ip_address', 50) + ->nullable(); + + $table->text('user_agent') + ->nullable(); + + $table->text('description') + ->nullable(); + + $table->timestamp('created_at') + ->useCurrent(); + + $table->index('user_id'); + $table->index('tenant_id'); + $table->index('module'); + $table->index('action'); + $table->index('record_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('audit_logs'); + } +}; diff --git a/routes/api.php b/routes/api.php index e21a058..f9c3235 100644 --- a/routes/api.php +++ b/routes/api.php @@ -8,6 +8,7 @@ use App\Http\Controllers\Ticket\TicketTypeController; use App\Http\Controllers\Ticket\TicketWorkflowController; use App\Http\Controllers\Ticket\TicketIncidentTypeController; use App\Http\Controllers\Material\MaterialController; +use App\Http\Controllers\Audit\AuditLogController; Route::post('/login', [AuthController::class, 'login']); @@ -70,5 +71,14 @@ Route::middleware('auth:sanctum')->group(function () { }); + Route::prefix('audit-logs') + ->controller(AuditLogController::class) + ->group(function () { + + Route::get('/', 'index'); + + Route::get('/{id}', 'show'); + }); + });