feat: 판매대장 하단에 용량별·품목별 통계 표시
- 현재 필터(기간·판매소·대행소·카테고리·크기) 적용 결과 기준 집계 - 용량별(리터별): 크기 오름차순, 판매량·판매금액 합계 - 품목별: 봉투/음식물/폐기물 3분류, 판매량·판매금액 합계 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -141,6 +141,10 @@ class SalesReport extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 하단 통계: 현재 필터(카테고리·크기)까지 적용된 결과 기준으로
|
||||||
|
// ① 용량별(리터별) ② 품목별(봉투/음식물/폐기물) 집계
|
||||||
|
[$sizeStats, $catStats] = $this->buildLedgerBottomStats($filtered, $sizeOf);
|
||||||
|
|
||||||
if ($mode === 'daily') {
|
if ($mode === 'daily') {
|
||||||
$built = $this->buildDailyLedgerPresentation($filtered, $hasBsFee);
|
$built = $this->buildDailyLedgerPresentation($filtered, $hasBsFee);
|
||||||
} else {
|
} else {
|
||||||
@@ -235,6 +239,8 @@ class SalesReport extends BaseController
|
|||||||
'cats' => $cats,
|
'cats' => $cats,
|
||||||
'sizeGroups' => $sizeGroups,
|
'sizeGroups' => $sizeGroups,
|
||||||
'size' => $size,
|
'size' => $size,
|
||||||
|
'sizeStats' => $sizeStats,
|
||||||
|
'catStats' => $catStats,
|
||||||
'shops' => $shops,
|
'shops' => $shops,
|
||||||
'agencies' => $agencies,
|
'agencies' => $agencies,
|
||||||
'lgName' => $lgName,
|
'lgName' => $lgName,
|
||||||
@@ -286,6 +292,68 @@ class SalesReport extends BaseController
|
|||||||
return $builder->get()->getResult();
|
return $builder->get()->getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 판매대장 하단 통계: 용량별(리터별)·품목별(봉투/음식물/폐기물) 집계.
|
||||||
|
*
|
||||||
|
* @param list<object> $rows 필터가 적용된 상세 판매행
|
||||||
|
* @param callable $sizeOf 품목명 → 크기 토큰(예: '10L', '1000원', '')
|
||||||
|
* @return array{0: list<array{label:string,qty:int,amount:int}>, 1: list<array{label:string,qty:int,amount:int}>}
|
||||||
|
*/
|
||||||
|
private function buildLedgerBottomStats(array $rows, callable $sizeOf): array
|
||||||
|
{
|
||||||
|
$bySize = []; // 크기토큰 => [qty, amount]
|
||||||
|
$byCat = []; // '봉투'|'음식물'|'폐기물' => [qty, amount]
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$name = (string) ($row->bs_bag_name ?? '');
|
||||||
|
$qty = (int) ($row->line_qty ?? 0);
|
||||||
|
$amt = (float) ($row->line_amount ?? 0);
|
||||||
|
|
||||||
|
// ① 용량별(리터별) — 크기 토큰이 없으면 '기타'
|
||||||
|
$sz = (string) $sizeOf($name);
|
||||||
|
if ($sz === '') {
|
||||||
|
$sz = '기타';
|
||||||
|
}
|
||||||
|
$bySize[$sz]['qty'] = ($bySize[$sz]['qty'] ?? 0) + $qty;
|
||||||
|
$bySize[$sz]['amount'] = ($bySize[$sz]['amount'] ?? 0) + $amt;
|
||||||
|
|
||||||
|
// ② 품목별 — 봉투/음식물/폐기물 3분류
|
||||||
|
if (mb_strpos($name, '음식물') !== false) {
|
||||||
|
$cat = '음식물';
|
||||||
|
} elseif (mb_strpos($name, '폐기물') !== false || mb_strpos($name, '대형') !== false) {
|
||||||
|
$cat = '폐기물';
|
||||||
|
} else {
|
||||||
|
$cat = '봉투';
|
||||||
|
}
|
||||||
|
$byCat[$cat]['qty'] = ($byCat[$cat]['qty'] ?? 0) + $qty;
|
||||||
|
$byCat[$cat]['amount'] = ($byCat[$cat]['amount'] ?? 0) + $amt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 용량별: 숫자(리터/금액) 오름차순, '기타'는 맨 뒤
|
||||||
|
$sizeStats = [];
|
||||||
|
foreach ($bySize as $label => $v) {
|
||||||
|
$sizeStats[] = ['label' => (string) $label, 'qty' => (int) $v['qty'], 'amount' => (int) round($v['amount'])];
|
||||||
|
}
|
||||||
|
usort($sizeStats, static function (array $a, array $b): int {
|
||||||
|
$ao = $a['label'] === '기타' ? 1 : 0;
|
||||||
|
$bo = $b['label'] === '기타' ? 1 : 0;
|
||||||
|
if ($ao !== $bo) {
|
||||||
|
return $ao <=> $bo;
|
||||||
|
}
|
||||||
|
return (int) preg_replace('/\D/', '', $a['label']) <=> (int) preg_replace('/\D/', '', $b['label']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 품목별: 봉투 → 음식물 → 폐기물 고정 순서
|
||||||
|
$catStats = [];
|
||||||
|
foreach (['봉투', '음식물', '폐기물'] as $c) {
|
||||||
|
if (isset($byCat[$c])) {
|
||||||
|
$catStats[] = ['label' => $c, 'qty' => (int) $byCat[$c]['qty'], 'amount' => (int) round($byCat[$c]['amount'])];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$sizeStats, $catStats];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param list<string> $cats 빈 배열이면 전체
|
* @param list<string> $cats 빈 배열이면 전체
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -250,6 +250,80 @@ $excelUrl = mgmt_url('reports/sales-ledger?' . http_build_query($exportParams));
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-700 mt-2 mb-0 no-print">판매건수(상세 행): <?= number_format((int) ($saleLineCount ?? 0)) ?>건</p>
|
<p class="text-sm text-gray-700 mt-2 mb-0 no-print">판매건수(상세 행): <?= number_format((int) ($saleLineCount ?? 0)) ?>건</p>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$sizeStats = $sizeStats ?? [];
|
||||||
|
$catStats = $catStats ?? [];
|
||||||
|
$sizeTotQty = array_sum(array_map(static fn ($r) => (int) $r['qty'], $sizeStats));
|
||||||
|
$sizeTotAmt = array_sum(array_map(static fn ($r) => (int) $r['amount'], $sizeStats));
|
||||||
|
$catTotQty = array_sum(array_map(static fn ($r) => (int) $r['qty'], $catStats));
|
||||||
|
$catTotAmt = array_sum(array_map(static fn ($r) => (int) $r['amount'], $catStats));
|
||||||
|
?>
|
||||||
|
<div class="mt-4 grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
|
<!-- 용량별(리터별) 통계 -->
|
||||||
|
<div>
|
||||||
|
<div class="text-sm font-bold text-gray-700 mb-1">용량별(리터별) 통계</div>
|
||||||
|
<table class="w-full data-table text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-gray-50">
|
||||||
|
<th class="text-left px-2 py-1">용량</th>
|
||||||
|
<th class="text-right px-2 py-1">판매량(매)</th>
|
||||||
|
<th class="text-right px-2 py-1">판매금액(원)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if ($sizeStats === []): ?>
|
||||||
|
<tr><td colspan="3" class="text-center text-gray-400 py-4">데이터 없음</td></tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($sizeStats as $r): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-left px-2 py-1"><?= esc($r['label']) ?></td>
|
||||||
|
<td class="text-right px-2 py-1"><?= number_format((int) $r['qty']) ?></td>
|
||||||
|
<td class="text-right px-2 py-1"><?= number_format((int) $r['amount']) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<tr class="bg-amber-50 font-bold">
|
||||||
|
<td class="text-left px-2 py-1">합계</td>
|
||||||
|
<td class="text-right px-2 py-1"><?= number_format((int) $sizeTotQty) ?></td>
|
||||||
|
<td class="text-right px-2 py-1"><?= number_format((int) $sizeTotAmt) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 품목별(봉투/음식물/폐기물) 통계 -->
|
||||||
|
<div>
|
||||||
|
<div class="text-sm font-bold text-gray-700 mb-1">품목별(봉투·음식물·폐기물) 통계</div>
|
||||||
|
<table class="w-full data-table text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-gray-50">
|
||||||
|
<th class="text-left px-2 py-1">품목</th>
|
||||||
|
<th class="text-right px-2 py-1">판매량(매)</th>
|
||||||
|
<th class="text-right px-2 py-1">판매금액(원)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if ($catStats === []): ?>
|
||||||
|
<tr><td colspan="3" class="text-center text-gray-400 py-4">데이터 없음</td></tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($catStats as $r): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-left px-2 py-1"><?= esc($r['label']) ?></td>
|
||||||
|
<td class="text-right px-2 py-1"><?= number_format((int) $r['qty']) ?></td>
|
||||||
|
<td class="text-right px-2 py-1"><?= number_format((int) $r['amount']) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<tr class="bg-amber-50 font-bold">
|
||||||
|
<td class="text-left px-2 py-1">합계</td>
|
||||||
|
<td class="text-right px-2 py-1"><?= number_format((int) $catTotQty) ?></td>
|
||||||
|
<td class="text-right px-2 py-1"><?= number_format((int) $catTotAmt) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
Reference in New Issue
Block a user