CaiWuXiTong/app/Http/Controllers/Business/PayablesUnpricedCycleExport.php
苏木 c658bbed99 feat: 增加未定价账期汇总与导出 (#1179)
Co-authored-by: 苏木 <594326199@qq.com>
Co-committed-by: 苏木 <594326199@qq.com>
2026-11-15 11:28:34 +08:00

215 lines
8.2 KiB
PHP

<?php
namespace App\Http\Controllers\Business;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\EcommerceFinance\Services\PayablesFinalRegistry;
use Modules\EcommerceFinance\Services\PayablesUnpricedCycleProcessor;
use Modules\EcommerceFinance\Services\SupplierPayableOverrideStore;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use Symfony\Component\HttpFoundation\StreamedResponse;
class PayablesUnpricedCycleExport extends Controller
{
public function __construct(
private readonly SupplierPayableOverrideStore $store,
private readonly PayablesFinalRegistry $registry,
) {
$this->middleware('auth');
$this->middleware('company.identify');
}
public function export(Request $request): StreamedResponse
{
$requestedPeriod = trim((string) $request->query('month', ''));
$period = preg_match('/^\d{4}-\d{2}$/', $requestedPeriod) === 1
? $requestedPeriod
: now()->subMonthNoOverflow()->format('Y-m');
$companyId = (int) (company_id() ?: 1);
$deliveryRows = $this->periodRows(
PayablesUnpricedCycleProcessor::DELIVERY_PRICED_DATASET,
$period,
$companyId,
);
$returnRows = $this->periodRows(
PayablesUnpricedCycleProcessor::RETURN_PRICED_DATASET,
$period,
$companyId,
);
$deliverySummary = $this->summary($deliveryRows, 'guessAmount', 'amount');
$returnSummary = $this->summary($returnRows, 'guess_amount', 'amount');
$spreadsheet = new Spreadsheet();
$summarySheet = $spreadsheet->getActiveSheet();
$summarySheet->setTitle('账期汇总');
$summarySheet->fromArray([
['类型', '记录数', '件数', '金额', '预估金额'],
['送货未定价', $deliverySummary['count'], $deliverySummary['quantity'], $deliverySummary['amount'], $deliverySummary['estimated_amount']],
['退货未定价', $returnSummary['count'], $returnSummary['quantity'], $returnSummary['amount'], $returnSummary['estimated_amount']],
], null, 'A1');
$this->styleSheet($summarySheet, 5, 3, [2 => 'number', 3 => 'number', 4 => 'number', 5 => 'number']);
$summarySheet->getColumnDimension('A')->setWidth(18);
foreach (['B', 'C', 'D', 'E'] as $column) {
$summarySheet->getColumnDimension($column)->setWidth(18);
}
$deliverySheet = $spreadsheet->createSheet();
$this->writeDetailSheet(
$deliverySheet,
'送货未定价',
$this->registry->fields(PayablesFinalRegistry::SECTION_SUPPLIER, 'delivery-unpriced'),
$deliveryRows,
);
$returnSheet = $spreadsheet->createSheet();
$this->writeDetailSheet(
$returnSheet,
'退货未定价',
$this->registry->fields(PayablesFinalRegistry::SECTION_SUPPLIER, 'return-unpriced'),
$returnRows,
);
$spreadsheet->setActiveSheetIndex(0);
$fileName = '之前月未定价-本月定价_' . $period . '_' . now()->format('Ymd_His') . '.xlsx';
return response()->streamDownload(function () use ($spreadsheet): void {
(new Xlsx($spreadsheet))->save('php://output');
$spreadsheet->disconnectWorksheets();
}, $fileName, [
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'Cache-Control' => 'private, no-store',
]);
}
/**
* @return list<array<string,mixed>>
*/
private function periodRows(string $dataset, string $period, int $companyId): array
{
return array_values(array_filter(
$this->store->listRows($dataset, $companyId),
static fn (array $row): bool => (string) ($row['_accounting_period'] ?? '') === $period,
));
}
/**
* @param list<array<string,mixed>> $rows
* @return array{count:int,quantity:float,amount:float,estimated_amount:float}
*/
private function summary(array $rows, string $estimatedAmountField, string $amountField): array
{
$quantity = 0.0;
$amount = 0.0;
$estimatedAmount = 0.0;
foreach ($rows as $row) {
if (is_numeric($row['qty'] ?? null)) {
$quantity += (float) $row['qty'];
}
if (is_numeric($row[$amountField] ?? null)) {
$amount += (float) $row[$amountField];
}
if (is_numeric($row[$estimatedAmountField] ?? null)) {
$estimatedAmount += (float) $row[$estimatedAmountField];
}
}
return [
'count' => count($rows),
'quantity' => $quantity,
'amount' => $amount,
'estimated_amount' => $estimatedAmount,
];
}
/**
* @param array<string,array{label:string,type:string}> $fields
* @param list<array<string,mixed>> $rows
*/
private function writeDetailSheet(
\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $sheet,
string $title,
array $fields,
array $rows,
): void {
$sheet->setTitle($title);
$headers = array_merge(['序号'], array_column($fields, 'label'));
$sheet->fromArray($headers, null, 'A1');
$data = [];
foreach ($rows as $index => $row) {
$line = [$index + 1];
foreach ($fields as $field => $definition) {
$value = $row[$field] ?? '';
$line[] = $definition['type'] === 'number' && is_numeric($value)
? (float) $value
: (string) $value;
}
$data[] = $line;
}
if ($data !== []) {
$sheet->fromArray($data, null, 'A2');
}
$lastColumn = Coordinate::stringFromColumnIndex(count($headers));
$lastRow = max(1, count($data) + 1);
$types = [];
$columnIndex = 2;
foreach ($fields as $definition) {
if ($definition['type'] === 'number') {
$types[$columnIndex] = 'number';
}
$column = Coordinate::stringFromColumnIndex($columnIndex);
$sheet->getColumnDimension($column)->setWidth(min(32, max(12, mb_strlen($definition['label']) * 2 + 5)));
$columnIndex++;
}
$sheet->getColumnDimension('A')->setWidth(9);
$this->styleSheet($sheet, count($headers), $lastRow, $types);
$sheet->freezePane('A2');
$sheet->setAutoFilter('A1:' . $lastColumn . $lastRow);
}
/**
* @param array<int,string> $types
*/
private function styleSheet(
\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $sheet,
int $columnCount,
int $rowCount,
array $types,
): void {
$lastColumn = Coordinate::stringFromColumnIndex($columnCount);
$headerRange = 'A1:' . $lastColumn . '1';
$sheet->getStyle($headerRange)->applyFromArray([
'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '856632']],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER],
'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'D8D2C7']]],
]);
$sheet->getRowDimension(1)->setRowHeight(24);
if ($rowCount > 1) {
$sheet->getStyle('A2:' . $lastColumn . $rowCount)
->getBorders()
->getAllBorders()
->setBorderStyle(Border::BORDER_THIN)
->getColor()
->setRGB('E7E2D8');
}
foreach ($types as $index => $type) {
if ($type !== 'number') {
continue;
}
$column = Coordinate::stringFromColumnIndex($index);
$sheet->getStyle($column . '2:' . $column . max(2, $rowCount))
->getNumberFormat()
->setFormatCode('#,##0.00');
}
}
}