账单导出
This commit is contained in:
@@ -7,7 +7,7 @@ use Illuminate\Support\Collection;
|
||||
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
|
||||
|
||||
/**
|
||||
* 门店账单批量导出:同一门店的账单合并在一张工作表,不同门店拆分为多个工作表
|
||||
* 门店账单批量导出:同一门店的账单数据合并为一张表,不同门店拆分为多个工作表
|
||||
*/
|
||||
class BillBatchExport implements WithMultipleSheets
|
||||
{
|
||||
|
||||
+86
-49
@@ -18,8 +18,8 @@ use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
/**
|
||||
* 门店账单导出:单账单可直接下载;多账单在同一工作表依次展示(批量导出按门店分组),
|
||||
* 账单内明细按商品合并(可按一级分类过滤),表尾汇总商品金额/配送费/附加金额/售后金额/总金额
|
||||
* 门店账单导出:单账单可直接下载;多账单按商品合并为一张汇总表(批量导出按门店分组),
|
||||
* 明细可按一级分类过滤,表尾汇总商品金额/配送费/附加金额/售后金额/总金额
|
||||
*/
|
||||
class BillExport implements FromCollection, WithStyles, WithTitle
|
||||
{
|
||||
@@ -46,12 +46,6 @@ class BillExport implements FromCollection, WithStyles, WithTitle
|
||||
/** 列头所在行索引 */
|
||||
private int $headerRow = 1;
|
||||
|
||||
/** @var array<int, int> 各账单列头所在行索引 */
|
||||
private array $headerRows = [];
|
||||
|
||||
/** @var array<int, array{header: int, last: int}> 各账单表格区域 */
|
||||
private array $tableRanges = [];
|
||||
|
||||
/** 数据末行索引 */
|
||||
private int $lastRow = 1;
|
||||
|
||||
@@ -77,7 +71,7 @@ class BillExport implements FromCollection, WithStyles, WithTitle
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出行:每张账单保留独立的标题/范围/列头/明细/合计,多账单之间空一行
|
||||
* 导出行:所选门店账单合并为一张表格,同商品及账单级费用分别汇总
|
||||
*/
|
||||
public function collection(): Collection
|
||||
{
|
||||
@@ -87,25 +81,20 @@ class BillExport implements FromCollection, WithStyles, WithTitle
|
||||
|
||||
$rows = [];
|
||||
$rowIndex = 0;
|
||||
foreach ($this->bills as $index => $bill) {
|
||||
if ($index > 0) {
|
||||
$rows[] = [''];
|
||||
$rowIndex++;
|
||||
}
|
||||
$this->appendBillRows($bill, $rows, $rowIndex);
|
||||
}
|
||||
$this->appendBillRows($this->bills, $rows, $rowIndex);
|
||||
|
||||
return $this->rows = collect($rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加一张账单的完整表格
|
||||
* 追加所选账单的合并表格
|
||||
*
|
||||
* @param Collection<int, BillModel> $bills
|
||||
* @param array<int, array<int, mixed>> $rows
|
||||
*/
|
||||
private function appendBillRows(BillModel $bill, array &$rows, int &$rowIndex): void
|
||||
private function appendBillRows(Collection $bills, array &$rows, int &$rowIndex): void
|
||||
{
|
||||
$items = app(BillDetailService::class)->mergedItems($bill);
|
||||
$items = $this->mergedItems($bills);
|
||||
|
||||
// 商品 => 一级分类解析(明细行「一级分类」列与分类过滤共用同一份结果)
|
||||
[$categories, $rootIds] = $this->resolveRootCategories($items);
|
||||
@@ -115,15 +104,29 @@ class BillExport implements FromCollection, WithStyles, WithTitle
|
||||
[$items, $categoryName] = $this->filterByRootCategory($items, $categories, $rootIds);
|
||||
}
|
||||
|
||||
$bill = $bills->first();
|
||||
$isMerged = $bills->count() > 1;
|
||||
|
||||
// 标题行
|
||||
$rows[] = ['门店账单 ' . $bill->bill_no];
|
||||
$rows[] = [$isMerged ? '门店账单 合并导出(' . $bills->count() . '张)' : '门店账单 ' . $bill->bill_no];
|
||||
$this->specialRows[++$rowIndex] = 'title';
|
||||
|
||||
// 范围行:门店 / 账单日期 / 分类 / 导出时间
|
||||
// 范围行:门店 / 账单日期(合并时取起止日期)/ 分类 / 导出时间
|
||||
$billDate = $bill->bill_date?->toDateString();
|
||||
if ($isMerged) {
|
||||
$dates = $bills->map(static fn (BillModel $item): ?string => $item->bill_date?->toDateString())
|
||||
->filter()
|
||||
->sort()
|
||||
->values();
|
||||
$billDate = $dates->first() === $dates->last()
|
||||
? $dates->first()
|
||||
: $dates->first() . ' 至 ' . $dates->last();
|
||||
}
|
||||
$storeName = $bill->store->name ?? ('门店#' . $bill->store_id);
|
||||
$rows[] = [
|
||||
'门店:' . $storeName
|
||||
. ' 账单日期:' . $bill->bill_date?->toDateString()
|
||||
. ' 账单日期:' . $billDate
|
||||
. ($isMerged ? ' 账单数量:' . $bills->count() : '')
|
||||
. ' 分类:' . $categoryName
|
||||
. ' 导出时间:' . now('Asia/Shanghai')->format('Y-m-d H:i:s'),
|
||||
];
|
||||
@@ -136,13 +139,9 @@ class BillExport implements FromCollection, WithStyles, WithTitle
|
||||
// 列头(包规=包规+单位合并;单价=每件零售价+单价单位;一级分类=商品根分类名)
|
||||
$rows[] = ['序号', '品名', '一级分类', '包规', '单价', '数量', '重量(斤)', '金额'];
|
||||
$this->specialRows[++$rowIndex] = 'header';
|
||||
$currentHeaderRow = $rowIndex;
|
||||
$this->headerRows[] = $currentHeaderRow;
|
||||
if (count($this->headerRows) === 1) {
|
||||
$this->headerRow = $rowIndex;
|
||||
}
|
||||
$this->headerRow = $rowIndex;
|
||||
|
||||
// 明细行(账单内同商品已合并,单价为每件价折算的零售价)
|
||||
// 明细行(所选账单的同商品已合并,单价为合并后的每件价折算零售价)
|
||||
$totalQuantity = 0;
|
||||
$totalWeight = '0';
|
||||
$totalAmount = '0';
|
||||
@@ -171,10 +170,19 @@ class BillExport implements FromCollection, WithStyles, WithTitle
|
||||
$this->fillCells[] = ['row' => $rowIndex, 'column' => 'F', 'value' => (float) $totalQuantity];
|
||||
$this->fillCells[] = ['row' => $rowIndex, 'column' => 'H', 'value' => (float) $totalAmount];
|
||||
|
||||
// 配送费/附加金额/售后金额/总金额(账单级费用全额计入,不受分类过滤影响)
|
||||
$deliveryTotal = (string) $bill->delivery_fee;
|
||||
$addedTotal = (string) $bill->added_amount;
|
||||
$afterSaleTotal = (string) $bill->after_sale;
|
||||
// 配送费/附加金额/售后金额按所选账单分别累加,不受分类过滤影响
|
||||
$deliveryTotal = '0';
|
||||
$addedTotal = '0';
|
||||
$afterSaleTotal = '0';
|
||||
$boxNum = 0;
|
||||
$trayNum = 0;
|
||||
foreach ($bills as $selectedBill) {
|
||||
$deliveryTotal = bcadd($deliveryTotal, (string) $selectedBill->delivery_fee, 2);
|
||||
$addedTotal = bcadd($addedTotal, (string) $selectedBill->added_amount, 2);
|
||||
$afterSaleTotal = bcadd($afterSaleTotal, (string) $selectedBill->after_sale, 2);
|
||||
$boxNum += (int) $selectedBill->box_num;
|
||||
$trayNum += (int) $selectedBill->tray_num;
|
||||
}
|
||||
$grandTotal = bcadd(bcadd(bcadd($totalAmount, $deliveryTotal, 2), $addedTotal, 2), $afterSaleTotal, 2);
|
||||
|
||||
$rows[] = ['配送费', '', '', '', '', '', '', (float) $deliveryTotal];
|
||||
@@ -182,7 +190,7 @@ class BillExport implements FromCollection, WithStyles, WithTitle
|
||||
$this->mergeRows[] = $rowIndex;
|
||||
$this->fillCells[] = ['row' => $rowIndex, 'column' => 'H', 'value' => (float) $deliveryTotal];
|
||||
|
||||
$rows[] = ['附加金额(周转筐 ' . (int) $bill->box_num . ' 个 / 周转托盘 ' . (int) $bill->tray_num . ' 个)', '', '', '', '', '', '', (float) $addedTotal];
|
||||
$rows[] = ['附加金额(周转筐 ' . $boxNum . ' 个 / 周转托盘 ' . $trayNum . ' 个)', '', '', '', '', '', '', (float) $addedTotal];
|
||||
$this->specialRows[++$rowIndex] = 'summary';
|
||||
$this->mergeRows[] = $rowIndex;
|
||||
$this->fillCells[] = ['row' => $rowIndex, 'column' => 'H', 'value' => (float) $addedTotal];
|
||||
@@ -197,7 +205,38 @@ class BillExport implements FromCollection, WithStyles, WithTitle
|
||||
$this->mergeRows[] = $rowIndex;
|
||||
$this->fillCells[] = ['row' => $rowIndex, 'column' => 'H', 'value' => (float) $grandTotal];
|
||||
$this->lastRow = $rowIndex;
|
||||
$this->tableRanges[] = ['header' => $currentHeaderRow, 'last' => $rowIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并所选账单商品:数量、重量、金额累加,单价按总金额 ÷ 总数量重新加权
|
||||
*
|
||||
* @param Collection<int, BillModel> $bills
|
||||
* @return array<int, array{product_id: int, product_name: string, product_spec: string, unit: string, price: string, quantity: int, weight: string, amount: string, spec: string, price_unit: string, image: string}>
|
||||
*/
|
||||
private function mergedItems(Collection $bills): array
|
||||
{
|
||||
$merged = [];
|
||||
$service = app(BillDetailService::class);
|
||||
foreach ($bills as $bill) {
|
||||
foreach ($service->mergedItems($bill) as $item) {
|
||||
$productId = (int) $item['product_id'];
|
||||
if (! isset($merged[$productId])) {
|
||||
$merged[$productId] = $item;
|
||||
continue;
|
||||
}
|
||||
|
||||
$merged[$productId]['quantity'] += (int) $item['quantity'];
|
||||
$merged[$productId]['weight'] = bcadd($merged[$productId]['weight'], $item['weight'], 3);
|
||||
$merged[$productId]['amount'] = bcadd($merged[$productId]['amount'], $item['amount'], 2);
|
||||
$merged[$productId]['price'] = bcdiv(
|
||||
$merged[$productId]['amount'],
|
||||
(string) $merged[$productId]['quantity'],
|
||||
2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($merged);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,22 +270,20 @@ class BillExport implements FromCollection, WithStyles, WithTitle
|
||||
$sheet->getStyle('A' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT);
|
||||
}
|
||||
|
||||
// 各账单表格区域(列头 → 末行)添加所有边框,金额列设置货币格式
|
||||
foreach ($this->tableRanges as $range) {
|
||||
$sheet->getStyle('A' . $range['header'] . ':H' . $range['last'])
|
||||
->getBorders()
|
||||
->getAllBorders()
|
||||
->setBorderStyle(Border::BORDER_THIN);
|
||||
$sheet->getStyle('H' . ($range['header'] + 1) . ':H' . $range['last'])
|
||||
->getNumberFormat()
|
||||
->setFormatCode(self::CURRENCY_FORMAT);
|
||||
}
|
||||
// 表格区域(列头 → 末行)添加所有边框
|
||||
$sheet->getStyle('A' . $this->headerRow . ':H' . $this->lastRow)
|
||||
->getBorders()
|
||||
->getAllBorders()
|
||||
->setBorderStyle(Border::BORDER_THIN);
|
||||
|
||||
// 金额列货币格式:¥ + 两位小数(单价列为文本,不设格式)
|
||||
$sheet->getStyle('H' . ($this->headerRow + 1) . ':H' . $this->lastRow)
|
||||
->getNumberFormat()
|
||||
->setFormatCode(self::CURRENCY_FORMAT);
|
||||
|
||||
// 列头固定填色:数量列(F)浅黄、金额列(H)浅红
|
||||
foreach ($this->headerRows as $headerRow) {
|
||||
$this->fillCell($sheet, 'F' . $headerRow, self::QUANTITY_FILL);
|
||||
$this->fillCell($sheet, 'H' . $headerRow, self::AMOUNT_FILL);
|
||||
}
|
||||
$this->fillCell($sheet, 'F' . $this->headerRow, self::QUANTITY_FILL);
|
||||
$this->fillCell($sheet, 'H' . $this->headerRow, self::AMOUNT_FILL);
|
||||
|
||||
// 数据区条件填色:非 0 填色,0 无背景(金额含负值)
|
||||
foreach ($this->fillCells as $cell) {
|
||||
@@ -322,7 +359,7 @@ class BillExport implements FromCollection, WithStyles, WithTitle
|
||||
));
|
||||
|
||||
if ($filtered === []) {
|
||||
throw new RepositoryException('该账单在「' . $categoryName . '」分类下无商品明细');
|
||||
throw new RepositoryException('所选账单在「' . $categoryName . '」分类下无商品明细');
|
||||
}
|
||||
|
||||
return [$filtered, $categoryName];
|
||||
|
||||
@@ -88,7 +88,7 @@ class BillController extends BaseMiniController
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量导出账单:仅可导出本店账单,选中账单合并在同一工作表
|
||||
* 批量导出账单:仅可导出本店账单,所选账单数据合并为一张表
|
||||
*/
|
||||
#[GetRoute('/bill/batch-export', authorize: true)]
|
||||
public function batchExport(Request $request): Response
|
||||
|
||||
@@ -62,7 +62,7 @@ class BillController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量导出账单:同一门店的账单合并在一张工作表,不同门店拆分为多个工作表
|
||||
* 批量导出账单:同一门店的账单数据合并为一张表,不同门店拆分为多个工作表
|
||||
*/
|
||||
#[GetRoute(route: '/batch-export', authorize: 'export')]
|
||||
public function batchExport(Request $request): Response
|
||||
|
||||
Reference in New Issue
Block a user