215 lines
8.2 KiB
PHP
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');
|
|
}
|
|
}
|
|
}
|