Files
services_core/app/Http/Controllers/Ticket/TicketController.php
T
2026-08-07 09:16:13 +07:00

173 lines
11 KiB
PHP

<?php
namespace App\Http\Controllers\Ticket;
use App\Http\Controllers\ApiController;
use App\Http\Requests\Ticket\IndexTicketRequest;
use App\Http\Requests\Ticket\StoreTicketRequest;
use App\Http\Requests\Ticket\UpdateTicketRequest;
use App\Http\Resources\Ticket\TicketResource;
use App\Models\Ticket;
use App\Services\Ticket\TicketService;
use App\Models\Customer;
use App\Models\TicketType;
use App\Models\User;
use App\Models\TicketMaterial;
use Illuminate\Http\Request;
class TicketController extends ApiController
{
public function __construct(
protected TicketService $service
) {}
public function index(IndexTicketRequest $request)
{
$filters = $request->validated();
$tenantId = $request->attributes->get('tenant_id');
$sortBy = $filters['sort_by'] ?? 'created_at';
$sortDirection = $filters['sort_direction'] ?? 'desc';
$tickets = Ticket::query()->with(['ticketType:id,code,name,need_approval,require_photo,require_material','customer:id,customer_code,name','tenant:id,tenant_name','creator:id,name','assignments.user:id,name'])
->when(! $request->user()->isMasterAdmin(), fn ($query) => $query->where('tenant_id', $tenantId))
->when($request->user()->isMasterAdmin() && $tenantId, fn ($query) => $query->where('tenant_id', $tenantId))
->when(! empty($filters['assigned_to_me']), fn ($query) => $query->whereHas('assignments', fn ($assignment) => $assignment->where('user_id', $request->user()->id)))
->when(
! empty($filters['search']),
fn ($query) => $query->where(function ($searchQuery) use ($filters) {
$searchQuery->where('ticket_no', 'ILIKE', "%{$filters['search']}%")
->orWhere('title', 'ILIKE', "%{$filters['search']}%")
->orWhere('description', 'ILIKE', "%{$filters['search']}%");
})
)
->when(
! empty($filters['status']),
fn ($query) => $query->where('status', $filters['status'])
)
->when(
! empty($filters['priority']),
fn ($query) => $query->where('priority', $filters['priority'])
)
->when(
! empty($filters['ticket_type_id']),
fn ($query) => $query->where('ticket_type_id', $filters['ticket_type_id'])
)
->when(
! empty($filters['customer_id']),
fn ($query) => $query->where('customer_id', $filters['customer_id'])
)
->orderBy($sortBy, $sortDirection)
->paginate($filters['per_page'] ?? 10);
return $this->successPaginated(
$tickets,
TicketResource::class,
'Data Ticket berhasil diambil'
);
}
public function store(StoreTicketRequest $request)
{
$ticket = $this->service->create(
$request->validated(), $request->user(), $request->attributes->get('tenant_id')
);
return $this->created(
new TicketResource($ticket),
'Ticket berhasil dibuat'
);
}
public function show(Request $request, Ticket $ticket)
{
$this->authorizeTenant($request, $ticket);
return $this->success(
new TicketResource($ticket->load(['ticketType','customer','tenant','creator','assignments.user','logs','materials','attachments']))
);
}
public function update(
UpdateTicketRequest $request,
Ticket $ticket
) {
$this->authorizeTenant($request, $ticket);
$ticket = $this->service->update(
$ticket,
$request->validated()
);
return $this->updated(
new TicketResource($ticket),
'Ticket berhasil diperbarui'
);
}
public function destroy(Request $request, Ticket $ticket)
{
$this->authorizeTenant($request, $ticket);
$this->service->delete($ticket);
return $this->deleted(
'Ticket berhasil dibatalkan'
);
}
public function options(Request $request)
{
$tenantId = $request->attributes->get('tenant_id');
return $this->success([
'ticket_types' => TicketType::orderBy('name')->get(['id','code','name','need_approval','require_photo','require_material']),
'customers' => Customer::when(! $request->user()->isMasterAdmin() || $tenantId, fn ($q) => $q->where('tenant_id', $tenantId))->orderBy('name')->limit(500)->get(['id','customer_code','name','tenant_id']),
'technicians' => User::whereHas('userTenants', fn ($q) => $q->where('tenant_id', $tenantId))->orderBy('name')->get(['id','name','email']),
'installation_setting' => $tenantId
? \App\Models\TenantInstallationSetting::firstOrCreate(['tenant_id' => $tenantId], ['activation_mode' => 'list_order'])
: null,
'package_profiles'=>\App\Models\NasPackageProfile::where('tenant_id',$tenantId)->where('status','active')->orderBy('name')->get(['id','name']),
'nas_mikrotiks'=>\App\Models\NasMikrotik::where('tenant_id',$tenantId)->where('status','active')->orderBy('name')->get(['id','name']),
'billing_profiles'=>\App\Models\BillingProfile::where('tenant_id',$tenantId)->where('status','active')->orderBy('name')->get(['id','name']),
'topology_odps'=>\App\Models\TopologyNode::where('tenant_id',$tenantId)->whereHas('deviceType',fn($q)=>$q->whereRaw('UPPER(code) = ?',['ODP']))->orderBy('name')->get(['id','code','name']),
], 'Pilihan ticket berhasil diambil');
}
public function storeMaterial(Request $request, Ticket $ticket)
{
$this->authorizeTenant($request,$ticket);
$data=$request->validate(['technician_material_id'=>['nullable','required_without:technician_consumable_id','integer','exists:technician_materials,id'],'technician_consumable_id'=>['nullable','required_without:technician_material_id','integer','exists:technician_consumables,id'],'qty'=>['required','numeric','gt:0'],'notes'=>['nullable','string','max:1000']]);
abort_if(!empty($data['technician_material_id'])&&!empty($data['technician_consumable_id']),422,'Pilih satu material saja.');
$material=\Illuminate\Support\Facades\DB::transaction(function()use($data,$ticket,$request){
if(!empty($data['technician_material_id'])){$source=\App\Models\TechnicianMaterial::whereKey($data['technician_material_id'])->where('user_id',$request->user()->id)->where('status','assigned')->lockForUpdate()->firstOrFail();$qty=1;$source->update(['status'=>'used']);$name=$source->material_name;}
else{$source=\App\Models\TechnicianConsumable::whereKey($data['technician_consumable_id'])->where('user_id',$request->user()->id)->where('status','assigned')->lockForUpdate()->firstOrFail();$qty=(float)$data['qty'];abort_if((float)$source->qty_remaining<$qty,422,'Jumlah material yang dibawa tidak mencukupi.');$source->update(['qty_used'=>(float)$source->qty_used+$qty,'qty_remaining'=>(float)$source->qty_remaining-$qty]);$name=$source->material_name;}
\App\Models\MaterialMovement::create(['barcode_id'=>$source->barcode_id,'user_id'=>$request->user()->id,'from_user_id'=>$request->user()->id,'movement_type'=>'use','ticket_id'=>$ticket->id,'qty'=>$qty,'description'=>$data['notes']??null,'created_by'=>$request->user()->id,'created_at'=>now()]);
$warehouseBarcode=\App\Models\ItemBarcode::where('barcode',$source->barcode_id)->first();
if($warehouseBarcode)\App\Models\ItemMovement::create(['tenant_id'=>$ticket->tenant_id,'warehouse_item_id'=>$warehouseBarcode->warehouse_item_id,'item_barcode_id'=>$warehouseBarcode->id,'movement_type'=>'used_on_ticket','qty'=>$qty,'reference_type'=>'ticket','reference_id'=>$ticket->id,'from_user_id'=>$request->user()->id,'notes'=>$data['notes']??null,'created_by'=>$request->user()->id]);
return $ticket->materials()->create([...$data,'material_name'=>$name,'qty'=>$qty,'used_by'=>$request->user()->id,'created_at'=>now()]);
});
\App\Models\TicketLog::create(['ticket_id'=>$ticket->id,'user_id'=>$request->user()->id,'activity'=>'Material Digunakan','notes'=>$material->material_name.' x '.$material->qty,'created_at'=>now()]);
return $this->created($material->load('user:id,name'),'Material ticket berhasil ditambahkan');
}
public function materialOptions(Request $request, Ticket $ticket)
{
$this->authorizeTenant($request,$ticket);$userId=$request->user()->id;
return $this->success(['serialized'=>\App\Models\TechnicianMaterial::where('user_id',$userId)->where('status','assigned')->get(['id','barcode_id','material_name','serial_number']),'consumables'=>\App\Models\TechnicianConsumable::where('user_id',$userId)->where('status','assigned')->where('qty_remaining','>',0)->get(['id','barcode_id','material_name','unit','qty_remaining'])],'Material yang dibawa berhasil diambil');
}
public function installationPorts(Request $request,Ticket $ticket,int $node,\App\Services\Customer\CustomerService $customers){$this->authorizeTenant($request,$ticket);return $this->success($customers->availableOdpPorts($node,$request->user(),$ticket->tenant_id,$ticket->customer_id),'Port ODP berhasil diambil');}
public function destroyMaterial(Request $request, Ticket $ticket, TicketMaterial $material)
{
$this->authorizeTenant($request,$ticket);abort_unless((int)$material->ticket_id===(int)$ticket->id,404);
abort_if(in_array($ticket->status,['resolved','closed'],true),422,'Material ticket yang sudah selesai tidak dapat diubah.');
\Illuminate\Support\Facades\DB::transaction(function()use($request,$ticket,$material){
if($material->technician_material_id){$source=\App\Models\TechnicianMaterial::lockForUpdate()->find($material->technician_material_id);if($source)$source->update(['status'=>'assigned']);}
else{$source=\App\Models\TechnicianConsumable::lockForUpdate()->find($material->technician_consumable_id);if($source)$source->update(['qty_used'=>max(0,(float)$source->qty_used-(float)$material->qty),'qty_remaining'=>(float)$source->qty_remaining+(float)$material->qty,'status'=>'assigned']);}
if(isset($source)&&$source){\App\Models\MaterialMovement::create(['barcode_id'=>$source->barcode_id,'user_id'=>$request->user()->id,'to_user_id'=>$request->user()->id,'movement_type'=>'return','ticket_id'=>$ticket->id,'qty'=>$material->qty,'description'=>'Pembatalan pemakaian material pada ticket','created_by'=>$request->user()->id,'created_at'=>now()]);$warehouseBarcode=\App\Models\ItemBarcode::where('barcode',$source->barcode_id)->first();if($warehouseBarcode)\App\Models\ItemMovement::create(['tenant_id'=>$ticket->tenant_id,'warehouse_item_id'=>$warehouseBarcode->warehouse_item_id,'item_barcode_id'=>$warehouseBarcode->id,'movement_type'=>'ticket_usage_cancelled','qty'=>$material->qty,'reference_type'=>'ticket','reference_id'=>$ticket->id,'to_user_id'=>$request->user()->id,'notes'=>'Material dikembalikan ke saldo teknisi','created_by'=>$request->user()->id]);}
$material->delete();
});
return $this->deleted('Material ticket berhasil dihapus dan dikembalikan ke daftar bawaan user');
}
private function authorizeTenant(Request $request, Ticket $ticket): void
{
abort_unless($request->user()->isMasterAdmin() || (int) $ticket->tenant_id === (int) $request->attributes->get('tenant_id'), 403);
}
}