diff --git a/app/Filament/Widgets/InvoiceChart.php b/app/Filament/Widgets/InvoiceChart.php index 8322c1853..5bafbcab1 100644 --- a/app/Filament/Widgets/InvoiceChart.php +++ b/app/Filament/Widgets/InvoiceChart.php @@ -5,6 +5,7 @@ namespace App\Filament\Widgets; use App\Models\InvoiceValidation; use App\Models\Line; use App\Models\Plant; +use DB; use Filament\Widgets\ChartWidget; class InvoiceChart extends ChartWidget @@ -15,113 +16,215 @@ class InvoiceChart extends ChartWidget protected $listeners = ['invoiceChart']; + protected function getData(): array { - $selectedPlant =session('selec_plant'); - + $selectedPlant = session('selec_plant'); $selectedInvoice = session('select_invoice'); - $activeFilter = $this->filter; if (!$selectedPlant || !$selectedInvoice) { - return - [ + return [ 'datasets' => [], 'labels' => [], ]; } if ($activeFilter === 'yesterday') { - $startDate = now()->subDay()->setTime(8, 0, 0); // yesterday 8:00 AM - $endDate = now()->setTime(8, 0, 0); // today 8:00 AM - $groupBy = 'EXTRACT(HOUR FROM invoice_validations.created_at)'; - } - else if ($activeFilter === 'this_week') { - // Monday 8:00 AM of the current week + $startDate = now()->subDay()->setTime(8, 0, 0); + $endDate = now()->setTime(8, 0, 0); + $groupBy = 'hour'; + } elseif ($activeFilter === 'this_week') { $startDate = now()->startOfWeek()->setTime(8, 0, 0); - - // Next Monday 8:00 AM (end of Sunday + 8 hrs) $endDate = now()->endOfWeek()->addDay()->setTime(8, 0, 0); - - $groupBy = 'EXTRACT(DOW FROM invoice_validations.created_at)'; // Group by day of week - } - else if ($activeFilter === 'this_month') { + $groupBy = 'day_of_week'; + } elseif ($activeFilter === 'this_month') { $startDate = now()->startOfMonth(); $endDate = now()->endOfMonth(); - $groupBy = "FLOOR((EXTRACT(DAY FROM invoice_validations.created_at) - 1) / 7) + 1"; - } - else - { - $startDate = now()->setTime(8, 0, 0); // today at 8:00 AM - $endDate = now()->copy()->addDay()->setTime(8, 0, 0); // tomorrow at 8:00 AM - $groupBy = 'EXTRACT(HOUR FROM invoice_validations.created_at)'; + $groupBy = 'week_of_month'; + } else { + $startDate = now()->setTime(8, 0, 0); + $endDate = now()->copy()->addDay()->setTime(8, 0, 0); + $groupBy = 'hour'; } - // $fullyScannedInvoiceNumbers = \DB::table('invoice_validations') - // ->select('invoice_number') - // ->where('plant_id', $selectedPlant) - // ->groupBy('invoice_number') - // ->havingRaw("SUM(CASE WHEN scanned_status = 'Scanned' THEN 1 ELSE 0 END) = COUNT(*)") - // ->pluck('invoice_number'); + // Get fully scanned invoices + if ($selectedInvoice === 'individual_material') { + + $allInvoiceNumbers = \DB::table('invoice_validations') + ->select('invoice_number') + ->where('plant_id', $selectedPlant) + ->where('quantity', 1) + ->groupBy('invoice_number') + ->pluck('invoice_number'); - if ($selectedInvoice === 'individual_material') - { $fullyScannedInvoiceNumbers = \DB::table('invoice_validations') ->select('invoice_number') ->where('plant_id', $selectedPlant) - ->havingRaw('MIN(quantity) = 1 AND MAX(quantity) = 1') // All rows must have quantity = 1 + ->where('quantity', 1) ->groupBy('invoice_number') ->havingRaw("SUM(CASE WHEN serial_number IS NOT NULL AND serial_number != '' THEN 1 ELSE 0 END) = COUNT(*)") ->pluck('invoice_number'); - } - else if ($selectedInvoice === 'bundle_material') - { - $fullyScannedInvoiceNumbers = \DB::table('invoice_validations') - ->select('invoice_number') - ->where('plant_id', $selectedPlant) - ->havingRaw('MIN(quantity) > 1') - ->groupBy('invoice_number') - ->havingRaw("SUM(CASE WHEN serial_number IS NOT NULL AND serial_number != '' THEN 1 ELSE 0 END) = COUNT(*)") - ->pluck('invoice_number'); - } - else - { + } //elseif ($selectedInvoice === 'bundle_material') { + + // $allInvoiceNumbers = \DB::table('invoice_validations') + // ->select('invoice_number') + // ->where('plant_id', $selectedPlant) + // ->havingRaw('MIN(quantity) > 1') + // ->groupBy('invoice_number') + // ->pluck('invoice_number'); + + // $fullyScannedInvoiceNumbers = \DB::table('invoice_validations') + // ->select('invoice_number') + // ->where('plant_id', $selectedPlant) + // ->havingRaw('MIN(quantity) > 1') + // ->groupBy('invoice_number') + // ->havingRaw("SUM(CASE WHEN serial_number IS NOT NULL AND serial_number != '' THEN 1 ELSE 0 END) = COUNT(*)") + // ->pluck('invoice_number'); + // } + else if($selectedInvoice === 'serial_invoice') { + $allInvoiceNumbers = \DB::table('invoice_validations') + ->select('invoice_number') + ->where('plant_id', $selectedPlant) + ->whereNull('quantity') + ->pluck('invoice_number'); + $fullyScannedInvoiceNumbers = \DB::table('invoice_validations') ->select('invoice_number') ->where('plant_id', $selectedPlant) + ->whereNull('quantity') ->groupBy('invoice_number') ->havingRaw("SUM(CASE WHEN scanned_status = 'Scanned' THEN 1 ELSE 0 END) = COUNT(*)") ->pluck('invoice_number'); } + if ($selectedInvoice === 'bundle_material') { + //Get all invoice numbers that contain bundle materials + $allInvoiceNumbers = DB::table('invoice_validations') + ->select('invoice_number') + ->where('plant_id', $selectedPlant) + ->whereIn('invoice_number', function ($query) use ($selectedPlant) { + $query->select('invoice_number') + ->from('invoice_validations') + ->where('plant_id', $selectedPlant) + ->where('quantity', '>', 1); + }) + ->groupBy('invoice_number') + ->pluck('invoice_number'); - //query data - $query = \DB::table('invoice_validations') - ->join('plants', 'invoice_validations.plant_id', '=', 'plants.id') - ->selectRaw("$groupBy AS time_unit, count(distinct invoice_validations.invoice_number) AS total_quantity") - ->whereBetween('invoice_validations.created_at', [$startDate, $endDate]) - ->where('invoice_validations.plant_id', $selectedPlant) - ->whereIn('invoice_validations.invoice_number', $fullyScannedInvoiceNumbers) - ->when($selectedInvoice === 'serial_invoice', fn($q) => - $q->whereNotNull('invoice_validations.serial_number') - ->where('invoice_validations.serial_number', '!=', '') - ) - ->when($selectedInvoice === 'individual_material', fn($q) => - $q->where('invoice_validations.quantity', 1) // Only individual items - ) - ->when($selectedInvoice === 'bundle_material', fn($q) => - $q->where('invoice_validations.quantity', '>', 1) // Assume bundles have quantity > 1 - ) - ->groupByRaw($groupBy) - ->orderByRaw($groupBy) - ->pluck('total_quantity', 'time_unit') - ->toArray(); - if ($activeFilter === 'this_month') { - $weeksCount = ceil($endDate->day / 7); // Calculate total weeks dynamically - $allWeeks = array_fill(1, $weeksCount, 0); // Initialize all weeks with 0 - $data = array_replace($allWeeks, $query); // Fill missing weeks with 0 + //Get fully scanned bundle invoices + $fullyScannedBundle = DB::table('invoice_validations') + ->select('invoice_number') + ->where('plant_id', $selectedPlant) + ->where('quantity', '>', 1) + ->groupBy('invoice_number') + ->havingRaw("SUM(CASE WHEN serial_number IS NOT NULL AND serial_number != '' THEN 1 ELSE 0 END) = COUNT(*)") + ->pluck('invoice_number'); - // Generate dynamic week labels + //Get individual invoice numbers + $individualInvoices = DB::table('invoice_validations') + ->select('invoice_number') + ->where('plant_id', $selectedPlant) + ->where('quantity', 1) + ->groupBy('invoice_number') + ->pluck('invoice_number'); + + //Get fully scanned individual invoices + $fullyScannedIndividual = DB::table('invoice_validations') + ->select('invoice_number') + ->where('plant_id', $selectedPlant) + ->where('quantity', 1) + ->groupBy('invoice_number') + ->havingRaw("SUM(CASE WHEN serial_number IS NOT NULL AND serial_number != '' THEN 1 ELSE 0 END) = COUNT(*)") + ->pluck('invoice_number'); + + //Find common invoices between bundle and individual + $commonInvoices = $allInvoiceNumbers->intersect($individualInvoices); + + //Identify incomplete common invoices + $notScannedInvoicesForBundle = $commonInvoices->filter(function ($invoice) use ($fullyScannedIndividual, $fullyScannedBundle) { + return !($fullyScannedIndividual->contains($invoice) && $fullyScannedBundle->contains($invoice)); + }); + + //Final fully scanned = only those that are scanned in both individual & bundle + $fullyScannedInvoiceNumbers = $fullyScannedBundle->filter(function ($invoice) use ($fullyScannedIndividual) { + return $fullyScannedIndividual->contains($invoice); + }); + + //Exclude invoices that should be marked as "not scanned" + $fullyScannedInvoiceNumbers = $fullyScannedInvoiceNumbers->diff($notScannedInvoicesForBundle); + + //Calculate not scanned invoices = all - fully scanned + $notScannedInvoiceNumbers = $allInvoiceNumbers->diff($fullyScannedInvoiceNumbers); + } + + // Not scanned = all - scanned + $notScannedInvoiceNumbers = $allInvoiceNumbers->diff($fullyScannedInvoiceNumbers); + + //Get timestamps + $scannedTimestamps = \DB::table('invoice_validations') + ->selectRaw("MAX(updated_at) AS final_time, invoice_number") + ->where('plant_id', $selectedPlant) + ->whereIn('invoice_number', $fullyScannedInvoiceNumbers) + ->whereBetween('updated_at', [$startDate, $endDate]) + ->groupBy('invoice_number') + ->pluck('final_time', 'invoice_number'); + + $notScannedTimestamps = \DB::table('invoice_validations') + ->selectRaw("MIN(created_at) AS first_time, invoice_number") + ->where('plant_id', $selectedPlant) + ->whereIn('invoice_number', $notScannedInvoiceNumbers) + ->whereBetween('created_at', [$startDate, $endDate]) + ->groupBy('invoice_number') + ->pluck('first_time', 'invoice_number'); + + // Group timestamps + $scannedGrouped = []; + $notScannedGrouped = []; + + foreach ($scannedTimestamps as $timestamp) { + $time = \Carbon\Carbon::parse($timestamp); + if ($time->lt($startDate) || $time->gt($endDate)) continue; + + $key = match ($groupBy) { + 'hour' => (int) $time->format('G'), + 'day_of_week' => (int) $time->dayOfWeek, + 'week_of_month' => (int) ceil($time->day / 7), + }; + + $scannedGrouped[$key] = ($scannedGrouped[$key] ?? 0) + 1; + } + + foreach ($notScannedTimestamps as $timestamp) { + $time = \Carbon\Carbon::parse($timestamp); + if ($time->lt($startDate) || $time->gt($endDate)) continue; + + $key = match ($groupBy) { + 'hour' => (int) $time->format('G'), + 'day_of_week' => (int) $time->dayOfWeek, + 'week_of_month' => (int) ceil($time->day / 7), + }; + + $notScannedGrouped[$key] = ($notScannedGrouped[$key] ?? 0) + 1; + } + + // Build x-axis labels and data arrays + if ($groupBy === 'hour') { + $shiftedKeys = array_merge(range(8, 23), range(0, 7)); + $labels = array_map(fn($h) => date("g A", strtotime("$h:00")), $shiftedKeys); + $scannedData = array_map(fn($h) => $scannedGrouped[$h] ?? 0, $shiftedKeys); + $notScannedData = array_map(fn($h) => $notScannedGrouped[$h] ?? 0, $shiftedKeys); + } elseif ($groupBy === 'day_of_week') { + $labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + $scannedData = array_fill(0, 7, 0); + $notScannedData = array_fill(0, 7, 0); + foreach ($scannedGrouped as $k => $v) $scannedData[$k] = $v; + foreach ($notScannedGrouped as $k => $v) $notScannedData[$k] = $v; + // Shift so Monday starts first + $scannedData = [$scannedData[1], $scannedData[2], $scannedData[3], $scannedData[4], $scannedData[5], $scannedData[6], $scannedData[0]]; + $notScannedData = [$notScannedData[1], $notScannedData[2], $notScannedData[3], $notScannedData[4], $notScannedData[5], $notScannedData[6], $notScannedData[0]]; + } else { + $weeksCount = ceil($endDate->day / 7); $labels = []; for ($i = 1; $i <= $weeksCount; $i++) { $weekStart = $startDate->copy()->addDays(($i - 1) * 7)->format('d M'); @@ -129,56 +232,29 @@ class InvoiceChart extends ChartWidget $labels[] = "Week $i ($weekStart - $weekEnd)"; } - $orderedData = array_values($data); + $scannedData = array_fill(1, $weeksCount, 0); + $notScannedData = array_fill(1, $weeksCount, 0); + foreach ($scannedGrouped as $k => $v) $scannedData[$k] = $v; + foreach ($notScannedGrouped as $k => $v) $notScannedData[$k] = $v; + $scannedData = array_values($scannedData); + $notScannedData = array_values($notScannedData); } - else if ($activeFilter === 'this_week') { - // Correct week labels: ['Mon', 'Tue', ..., 'Sun'] - $labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; - // Initialize default data for all 7 days - $data = array_fill(0, 7, 0); - - // Fill in data from query results - foreach ($query as $dow => $count) { - $data[$dow] = $count; - } - - // Ensure days are ordered from Monday to Sunday - $orderedData = [ - $data[1] ?? 0, // Monday - $data[2] ?? 0, // Tuesday - $data[3] ?? 0, // Wednesday - $data[4] ?? 0, // Thursday - $data[5] ?? 0, // Friday - $data[6] ?? 0, // Saturday - $data[0] ?? 0, // Sunday (move to last) - ]; - } - else - { - // Hourly data (same as before) - $allHours = array_fill(0, 24, 0); - $data = array_replace($allHours, $query); - - // Shift hours for proper display (8 AM to 7 AM) - $shiftedKeys = array_merge(range(8, 23), range(0, 8)); - $orderedData = array_map(fn($hour) => $data[$hour], $shiftedKeys); - - // Labels: ["8 AM", "9 AM", ..., "7 AM"] - $labels = array_map(fn ($hour) => date("g A", strtotime("$hour:00")), $shiftedKeys); - } return [ 'datasets' => [ [ - 'label' => match ($activeFilter) { - 'this_week' => "Daily imported excel file count", - 'this_month' => "Weekly imported excel file count", // Updated Label - 'yesterday' => "Yesterday's imported excel file count", - default => "Today's imported excel file count", - }, - 'data' => $orderedData, + 'label' => 'Scanned Invoices', + 'data' => $scannedData, 'borderColor' => 'rgba(75, 192, 192, 1)', - 'backgroundColor' => 'rgba(75, 192, 192, 0.2)', + 'backgroundColor' => 'rgba(23, 211, 80, 0.7)', + 'fill' => false, + 'tension' => 0.3, + ], + [ + 'label' => 'Not Scanned Invoices', + 'data' => $notScannedData, + 'borderColor' => 'rgba(255, 99, 132, 1)', + 'backgroundColor' => 'rgba(214, 105, 32, 0.9)', 'fill' => false, 'tension' => 0.3, ], @@ -187,6 +263,8 @@ class InvoiceChart extends ChartWidget ]; } + + protected function getType(): string { return 'bar';