fix feature report stock product

This commit is contained in:
2025-07-08 14:24:01 +07:00
parent 956df5cfe6
commit cfef3775d7
11 changed files with 1196 additions and 16 deletions

View File

@@ -0,0 +1,316 @@
<?php
namespace App\Exports;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\WithColumnWidths;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Collection;
class StockProductsExport implements FromCollection, WithHeadings, WithStyles, WithColumnWidths
{
protected $reportData;
public function __construct($reportData)
{
// Validate and sanitize report data
if (!is_array($reportData)) {
throw new \InvalidArgumentException('Report data must be an array');
}
if (!isset($reportData['data']) || !isset($reportData['dealers'])) {
throw new \InvalidArgumentException('Report data must contain "data" and "dealers" keys');
}
// Ensure dealers is a collection
if (!($reportData['dealers'] instanceof Collection)) {
$reportData['dealers'] = collect($reportData['dealers']);
}
// Ensure data is an array
if (!is_array($reportData['data'])) {
$reportData['data'] = [];
}
$this->reportData = $reportData;
// Debug: Log the structure of report data
Log::info('StockProductsExport constructor', [
'has_data' => isset($reportData['data']),
'has_dealers' => isset($reportData['dealers']),
'data_count' => isset($reportData['data']) ? count($reportData['data']) : 0,
'dealers_count' => isset($reportData['dealers']) ? count($reportData['dealers']) : 0,
'dealers' => isset($reportData['dealers']) ? $reportData['dealers']->pluck('name')->toArray() : []
]);
}
public function collection()
{
try {
$data = collect();
$no = 1;
foreach ($this->reportData['data'] as $row) {
$exportRow = [
'no' => $no++,
'kode_produk' => $row['product_code'] ?? '',
'nama_produk' => $row['product_name'] ?? '',
'kategori' => $row['category_name'] ?? '',
'satuan' => $row['unit'] ?? ''
];
// Add dealer columns
foreach ($this->reportData['dealers'] as $dealer) {
try {
$dealerKey = "dealer_{$dealer->id}";
// Clean dealer name for array key to avoid special characters
$cleanDealerName = $this->cleanDealerName($dealer->name);
$exportRow[$cleanDealerName] = $row[$dealerKey] ?? 0;
Log::info('Processing dealer column', [
'original_name' => $dealer->name,
'clean_name' => $cleanDealerName,
'dealer_key' => $dealerKey,
'value' => $row[$dealerKey] ?? 0
]);
} catch (\Exception $e) {
Log::error('Error processing dealer column', [
'dealer_id' => $dealer->id,
'dealer_name' => $dealer->name,
'error' => $e->getMessage()
]);
// Use a safe fallback name
$exportRow['Dealer_' . $dealer->id] = $row["dealer_{$dealer->id}"] ?? 0;
}
}
// Add total stock
$exportRow['total_stok'] = $row['total_stock'] ?? 0;
$data->push($exportRow);
}
return $data;
} catch (\Exception $e) {
Log::error('Error in collection method', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
// Return empty collection as fallback
return collect();
}
}
public function headings(): array
{
try {
$headings = [
'No',
'Kode Produk',
'Nama Produk',
'Kategori',
'Satuan'
];
// Add dealer headings
foreach ($this->reportData['dealers'] as $dealer) {
try {
$cleanName = $this->cleanDealerName($dealer->name);
$headings[] = $cleanName;
Log::info('Processing dealer heading', [
'original_name' => $dealer->name,
'clean_name' => $cleanName
]);
} catch (\Exception $e) {
Log::error('Error processing dealer heading', [
'dealer_id' => $dealer->id,
'dealer_name' => $dealer->name,
'error' => $e->getMessage()
]);
// Use a safe fallback name
$headings[] = 'Dealer_' . $dealer->id;
}
}
// Add total heading
$headings[] = 'Total Stok';
return $headings;
} catch (\Exception $e) {
Log::error('Error in headings method', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
// Return basic headings as fallback
return ['No', 'Kode Produk', 'Nama Produk', 'Kategori', 'Satuan', 'Total Stok'];
}
}
public function styles(Worksheet $sheet)
{
try {
$lastColumn = $sheet->getHighestColumn();
$lastRow = $sheet->getHighestRow();
// Validate column and row values
if (!$lastColumn || !$lastRow || $lastRow < 1) {
Log::warning('Invalid sheet dimensions', ['lastColumn' => $lastColumn, 'lastRow' => $lastRow]);
return $sheet;
}
// Style header row
$sheet->getStyle('A1:' . $lastColumn . '1')->applyFromArray([
'font' => [
'bold' => true,
'color' => ['rgb' => 'FFFFFF'],
],
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['rgb' => '4472C4'],
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
'vertical' => Alignment::VERTICAL_CENTER,
],
]);
// Style all cells
$sheet->getStyle('A1:' . $lastColumn . $lastRow)->applyFromArray([
'borders' => [
'allBorders' => [
'borderStyle' => Border::BORDER_THIN,
'color' => ['rgb' => '000000'],
],
],
'alignment' => [
'vertical' => Alignment::VERTICAL_CENTER,
],
]);
// Center align specific columns
$sheet->getStyle('A:A')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getStyle('D:D')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
// Right align numeric columns (dealer columns and total)
$dealerStartCol = 'E';
$dealerCount = count($this->reportData['dealers']);
if ($dealerCount > 0) {
$dealerEndCol = chr(ord('E') + $dealerCount - 1);
$totalCol = chr(ord($dealerStartCol) + $dealerCount);
// Validate column letters
if (ord($dealerEndCol) <= ord('Z') && ord($totalCol) <= ord('Z')) {
$sheet->getStyle($dealerStartCol . ':' . $dealerEndCol)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
$sheet->getStyle($totalCol . ':' . $totalCol)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
// Bold total column
$sheet->getStyle($totalCol . '1:' . $totalCol . $lastRow)->getFont()->setBold(true);
}
}
// Auto-size columns safely
foreach (range('A', $lastColumn) as $column) {
try {
$sheet->getColumnDimension($column)->setAutoSize(true);
} catch (\Exception $e) {
Log::warning('Failed to auto-size column', ['column' => $column, 'error' => $e->getMessage()]);
}
}
return $sheet;
} catch (\Exception $e) {
Log::error('Error in styles method', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return $sheet;
}
}
public function columnWidths(): array
{
try {
$widths = [
'A' => 8, // No
'B' => 15, // Kode Produk
'C' => 30, // Nama Produk
'D' => 15, // Kategori
'E' => 10, // Satuan
];
// Add dealer column widths safely
$currentCol = 'F';
$dealerCount = count($this->reportData['dealers']);
for ($i = 0; $i < $dealerCount; $i++) {
// Validate column letter
if (ord($currentCol) <= ord('Z')) {
$widths[$currentCol] = 15;
$currentCol = chr(ord($currentCol) + 1);
} else {
Log::warning('Too many dealer columns, stopping at column Z');
break;
}
}
// Add total column width if we haven't exceeded Z
if (ord($currentCol) <= ord('Z')) {
$widths[$currentCol] = 15;
}
return $widths;
} catch (\Exception $e) {
Log::error('Error in columnWidths method', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
// Return basic widths as fallback
return [
'A' => 8, // No
'B' => 15, // Kode Produk
'C' => 30, // Nama Produk
'D' => 15, // Kategori
'E' => 10, // Satuan
'F' => 15 // Total Stok
];
}
}
/**
* Clean dealer name to make it safe for array keys and Excel headers
*/
private function cleanDealerName($dealerName)
{
// Remove or replace special characters that can cause issues with Excel
$cleanName = preg_replace('/[^a-zA-Z0-9\s\-_]/', '', $dealerName);
$cleanName = trim($cleanName);
// If name becomes empty, use a default
if (empty($cleanName)) {
$cleanName = 'Dealer';
}
// Limit length to avoid Excel issues
if (strlen($cleanName) > 31) {
$cleanName = substr($cleanName, 0, 31);
}
return $cleanName;
}
}