partial update report technician

This commit is contained in:
2025-07-08 19:44:07 +07:00
parent cfef3775d7
commit 685c6df82e
7 changed files with 2257 additions and 7 deletions

View File

@@ -0,0 +1,411 @@
<?php
namespace App\Exports;
use App\Services\TechnicianReportService;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\WithColumnWidths;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Events\AfterSheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Border;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
class TechnicianReportExport implements FromCollection, WithHeadings, WithStyles, WithColumnWidths, WithEvents
{
protected $dealerId;
protected $startDate;
protected $endDate;
protected $technicianReportService;
protected $mechanics;
protected $headings;
protected $filterInfo;
public function __construct($dealerId = null, $startDate = null, $endDate = null)
{
$this->dealerId = $dealerId;
$this->startDate = $startDate;
$this->endDate = $endDate;
$this->technicianReportService = new TechnicianReportService();
// Get mechanics and prepare headings
$this->prepareHeadings();
$this->prepareFilterInfo();
}
private function prepareHeadings()
{
try {
$reportData = $this->technicianReportService->getTechnicianReportData(
$this->dealerId,
$this->startDate,
$this->endDate
);
$this->mechanics = $reportData['mechanics'];
// Build headings - simplified structure
$this->headings = [
'No',
'Nama Pekerjaan',
'Kode Pekerjaan',
'Kategori'
];
// Add mechanic columns (only total, no completed/pending)
foreach ($this->mechanics as $mechanic) {
$mechanicName = $this->cleanName($mechanic->name);
$this->headings[] = $mechanicName;
}
// Add total column at the end
$this->headings[] = 'Total';
} catch (\Exception $e) {
Log::error('Error preparing headings: ' . $e->getMessage());
$this->headings = ['Error preparing data'];
$this->mechanics = collect();
}
}
private function prepareFilterInfo()
{
$this->filterInfo = [];
// Dealer filter
if ($this->dealerId) {
$dealer = \App\Models\Dealer::find($this->dealerId);
$dealerName = $dealer ? $dealer->name : 'Unknown Dealer';
$this->filterInfo[] = "Dealer: {$dealerName}";
} else {
$this->filterInfo[] = "Dealer: Semua Dealer";
}
// Date range filter
if ($this->startDate && $this->endDate) {
$startDateFormatted = Carbon::parse($this->startDate)->format('d/m/Y');
$endDateFormatted = Carbon::parse($this->endDate)->format('d/m/Y');
$this->filterInfo[] = "Periode: {$startDateFormatted} - {$endDateFormatted}";
} elseif ($this->startDate) {
$startDateFormatted = Carbon::parse($this->startDate)->format('d/m/Y');
$this->filterInfo[] = "Tanggal Mulai: {$startDateFormatted}";
} elseif ($this->endDate) {
$endDateFormatted = Carbon::parse($this->endDate)->format('d/m/Y');
$this->filterInfo[] = "Tanggal Akhir: {$endDateFormatted}";
} else {
$this->filterInfo[] = "Periode: Semua Periode";
}
// Export date
$exportDate = Carbon::now()->format('d/m/Y H:i:s');
$this->filterInfo[] = "Tanggal Export: {$exportDate}";
}
/**
* Clean name for Excel compatibility
*/
private function cleanName($name)
{
// Remove special characters and limit length
$cleaned = preg_replace('/[^a-zA-Z0-9\s]/', '', $name);
$cleaned = trim($cleaned);
// Limit to 31 characters (Excel sheet name limit)
if (strlen($cleaned) > 31) {
$cleaned = substr($cleaned, 0, 31);
}
return $cleaned ?: 'Unknown';
}
public function collection()
{
try {
$reportData = $this->technicianReportService->getTechnicianReportData(
$this->dealerId,
$this->startDate,
$this->endDate
);
$data = [];
$no = 1;
$columnTotals = [];
foreach ($this->mechanics as $mechanic) {
$columnTotals["mechanic_{$mechanic->id}_total"] = 0;
}
$columnTotals['row_total'] = 0;
foreach ($reportData['data'] as $row) {
$rowTotal = 0;
$exportRow = [
$no++,
$row['work_name'],
$row['work_code'],
$row['category_name']
];
foreach ($this->mechanics as $mechanic) {
$mechanicTotal = $row["mechanic_{$mechanic->id}_total"] ?? 0;
$exportRow[] = $mechanicTotal;
$rowTotal += $mechanicTotal;
$columnTotals["mechanic_{$mechanic->id}_total"] += $mechanicTotal;
}
$exportRow[] = $rowTotal;
$columnTotals['row_total'] += $rowTotal;
$data[] = $exportRow;
}
// Add total row
$totalRow = ['', 'TOTAL', '', ''];
foreach ($this->mechanics as $mechanic) {
$totalRow[] = $columnTotals["mechanic_{$mechanic->id}_total"];
}
$totalRow[] = $columnTotals['row_total'];
$data[] = $totalRow;
return collect($data);
} catch (\Exception $e) {
Log::error('Error in collection: ' . $e->getMessage());
return collect([['Error loading data']]);
}
}
public function headings(): array
{
return $this->headings;
}
public function styles(Worksheet $sheet)
{
try {
$lastColumn = $sheet->getHighestColumn();
$lastRow = $sheet->getHighestRow();
// Calculate positions
$titleRow = 1;
$headerRow = 1; // Headers are now in row 2
$dataStartRow = 2; // Data starts in row 3
// Calculate total row position (after data)
$dataRows = count($this->technicianReportService->getTechnicianReportData($this->dealerId, $this->startDate, $this->endDate)['data']);
$totalRow = $dataStartRow + $dataRows;
$filterStartRow = $totalRow + 2; // After total row + empty row
// Style the title row (row 1)
$sheet->getStyle('A' . $titleRow . ':' . $lastColumn . $titleRow)->applyFromArray([
'font' => [
'bold' => true,
'size' => 16,
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
'vertical' => Alignment::VERTICAL_CENTER,
],
]);
// Header styling (row 2)
$sheet->getStyle('A' . $headerRow . ':' . $lastColumn . $headerRow)->applyFromArray([
'font' => [
'bold' => true,
'color' => ['rgb' => 'FFFFFF'],
'size' => 10,
],
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['rgb' => '2E5BBA'],
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
'vertical' => Alignment::VERTICAL_CENTER,
],
'borders' => [
'allBorders' => [
'borderStyle' => Border::BORDER_THIN,
'color' => ['rgb' => '000000'],
],
],
]);
// Data styling (starting from row 3)
if ($lastRow > $headerRow) {
$dataEndRow = $totalRow;
$sheet->getStyle('A' . $dataStartRow . ':' . $lastColumn . $dataEndRow)->applyFromArray([
'borders' => [
'allBorders' => [
'borderStyle' => Border::BORDER_THIN,
'color' => ['rgb' => '000000'],
],
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
'vertical' => Alignment::VERTICAL_CENTER,
],
]);
// Left align text columns
$sheet->getStyle('B' . $dataStartRow . ':D' . $dataEndRow)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT);
// Style the total row
$sheet->getStyle('A' . $totalRow . ':' . $lastColumn . $totalRow)->applyFromArray([
'font' => [
'bold' => true,
'size' => 11,
],
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['rgb' => 'F2F2F2'],
],
'borders' => [
'allBorders' => [
'borderStyle' => Border::BORDER_THIN,
'color' => ['rgb' => '000000'],
],
],
]);
}
// Style the export information section
if ($filterStartRow <= $lastRow) {
$exportInfoRow = $totalRow + 2; // After total row + empty row
$filterEndRow = $lastRow;
// Style the "INFORMASI EXPORT" title
$sheet->getStyle('A' . $exportInfoRow . ':' . $lastColumn . $exportInfoRow)->applyFromArray([
'font' => [
'bold' => true,
'size' => 12,
],
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['rgb' => 'E6E6E6'],
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_LEFT,
'vertical' => Alignment::VERTICAL_CENTER,
],
]);
// Style the filter info rows
$filterInfoStartRow = $exportInfoRow + 3; // After title + empty + "Filter yang Digunakan:"
$sheet->getStyle('A' . $filterInfoStartRow . ':' . $lastColumn . $filterEndRow)->applyFromArray([
'font' => [
'size' => 10,
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_LEFT,
'vertical' => Alignment::VERTICAL_TOP,
],
]);
}
// Auto-size columns
foreach (range('A', $lastColumn) as $column) {
$sheet->getColumnDimension($column)->setAutoSize(true);
}
} catch (\Exception $e) {
Log::error('Error applying styles: ' . $e->getMessage());
}
}
public function columnWidths(): array
{
$widths = [
'A' => 8, // No
'B' => 30, // Nama Pekerjaan
'C' => 15, // Kode Pekerjaan
'D' => 20, // Kategori
];
// Add widths for mechanic columns
$currentColumn = 'E';
foreach ($this->mechanics as $mechanic) {
$widths[$currentColumn++] = 15; // Mechanic total
}
// Add width for total column
$widths[$currentColumn] = 15; // Total
return $widths;
}
public function registerEvents(): array
{
return [
AfterSheet::class => function(AfterSheet $event) {
$sheet = $event->sheet->getDelegate();
$highestColumn = $sheet->getHighestColumn();
$highestRow = $sheet->getHighestRow();
// Header styling ONLY for row 1
$sheet->getStyle('A1:' . $highestColumn . '1')->applyFromArray([
'font' => [
'bold' => true,
'color' => ['rgb' => 'FFFFFF'],
'size' => 10,
],
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['rgb' => '2E5BBA'],
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
'vertical' => Alignment::VERTICAL_CENTER,
],
'borders' => [
'allBorders' => [
'borderStyle' => Border::BORDER_THIN,
'color' => ['rgb' => '000000'],
],
],
]);
// Total row styling (only last row)
$sheet->getStyle('A' . $highestRow . ':' . $highestColumn . $highestRow)->applyFromArray([
'font' => [
'bold' => true,
'size' => 11,
],
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['rgb' => 'F2F2F2'],
],
'borders' => [
'allBorders' => [
'borderStyle' => Border::BORDER_THIN,
'color' => ['rgb' => '000000'],
],
],
]);
// Export info below table
$infoStartRow = $highestRow + 2;
$sheet->setCellValue('A' . $infoStartRow, 'INFORMASI EXPORT');
$sheet->getStyle('A' . $infoStartRow . ':' . $highestColumn . $infoStartRow)->applyFromArray([
'font' => [
'bold' => true,
'size' => 12,
],
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['rgb' => 'E6E6E6'],
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_LEFT,
'vertical' => Alignment::VERTICAL_CENTER,
],
]);
$sheet->setCellValue('A' . ($infoStartRow + 2), 'Filter yang Digunakan:');
$row = $infoStartRow + 3;
foreach ($this->filterInfo as $info) {
$sheet->setCellValue('A' . $row, $info);
$row++;
}
$sheet->getStyle('A' . ($infoStartRow + 2) . ':A' . ($row-1))->applyFromArray([
'font' => [ 'size' => 10 ],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_LEFT,
'vertical' => Alignment::VERTICAL_TOP,
],
]);
}
];
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace App\Http\Controllers\Reports;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Menu;
use App\Services\TechnicianReportService;
use App\Exports\TechnicianReportExport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Facades\Excel;
class ReportTechniciansController extends Controller
{
protected $technicianReportService;
public function __construct(TechnicianReportService $technicianReportService)
{
$this->technicianReportService = $technicianReportService;
}
public function index(Request $request)
{
$menu = Menu::where('link','reports.technician.index')->first();
abort_if(!Gate::allows('view', $menu), 403);
return view('reports.technician');
}
/**
* Get dealers for filter dropdown
*/
public function getDealers()
{
try {
$dealers = $this->technicianReportService->getDealers();
// Default ke "Semua Dealer" (tidak ada dealer yang terselect)
return response()->json([
'status' => 'success',
'data' => $dealers,
'default_dealer' => null
]);
} catch (\Exception $e) {
Log::error('Error getting dealers: ' . $e->getMessage());
return response()->json([
'status' => 'error',
'message' => 'Gagal mengambil data dealer'
], 500);
}
}
/**
* Get technician report data for DataTable
*/
public function getData(Request $request)
{
try {
$dealerId = $request->input('dealer_id');
$startDate = $request->input('start_date');
$endDate = $request->input('end_date');
Log::info('Requesting technician report data:', [
'dealer_id' => $dealerId,
'start_date' => $startDate,
'end_date' => $endDate
]);
$reportData = $this->technicianReportService->getTechnicianReportData(
$dealerId,
$startDate,
$endDate
);
Log::info('Technician report data response:', [
'data_count' => count($reportData['data']),
'mechanics_count' => $reportData['mechanics']->count(),
'works_count' => $reportData['works']->count(),
'mechanics' => $reportData['mechanics']->map(function($mechanic) {
return [
'id' => $mechanic->id,
'name' => $mechanic->name,
'role_id' => $mechanic->role_id
];
})
]);
return response()->json([
'status' => 'success',
'data' => $reportData['data'],
'mechanics' => $reportData['mechanics'],
'works' => $reportData['works']
]);
} catch (\Exception $e) {
Log::error('Error getting technician report data: ' . $e->getMessage(), [
'dealer_id' => $request->input('dealer_id'),
'start_date' => $request->input('start_date'),
'end_date' => $request->input('end_date'),
'trace' => $e->getTraceAsString()
]);
return response()->json([
'status' => 'error',
'message' => 'Gagal mengambil data laporan teknisi: ' . $e->getMessage()
], 500);
}
}
/**
* Get technician report data for Yajra DataTable
*/
public function getDataTable(Request $request)
{
try {
$dealerId = $request->input('dealer_id');
$startDate = $request->input('start_date');
$endDate = $request->input('end_date');
Log::info('Requesting technician report data for DataTable:', [
'dealer_id' => $dealerId,
'start_date' => $startDate,
'end_date' => $endDate
]);
$reportData = $this->technicianReportService->getTechnicianReportDataForDataTable(
$dealerId,
$startDate,
$endDate
);
return $reportData;
} catch (\Exception $e) {
Log::error('Error getting technician report data for DataTable: ' . $e->getMessage(), [
'dealer_id' => $request->input('dealer_id'),
'start_date' => $request->input('start_date'),
'end_date' => $request->input('end_date'),
'trace' => $e->getTraceAsString()
]);
return response()->json([
'error' => 'Gagal mengambil data laporan teknisi: ' . $e->getMessage()
], 500);
}
}
/**
* Export technician report to Excel
*/
public function export(Request $request)
{
try {
$dealerId = $request->input('dealer_id');
$startDate = $request->input('start_date');
$endDate = $request->input('end_date');
Log::info('Exporting technician report', [
'dealer_id' => $dealerId,
'start_date' => $startDate,
'end_date' => $endDate
]);
return Excel::download(new TechnicianReportExport($dealerId, $startDate, $endDate), 'laporan_teknisi_' . date('Y-m-d') . '.xlsx');
} catch (\Exception $e) {
Log::error('Error exporting technician report: ' . $e->getMessage(), [
'dealer_id' => $request->input('dealer_id'),
'start_date' => $request->input('start_date'),
'end_date' => $request->input('end_date')
]);
return response()->json([
'status' => 'error',
'message' => 'Gagal export laporan: ' . $e->getMessage()
], 500);
}
}
}

