Files
xin-procurement/app/Exports/PurchaseStoreSheet.php
2026-09-03 22:00:58 +08:00

205 lines
7.0 KiB
PHP
Raw Permalink 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\StoreModel;
use App\Services\PurchaseItemService;
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\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
/**
* 门店购买详情导出 · 单门店工作表
*/
class PurchaseStoreSheet implements FromCollection, WithStrictNullComparison, WithStyles, WithTitle
{
/** 数量列填充色(浅黄) */
private const string QUANTITY_FILL = 'FFFFF2CC';
/** 金额列填充色(浅红) */
private const string AMOUNT_FILL = 'FFFFCCCC';
/** 金额格式:¥ + 两位小数 */
private const string CURRENCY_FORMAT = '"¥"#,##0.00';
private ?Collection $rows = null;
/** @var array<int, string> 特殊行索引(1 起)=> 类型(title/header/summary */
private array $specialRows = [];
/** @var array<int, array{quantity: int, amount: float}> 明细行索引 => 填色判断数据 */
private array $rowData = [];
/** @var array{quantity: int, amount: float} 合计行填色判断数据 */
private array $summaryTotals = ['quantity' => 0, 'amount' => 0.0];
/** 列头所在行索引 */
private int $headerRow = 1;
/** 数据末行索引 */
private int $lastRow = 1;
public function __construct(
private readonly PurchaseOrderModel $purchase,
private readonly StoreModel $store,
private readonly string $sheetTitle,
) {
}
/**
* 导出行:标题/空行/列头/明细/合计
*/
public function collection(): Collection
{
if ($this->rows !== null) {
return $this->rows;
}
$items = app(PurchaseItemService::class)->storeSummaryRows($this->purchase->id, $this->store->id);
$rows = [];
$rowIndex = 0;
// 标题行
$rows[] = [$this->store->name . ' · 采购单 ' . $this->purchase->purchase_no];
$this->specialRows[++$rowIndex] = 'title';
// 空行
$rows[] = [''];
$rowIndex++;
// 列头
$rows[] = ['序号', '品名', '市场', '包规', '单位', '单价', '数量', '重量(斤)', '预计金额'];
$this->specialRows[++$rowIndex] = 'header';
$this->headerRow = $rowIndex;
// 明细行
$totalQuantity = 0;
$totalWeight = '0';
$totalAmount = '0';
foreach (array_values($items) as $sort => $item) {
$this->rowData[++$rowIndex] = [
'quantity' => (int) $item['quantity'],
'amount' => (float) $item['amount'],
];
$rows[] = [
$sort + 1,
$item['product_name'],
$item['market'],
$item['product_spec'],
$item['unit'],
(float) $item['price'],
(int) $item['quantity'],
(float) $item['weight'],
(float) $item['amount'],
];
$totalQuantity += (int) $item['quantity'];
$totalWeight = bcadd($totalWeight, (string) $item['weight'], 3);
$totalAmount = bcadd($totalAmount, (string) $item['amount'], 2);
}
// 合计行
$rows[] = ['', '合计', '', '', '', '', $totalQuantity, (float) $totalWeight, (float) $totalAmount];
$this->specialRows[++$rowIndex] = 'summary';
$this->lastRow = $rowIndex;
$this->summaryTotals = ['quantity' => $totalQuantity, 'amount' => (float) $totalAmount];
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 = [6, 24, 12, 14, 8, 10, 10, 12, 12];
foreach ($widths as $index => $width) {
$sheet->getColumnDimension(\PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($index + 1))
->setWidth($width);
}
// 全部单元格水平/垂直居中
$sheet->getStyle('A1:I' . $this->lastRow)
->getAlignment()
->setHorizontal(Alignment::HORIZONTAL_CENTER)
->setVertical(Alignment::VERTICAL_CENTER);
// 表格区域(列头 → 合计行)添加所有边框
$sheet->getStyle('A' . $this->headerRow . ':I' . $this->lastRow)
->getBorders()
->getAllBorders()
->setBorderStyle(Border::BORDER_THIN);
// 金额类列(单价/预计金额)货币格式:¥ + 两位小数
foreach (['F', 'I'] as $column) {
$sheet->getStyle($column . ($this->headerRow + 1) . ':' . $column . $this->lastRow)
->getNumberFormat()
->setFormatCode(self::CURRENCY_FORMAT);
}
// 数量列(G):有数据浅黄,0 无背景,列头固定浅黄
$this->fillCell($sheet, 'G' . $this->headerRow, self::QUANTITY_FILL);
foreach ($this->rowData as $row => $data) {
if ($data['quantity'] > 0) {
$this->fillCell($sheet, 'G' . $row, self::QUANTITY_FILL);
}
}
if ($this->summaryTotals['quantity'] > 0) {
$this->fillCell($sheet, 'G' . $this->lastRow, self::QUANTITY_FILL);
}
// 金额列(I):有数据浅红,0 无背景,列头固定浅红
$this->fillCell($sheet, 'I' . $this->headerRow, self::AMOUNT_FILL);
foreach ($this->rowData as $row => $data) {
if ($data['amount'] > 0) {
$this->fillCell($sheet, 'I' . $row, self::AMOUNT_FILL);
}
}
if ($this->summaryTotals['amount'] > 0) {
$this->fillCell($sheet, 'I' . $this->lastRow, self::AMOUNT_FILL);
}
$styles = [];
foreach ($this->specialRows as $row => $type) {
$style = match ($type) {
'title' => ['font' => ['bold' => true, 'size' => 14]],
'header', 'summary' => ['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);
}
}