Files
xin-procurement/app/Exports/PurchaseSupplierSheet.php
T
2026-09-05 14:03:23 +08:00

197 lines
7.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Exports;
use App\Models\PurchaseOrderModel;
use App\Models\SupplierModel;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithStrictNullComparison;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\WithTitle;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
/**
* 供应商采购明细导出 · 单市场工作表
* 模板列序:品名、汇总、市场、各门店明细(门店列=该供应商有明细的门店);
* 汇总列(浅黄)/门店数量列(浅蓝)按值条件填色(0 不填、列头固定填色)
*/
class PurchaseSupplierSheet implements FromCollection, WithStrictNullComparison, WithStyles, WithTitle
{
/** 汇总列填充色(浅黄) */
private const string QUANTITY_FILL = 'FFFFF2CC';
/** 门店数量列填充色(浅蓝) */
private const string STORE_FILL = 'FFDDEBF7';
private ?Collection $rows = null;
/** @var array<int, string> 特殊行索引(1 起)=> 类型(title/header */
private array $specialRows = [];
/** 列头所在行索引 */
private int $headerRow = 1;
/** 数据末行索引 */
private int $lastRow = 1;
/** @var array<int, array{quantity: int, store_quantities: array<int, int>}> 明细行索引 => 填色判断数据 */
private array $rowData = [];
/**
* @param PurchaseOrderModel $purchase 采购单
* @param SupplierModel $supplier 供应商
* @param string $marketLabel 市场名(空市场已转为「未设置」)
* @param array<int, array<string, mixed>> $productRows 商品行(品名/汇总数量/各门店数量)
* @param array<int, string> $stores 门店列(门店ID => 名称)
* @param string $sheetTitle 工作表名
*/
public function __construct(
private readonly PurchaseOrderModel $purchase,
private readonly SupplierModel $supplier,
private readonly string $marketLabel,
private readonly array $productRows,
private readonly array $stores,
private readonly string $sheetTitle,
) {
}
/**
* 导出行:标题/空行/列头(品名、汇总、市场、各门店)/明细
*/
public function collection(): Collection
{
if ($this->rows !== null) {
return $this->rows;
}
$storeIds = array_map('intval', array_keys($this->stores));
$rows = [];
$rowIndex = 0;
// 标题行
$rows[] = [$this->supplier->name . ' · ' . $this->marketLabel . ' · 采购单 ' . $this->purchase->purchase_no];
$this->specialRows[++$rowIndex] = 'title';
// 空行
$rows[] = [''];
$rowIndex++;
// 列头:品名、汇总、市场、各门店明细
$rows[] = array_merge(['品名', '汇总', '市场'], array_values($this->stores));
$this->specialRows[++$rowIndex] = 'header';
$this->headerRow = $rowIndex;
// 明细行(0 数量的门店格留空不填色)
foreach ($this->productRows as $productRow) {
$line = [
$productRow['product_name'],
(int) $productRow['quantity'],
$this->marketLabel,
];
$storeQuantities = [];
foreach ($storeIds as $storeId) {
$quantity = (int) ($productRow['store_quantities'][$storeId] ?? 0);
$line[] = $quantity > 0 ? $quantity : '';
$storeQuantities[$storeId] = $quantity;
}
$rows[] = $line;
$this->rowData[++$rowIndex] = [
'quantity' => (int) $productRow['quantity'],
'store_quantities' => $storeQuantities,
];
}
$this->lastRow = $rowIndex;
return $this->rows = collect($rows);
}
public function title(): string
{
return $this->sheetTitle;
}
/**
* 标题/列头加粗;全表居中 + 全边框;
* 汇总列(浅黄)/门店数量列(浅蓝)按值条件填色(0 不填、列头固定填色),冻结列头
*/
public function styles(Worksheet $sheet): array
{
$this->collection();
$sheet->freezePane('A' . ($this->headerRow + 1));
$widths = [24, 10, 12];
foreach ($widths as $index => $width) {
$sheet->getColumnDimension(Coordinate::stringFromColumnIndex($index + 1))->setWidth($width);
}
$storeIds = array_map('intval', array_keys($this->stores));
$lastColumn = Coordinate::stringFromColumnIndex(3 + max(count($storeIds), 1));
foreach ($storeIds as $i => $storeId) {
$sheet->getColumnDimension(Coordinate::stringFromColumnIndex(4 + $i))->setWidth(12);
}
// 全部单元格水平/垂直居中
$sheet->getStyle('A1:' . $lastColumn . $this->lastRow)
->getAlignment()
->setHorizontal(Alignment::HORIZONTAL_CENTER)
->setVertical(Alignment::VERTICAL_CENTER);
// 表格区域(列头 → 数据末行)添加所有边框
$sheet->getStyle('A' . $this->headerRow . ':' . $lastColumn . $this->lastRow)
->getBorders()
->getAllBorders()
->setBorderStyle(Border::BORDER_THIN);
// 汇总列(B):有数据浅黄,0 无背景,列头固定浅黄
$this->fillCell($sheet, 'B' . $this->headerRow, self::QUANTITY_FILL);
foreach ($this->rowData as $row => $data) {
if ($data['quantity'] > 0) {
$this->fillCell($sheet, 'B' . $row, self::QUANTITY_FILL);
}
}
// 门店数量列(D 起):有数据浅蓝,0 无背景,列头固定浅蓝
foreach ($storeIds as $i => $storeId) {
$column = Coordinate::stringFromColumnIndex(4 + $i);
$this->fillCell($sheet, $column . $this->headerRow, self::STORE_FILL);
foreach ($this->rowData as $row => $data) {
if (($data['store_quantities'][$storeId] ?? 0) > 0) {
$this->fillCell($sheet, $column . $row, self::STORE_FILL);
}
}
}
$styles = [];
foreach ($this->specialRows as $row => $type) {
$style = match ($type) {
'title' => ['font' => ['bold' => true, 'size' => 14]],
'header' => ['font' => ['bold' => true]],
default => [],
};
if ($style !== []) {
$styles[$row] = $style;
}
}
return $styles;
}
/**
* 单元格纯色填充
*/
private function fillCell(Worksheet $sheet, string $coordinate, string $argb): void
{
$sheet->getStyle($coordinate)
->getFill()
->setFillType(Fill::FILL_SOLID)
->getStartColor()
->setARGB($argb);
}
}