View File

@@ -0,0 +1,398 @@
<?php
namespace App\Services;
use App\Models\Work;
use App\Models\User;
use App\Models\Transaction;
use App\Models\Dealer;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class TechnicianReportService
{
/**
* Get technician report data for all works and mechanics on a specific date range
*/
public function getTechnicianReportData($dealerId = null, $startDate = null, $endDate = null)
{
try {
// Debug: Check all users and roles
$allUsers = User::with('role')->get();
Log::info('All users in database:', [
'total_users' => $allUsers->count(),
'users_with_roles' => $allUsers->map(function($user) {
$roleName = 'No role';
if ($user->role) {
$roleName = is_string($user->role) ? $user->role : $user->role->name;
}
return [
'id' => $user->id,
'name' => $user->name,
'role_id' => $user->role_id,
'role_name' => $roleName,
'dealer_id' => $user->dealer_id
];
})
]);
// Get all works with category in single query
$works = Work::with(['category'])
->orderBy('name')
->get();
// Get all mechanics (users with role name = 'mechanic')
$mechanics = User::with('role')->whereHas('role', function($query) {
$query->where('name', 'mechanic');
})
->when($dealerId, function($query) use ($dealerId) {
return $query->where('dealer_id', $dealerId);
})
->orderBy('name')
->get(['id', 'name', 'role_id', 'dealer_id']);
// Fallback: If no mechanics found, get all users with dealer_id
if ($mechanics->isEmpty()) {
Log::info('No users with role "mechanic" found, using fallback: all users with dealer_id');
$mechanics = User::with('role')->whereNotNull('dealer_id')
->whereNotNull('role_id')
->when($dealerId, function($query) use ($dealerId) {
return $query->where('dealer_id', $dealerId);
})
->orderBy('name')
->get(['id', 'name', 'role_id', 'dealer_id']);
}
Log::info('Mechanics found:', [
'count' => $mechanics->count(),
'dealer_id_filter' => $dealerId,
'mechanics' => $mechanics->map(function($mechanic) {
$roleName = 'Unknown';
if ($mechanic->role) {
$roleName = is_string($mechanic->role) ? $mechanic->role : $mechanic->role->name;
}
return [
'id' => $mechanic->id,
'name' => $mechanic->name,
'role_id' => $mechanic->role_id,
'role_name' => $roleName,
'dealer_id' => $mechanic->dealer_id
];
})
]);
// Get all transaction data in single optimized query
$transactions = $this->getOptimizedTransactionData($dealerId, $startDate, $endDate, $mechanics->pluck('id'), $works->pluck('id'));
Log::info('Transaction data:', [
'transaction_count' => count($transactions),
'sample_transactions' => array_slice($transactions, 0, 5, true)
]);
$data = [];
foreach ($works as $work) {
$row = [
'work_id' => $work->id,
'work_name' => $work->name,
'work_code' => $work->shortname,
'category_name' => $work->category ? $work->category->name : '-',
'total_tickets' => 0
];
// Calculate totals for each mechanic
foreach ($mechanics as $mechanic) {
$key = $work->id . '_' . $mechanic->id;
$mechanicData = $transactions[$key] ?? ['total' => 0, 'completed' => 0, 'pending' => 0];
$row["mechanic_{$mechanic->id}_total"] = $mechanicData['total'];
// Add to totals
$row['total_tickets'] += $mechanicData['total'];
}
$data[] = $row;
}
Log::info('Final data prepared:', [
'data_count' => count($data),
'sample_data' => array_slice($data, 0, 2)
]);
return [
'data' => $data,
'mechanics' => $mechanics,
'works' => $works
];
} catch (\Exception $e) {
Log::error('Error in getTechnicianReportData: ' . $e->getMessage(), [
'dealer_id' => $dealerId,
'start_date' => $startDate,
'end_date' => $endDate,
'trace' => $e->getTraceAsString()
]);
// Return empty data structure but with proper format
return [
'data' => [],
'mechanics' => collect(),
'works' => collect()
];
}
}
/**
* Get optimized transaction data in single query
*/
private function getOptimizedTransactionData($dealerId = null, $startDate = null, $endDate = null, $mechanicIds = null, $workIds = null)
{
$query = Transaction::select(
'work_id',
'user_id',
'status',
DB::raw('COUNT(*) as count')
)
->groupBy('work_id', 'user_id', 'status');
if ($dealerId) {
$query->where('dealer_id', $dealerId);
}
if ($startDate) {
$query->where('date', '>=', $startDate);
}
if ($endDate) {
$query->where('date', '<=', $endDate);
}
if ($mechanicIds && $mechanicIds->count() > 0) {
$query->whereIn('user_id', $mechanicIds);
}
if ($workIds && $workIds->count() > 0) {
$query->whereIn('work_id', $workIds);
}
// Remove index hint that doesn't exist
$results = $query->get();
// Organize data by work_id_user_id key
$organizedData = [];
foreach ($results as $result) {
$key = $result->work_id . '_' . $result->user_id;
if (!isset($organizedData[$key])) {
$organizedData[$key] = [
'total' => 0,
'completed' => 0,
'pending' => 0
];
}
$organizedData[$key]['total'] += $result->count;
if ($result->status == 1) {
$organizedData[$key]['completed'] += $result->count;
} else {
$organizedData[$key]['pending'] += $result->count;
}
}
return $organizedData;
}
/**
* Get total ticket count for a specific work and mechanic (legacy method for backward compatibility)
*/
private function getTicketCount($workId, $mechanicId, $dealerId = null, $startDate = null, $endDate = null)
{
$query = Transaction::where('work_id', $workId)
->where('user_id', $mechanicId);
if ($dealerId) {
$query->where('dealer_id', $dealerId);
}
if ($startDate) {
$query->where('date', '>=', $startDate);
}
if ($endDate) {
$query->where('date', '<=', $endDate);
}
return $query->count();
}
/**
* Get completed ticket count for a specific work and mechanic (legacy method for backward compatibility)
*/
private function getCompletedTicketCount($workId, $mechanicId, $dealerId = null, $startDate = null, $endDate = null)
{
$query = Transaction::where('work_id', $workId)
->where('user_id', $mechanicId)
->where('status', 1); // Assuming status 1 is completed
if ($dealerId) {
$query->where('dealer_id', $dealerId);
}
if ($startDate) {
$query->where('date', '>=', $startDate);
}
if ($endDate) {
$query->where('date', '<=', $endDate);
}
return $query->count();
}
/**
* Get pending ticket count for a specific work and mechanic (legacy method for backward compatibility)
*/
private function getPendingTicketCount($workId, $mechanicId, $dealerId = null, $startDate = null, $endDate = null)
{
$query = Transaction::where('work_id', $workId)
->where('user_id', $mechanicId)
->where('status', 0); // Assuming status 0 is pending
if ($dealerId) {
$query->where('dealer_id', $dealerId);
}
if ($startDate) {
$query->where('date', '>=', $startDate);
}
if ($endDate) {
$query->where('date', '<=', $endDate);
}
return $query->count();
}
/**
* Get all dealers for filter
*/
public function getDealers()
{
return Dealer::orderBy('name')->get(['id', 'name', 'dealer_code']);
}
/**
* Get default dealer for filter (tidak perlu berbasis user)
*/
public function getDefaultDealer()
{
// Dealer pertama saja jika ada
return Dealer::orderBy('name')->first();
}
/**
* Get mechanics for a specific dealer
*/
public function getMechanicsByDealer($dealerId = null)
{
$query = User::with('role')->whereHas('role', function($query) {
$query->where('name', 'mechanic');
});
if ($dealerId) {
$query->where('dealer_id', $dealerId);
}
return $query->orderBy('name')->get(['id', 'name', 'dealer_id']);
}
/**
* Get technician report data for Yajra DataTable
*/
public function getTechnicianReportDataForDataTable($dealerId = null, $startDate = null, $endDate = null)
{
try {
// Get all works with category
$works = Work::with(['category'])
->orderBy('name')
->get();
// Get all mechanics
$mechanics = User::with('role')->whereHas('role', function($query) {
$query->where('name', 'mechanic');
})
->when($dealerId, function($query) use ($dealerId) {
return $query->where('dealer_id', $dealerId);
})
->orderBy('name')
->get(['id', 'name', 'role_id', 'dealer_id']);
// Fallback: If no mechanics found, get all users with dealer_id
if ($mechanics->isEmpty()) {
Log::info('No users with role "mechanic" found, using fallback: all users with dealer_id');
$mechanics = User::with('role')->whereNotNull('dealer_id')
->whereNotNull('role_id')
->when($dealerId, function($query) use ($dealerId) {
return $query->where('dealer_id', $dealerId);
})
->orderBy('name')
->get(['id', 'name', 'role_id', 'dealer_id']);
}
// Get transaction data
$transactions = $this->getOptimizedTransactionData($dealerId, $startDate, $endDate, $mechanics->pluck('id'), $works->pluck('id'));
$data = [];
foreach ($works as $work) {
$row = [
'DT_RowIndex' => count($data) + 1,
'work_name' => $work->name,
'work_code' => $work->shortname,
'category_name' => $work->category ? $work->category->name : '-'
];
// Add mechanic columns
foreach ($mechanics as $mechanic) {
$key = $work->id . '_' . $mechanic->id;
$mechanicData = $transactions[$key] ?? ['total' => 0, 'completed' => 0, 'pending' => 0];
$row["mechanic_{$mechanic->id}_total"] = $mechanicData['total'];
}
$data[] = $row;
}
// Create DataTable response
return response()->json([
'draw' => request()->input('draw', 1),
'recordsTotal' => count($data),
'recordsFiltered' => count($data),
'data' => $data,
'mechanics' => $mechanics,
'works' => $works
]);
} catch (\Exception $e) {
Log::error('Error in getTechnicianReportDataForDataTable: ' . $e->getMessage(), [
'dealer_id' => $dealerId,
'start_date' => $startDate,
'end_date' => $endDate,
'trace' => $e->getTraceAsString()
]);
return response()->json([
'draw' => request()->input('draw', 1),
'recordsTotal' => 0,
'recordsFiltered' => 0,
'data' => [],
'mechanics' => collect(),
'works' => collect()
]);
}
}
}

