create export product stock dealers

This commit is contained in:
2025-06-19 17:35:35 +07:00
parent 22477b6dab
commit e478dc81bb
4 changed files with 221 additions and 2 deletions

View File

@@ -0,0 +1,201 @@
<?php
namespace App\Exports;
use App\Models\Dealer;
use App\Models\Product;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
use Maatwebsite\Excel\Concerns\WithTitle;
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;
class ProductStockDealers implements WithMultipleSheets
{
public function sheets(): array
{
$sheets = [];
$usedNames = [];
// Get all dealers with their stock data
$dealers = Dealer::with(['stocks.product.category'])->get();
/** @var Dealer $dealer */
foreach ($dealers as $dealer) {
$dealerSheet = new DealerStockSheet($dealer);
$sheetTitle = $dealerSheet->title();
// Handle duplicate sheet names
$originalTitle = $sheetTitle;
$counter = 1;
while (in_array($sheetTitle, $usedNames)) {
$sheetTitle = substr($originalTitle, 0, 28) . '_' . $counter;
$counter++;
}
$usedNames[] = $sheetTitle;
// Set the unique title
$dealerSheet->setUniqueTitle($sheetTitle);
$sheets[] = $dealerSheet;
}
return $sheets;
}
}
class DealerStockSheet implements FromCollection, WithTitle, WithHeadings, WithStyles, WithColumnWidths
{
protected $dealer;
protected $uniqueTitle;
public function __construct(Dealer $dealer)
{
$this->dealer = $dealer;
}
public function collection()
{
// Get all products with stock for this dealer
$stocks = $this->dealer->stocks()
->with(['product.category'])
->whereHas('product', function($query) {
$query->where('active', true);
})
->get();
$data = collect();
$no = 1;
foreach ($stocks as $stock) {
$product = $stock->product;
$data->push([
'no' => $no++,
'kode_produk' => $product->code,
'nama_produk' => $product->name,
'kategori' => $product->category ? $product->category->name : '-',
'satuan' => $product->unit ?? '-',
'stok' => number_format($stock->quantity, 2)
]);
}
// If no stock, add empty row
if ($data->isEmpty()) {
$data->push([
'no' => '-',
'kode_produk' => '-',
'nama_produk' => 'Tidak ada stok produk',
'kategori' => '-',
'satuan' => '-',
'stok' => '0'
]);
}
return $data;
}
public function setUniqueTitle(string $title): void
{
$this->uniqueTitle = $title;
}
public function title(): string
{
if (isset($this->uniqueTitle)) {
return $this->uniqueTitle;
}
// Clean dealer name for sheet title (remove invalid characters and handle edge cases)
$cleanName = $this->dealer->name;
// Remove parentheses and their contents
$cleanName = preg_replace('/\([^)]*\)/', '', $cleanName);
// Remove dots, commas, and other special characters
$cleanName = preg_replace('/[^A-Za-z0-9\-_ ]/', '', $cleanName);
// Clean up multiple spaces and trim
$cleanName = preg_replace('/\s+/', ' ', trim($cleanName));
// If name is empty after cleaning, use dealer ID
if (empty($cleanName)) {
$cleanName = 'Dealer_' . $this->dealer->id;
}
// Limit to 31 characters and ensure no leading/trailing spaces
$cleanName = trim(substr($cleanName, 0, 31));
// Ensure it doesn't end with a space (which can cause Excel issues)
return rtrim($cleanName);
}
public function headings(): array
{
return [
'No',
'Kode Produk',
'Nama Produk',
'Kategori',
'Satuan',
'Stok'
];
}
public function styles(Worksheet $sheet)
{
// Add dealer info at the top first
$sheet->insertNewRowBefore(1, 2);
$sheet->setCellValue('A1', 'STOK PRODUK DEALER: ' . strtoupper($this->dealer->name));
$sheet->setCellValue('A2', 'Tanggal Export: ' . now()->format('d/m/Y H:i:s'));
// Merge cells for dealer info
$sheet->mergeCells('A1:F1');
$sheet->mergeCells('A2:F2');
$lastRow = $sheet->getHighestRow();
// Style dealer info
$sheet->getStyle('A1:A2')->applyFromArray([
'font' => ['bold' => true, 'size' => 12],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER]
]);
// Style headers (row 3 after inserting 2 rows)
$sheet->getStyle('A3:F3')->applyFromArray([
'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '4472C4']],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]]
]);
// Style data rows if they exist
if ($lastRow > 3) {
$sheet->getStyle('A4:F' . $lastRow)->applyFromArray([
'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'CCCCCC']]],
'alignment' => ['vertical' => Alignment::VERTICAL_CENTER]
]);
// Center align specific columns
$sheet->getStyle('A4:A' . $lastRow)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getStyle('E4:F' . $lastRow)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
}
return $sheet;
}
public function columnWidths(): array
{
return [
'A' => 8, // No
'B' => 15, // Kode Produk
'C' => 30, // Nama Produk
'D' => 20, // Kategori
'E' => 12, // Satuan
'F' => 12 // Stok
];
}
}

View File

@@ -7,6 +7,7 @@ use App\Models\Dealer;
use App\Models\Menu;
use App\Models\Product;
use App\Models\ProductCategory;
use App\Exports\ProductStockDealers;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@@ -15,6 +16,7 @@ use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\DB;
use Yajra\DataTables\Facades\DataTables;
use Illuminate\Validation\Rule;
use Maatwebsite\Excel\Facades\Excel;
class ProductsController extends Controller
{
@@ -270,4 +272,16 @@ class ProductsController extends Controller
return DataTables::of($data)->make(true);
}
public function exportDealersStock()
{
try {
$fileName = 'stok_produk_dealers_' . date('Y-m-d_H-i-s') . '.xlsx';
return Excel::download(new ProductStockDealers(), $fileName);
} catch (\Exception $e) {
Log::error('Export dealers stock error: ' . $e->getMessage());
return back()->with('error', 'Gagal mengexport data. Silakan coba lagi.');
}
}
}

View File

@@ -81,15 +81,18 @@ table.dataTable thead th.sorting:hover:before {
Tabel Produk
</h3>
</div>
@can('create', $menus['products.index'])
<div class="kt-portlet__head-toolbar">
<div class="kt-portlet__head-wrapper">
<div class="kt-portlet__head-actions">
<a href="{{ route('products.export.dealers_stock') }}" class="btn btn-bold btn-success btn--sm" style="margin-right: 8px;">
<i class="flaticon2-download"></i>Export Stok Dealer
</a>
@can('create', $menus['products.index'])
<a href="{{ route('products.create') }}" class="btn btn-bold btn-label-brand btn--sm">Tambah</a>
@endcan
</div>
</div>
</div>
@endcan
</div>
<div class="kt-portlet__body">

View File

@@ -215,6 +215,7 @@ Route::group(['middleware' => 'auth'], function() {
Route::post('/', 'store')->name('products.store');
Route::get('all','all_products')->name('products.all');
Route::get('dealers-stock', 'dealers_stock')->name('products.dealers_stock');
Route::get('export/dealers-stock', 'exportDealersStock')->name('products.export.dealers_stock');
Route::get('{product}', 'show')->name('products.show');
Route::get('{product}/edit', 'edit')->name('products.edit');
Route::put('{product}', 'update')->name('products.update');