View File

@@ -42,6 +42,10 @@ class MenuSeeder extends Seeder
[
'name' => 'Stock Produk',
'link' => 'reports.stock-product.index'
],
[
'name' => 'Teknisi',
'link' => 'reports.technician.index'
]
];

View File

@@ -48,7 +48,6 @@
@if(Gate::check('view', $menus['user.index']) || Gate::check('view', $menus['roleprivileges.index']))
<div class="kt-menu__section" style="padding: 15px 20px; margin-top: 10px; margin-bottom: 5px;">
<div style="display: flex; align-items: center; color: #a7abc3; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">
{{-- <i class="fa fa-users" style="margin-right: 8px; font-size: 14px;"></i> --}}
<span>Manajemen Pengguna</span>
</div>
</div>
@@ -77,7 +76,6 @@
@if(Gate::check('view', $menus['work.index']) || Gate::check('view', $menus['category.index']) || Gate::check('view', $menus['dealer.index']))
<div class="kt-menu__section" style="padding: 15px 20px; margin-top: 10px; margin-bottom: 5px;">
<div style="display: flex; align-items: center; color: #a7abc3; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">
{{-- <i class="fa fa-exchange-alt" style="margin-right: 8px; font-size: 14px;"></i> --}}
<span>Manajemen Transaksi</span>
</div>
</div>
@@ -115,7 +113,6 @@
@if(Gate::check('view', $menus['products.index']) || Gate::check('view', $menus['product_categories.index']) || Gate::check('view', $menus['mutations.index']) || Gate::check('view', $menus['opnames.index']) || Gate::check('view', $menus['stock-audit.index']))
<div class="kt-menu__section" style="padding: 15px 20px; margin-top: 10px; margin-bottom: 5px;">
<div style="display: flex; align-items: center; color: #a7abc3; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">
{{-- <i class="fa fa-warehouse" style="margin-right: 8px; font-size: 14px;"></i> --}}
<span>Manajemen Gudang</span>
</div>
</div>
@@ -171,7 +168,6 @@
@if(Gate::check('view', $menus['report.transaction_sa']) || Gate::check('view', $menus['report.transaction']) || Gate::check('view', $menus['report.transaction_dealer']) || Gate::check('view', $menus['work.index']))
<div class="kt-menu__section" style="padding: 15px 20px; margin-top: 10px; margin-bottom: 5px;">
<div style="display: flex; align-items: center; color: #a7abc3; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">
{{-- <i class="fa fa-chart-bar" style="margin-right: 8px; font-size: 14px;"></i> --}}
<span>Laporan</span>
</div>
</div>
@@ -214,9 +210,9 @@
</li>
@endcan
@can('view', $menus['work.index'])
@can('view', $menus['reports.technician.index'])
<li class="kt-menu__item" aria-haspopup="true">
<a href="{{ route('work.index') }}" class="kt-menu__link">
<a href="{{ route('reports.technician.index') }}" class="kt-menu__link">
<i class="fa fa-user-cog" style="display: flex; align-items: center; margin-right: 10px;"></i>
<span class="kt-menu__link-text">Teknisi</span>
</a>
@@ -227,7 +223,6 @@
@if(Gate::check('view', $menus['kpi.targets.index']))
<div class="kt-menu__section" style="padding: 15px 20px; margin-top: 10px; margin-bottom: 5px;">
<div style="display: flex; align-items: center; color: #a7abc3; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">
{{-- <i class="fa fa-user-cog" style="margin-right: 8px; font-size: 14px;"></i> --}}
<span>KPI</span>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,7 @@ use App\Http\Controllers\WarehouseManagement\MutationsController;
use App\Http\Controllers\WarehouseManagement\StockAuditController;
use App\Http\Controllers\KPI\TargetsController;
use App\Http\Controllers\Reports\ReportStockProductsController;
use App\Http\Controllers\Reports\ReportTechniciansController;
use App\Models\Menu;
use App\Models\Privilege;
use App\Models\Role;
@@ -323,6 +324,13 @@ Route::group(['middleware' => 'auth'], function() {
Route::get('stock-products/data', [ReportStockProductsController::class, 'getData'])->name('reports.stock-product.data');
Route::get('stock-products/dealers', [ReportStockProductsController::class, 'getDealers'])->name('reports.stock-product.dealers');
Route::get('stock-products/export', [ReportStockProductsController::class, 'export'])->name('reports.stock-product.export');
Route::get('technician', [ReportTechniciansController::class, 'index'])->name('reports.technician.index');
Route::get('technician/data', [ReportTechniciansController::class, 'getData'])->name('reports.technician.data');
Route::get('technician/datatable', [ReportTechniciansController::class, 'getDataTable'])->name('reports.technician.datatable');
Route::get('technician/dealers', [ReportTechniciansController::class, 'getDealers'])->name('reports.technician.dealers');
Route::get('technician/export', [ReportTechniciansController::class, 'export'])->name('reports.technician.export');
});
});