2026-06-01 16:15:15 +09:00
|
|
|
<?php
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
/** @var list<object> $shops */
|
|
|
|
|
/** @var list<object> $agencies */
|
|
|
|
|
/** @var list<array<string,mixed>> $ledgerRows */
|
|
|
|
|
/** @var int $saleLineCount */
|
|
|
|
|
/** @var string $startDate */
|
|
|
|
|
/** @var string $endDate */
|
|
|
|
|
/** @var string $mode */
|
|
|
|
|
/** @var int $dsIdx */
|
|
|
|
|
/** @var int $saIdx */
|
|
|
|
|
/** @var list<string> $cats */
|
|
|
|
|
/** @var string $lgName */
|
|
|
|
|
/** @var string $filterAgencyLabel */
|
|
|
|
|
/** @var list<string> $printSubtitleLines */
|
|
|
|
|
|
|
|
|
|
$printTitle = ($mode ?? 'daily') === 'daily' ? '[지정판매소] 일자별 판매대장' : '[지정판매소] 기간별 판매대장';
|
|
|
|
|
$printDate = date('Y-m-d');
|
|
|
|
|
$printExtraLines = $printSubtitleLines ?? [];
|
|
|
|
|
$catKeys = ['general', 'food', 'sticker', 'reuse', 'apt', 'public_use', 'container', 'waste'];
|
|
|
|
|
$catLabels = [
|
|
|
|
|
'general' => '일반용',
|
|
|
|
|
'food' => '음식물',
|
|
|
|
|
'sticker' => '스티커',
|
|
|
|
|
'reuse' => '재사용',
|
|
|
|
|
'apt' => '공동주택용',
|
|
|
|
|
'public_use' => '공공용',
|
|
|
|
|
'container' => '용기',
|
|
|
|
|
'waste' => '폐기물',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$exportParams = [
|
|
|
|
|
'start_date' => $startDate,
|
|
|
|
|
'end_date' => $endDate,
|
|
|
|
|
'mode' => $mode,
|
|
|
|
|
'ds_idx' => $dsIdx,
|
|
|
|
|
'sa_idx' => $saIdx ?? 0,
|
|
|
|
|
'export' => '1',
|
|
|
|
|
];
|
|
|
|
|
if ($cats !== []) {
|
|
|
|
|
$exportParams['cat'] = $cats;
|
|
|
|
|
}
|
|
|
|
|
$excelUrl = mgmt_url('reports/sales-ledger?' . http_build_query($exportParams));
|
|
|
|
|
?>
|
|
|
|
|
<?= view('components/print_header', [
|
|
|
|
|
'printTitle' => $printTitle,
|
|
|
|
|
'printDate' => $printDate,
|
|
|
|
|
'printExtraLines' => $printExtraLines,
|
|
|
|
|
]) ?>
|
|
|
|
|
|
|
|
|
|
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel no-print">
|
2026-03-26 16:40:49 +09:00
|
|
|
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
2026-06-01 16:15:15 +09:00
|
|
|
<span class="text-sm font-bold text-gray-700">지정 판매소 판매 대장</span>
|
|
|
|
|
<div class="flex flex-wrap gap-2">
|
|
|
|
|
<button type="button" onclick="window.print()" class="border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
|
|
|
|
<a href="<?= esc($excelUrl, 'attr') ?>" class="inline-flex items-center border border-green-600 text-green-700 px-3 py-1 rounded-sm text-sm hover:bg-green-50 transition">엑셀저장</a>
|
|
|
|
|
</div>
|
2026-03-26 16:40:49 +09:00
|
|
|
</div>
|
2026-03-25 18:29:31 +09:00
|
|
|
</section>
|
2026-06-01 16:15:15 +09:00
|
|
|
|
|
|
|
|
<section class="p-3 bg-white border-b border-gray-200 no-print">
|
|
|
|
|
<form method="get" action="<?= mgmt_url('reports/sales-ledger') ?>" id="sales-ledger-form" class="space-y-3 text-sm">
|
|
|
|
|
<div class="flex flex-wrap items-end gap-3">
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-gray-600 mb-0.5">조회일자</label>
|
|
|
|
|
<div class="flex items-center gap-1">
|
|
|
|
|
<input type="date" name="start_date" value="<?= esc($startDate) ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
|
|
|
|
<span>~</span>
|
|
|
|
|
<input type="date" name="end_date" value="<?= esc($endDate) ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-gray-600 mb-0.5">지정판매소</label>
|
|
|
|
|
<select name="ds_idx" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[14rem] max-w-[20rem]">
|
|
|
|
|
<option value="0">전체</option>
|
|
|
|
|
<?php foreach ($shops as $s): ?>
|
|
|
|
|
<?php $sid = (int) ($s->ds_idx ?? 0); ?>
|
|
|
|
|
<option value="<?= esc((string) $sid) ?>" <?= $dsIdx === $sid ? 'selected' : '' ?>>
|
|
|
|
|
<?= esc(trim((string) ($s->ds_shop_no ?? '') . ' ' . (string) ($s->ds_name ?? ''))) ?>
|
|
|
|
|
</option>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-gray-600 mb-0.5">대행소</label>
|
|
|
|
|
<select name="sa_idx" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[12rem] max-w-[20rem]">
|
|
|
|
|
<option value="0">전체</option>
|
|
|
|
|
<?php foreach ($agencies ?? [] as $agency): ?>
|
|
|
|
|
<?php $aid = (int) ($agency->sa_idx ?? 0); ?>
|
|
|
|
|
<option value="<?= esc((string) $aid) ?>" <?= (int) ($saIdx ?? 0) === $aid ? 'selected' : '' ?>>
|
|
|
|
|
<?= esc(trim((string) ($agency->sa_name ?? ''))) ?>
|
|
|
|
|
</option>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<span class="block text-gray-600 mb-0.5">집계 방식</span>
|
|
|
|
|
<div class="flex gap-3">
|
|
|
|
|
<label class="inline-flex items-center gap-1"><input type="radio" name="mode" value="daily" <?= $mode === 'daily' ? 'checked' : '' ?>/> 일자별</label>
|
|
|
|
|
<label class="inline-flex items-center gap-1"><input type="radio" name="mode" value="period" <?= $mode === 'period' ? 'checked' : '' ?>/> 기간별</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<fieldset class="border border-gray-200 rounded p-2">
|
|
|
|
|
<legend class="text-xs text-gray-600 px-1">품목</legend>
|
|
|
|
|
<div class="flex flex-wrap gap-x-4 gap-y-1">
|
|
|
|
|
<label class="inline-flex items-center gap-1">
|
|
|
|
|
<input type="checkbox" name="cat[]" value="all" id="cat-all" <?= $cats === [] ? 'checked' : '' ?>/>
|
|
|
|
|
전체
|
|
|
|
|
</label>
|
|
|
|
|
<?php foreach ($catKeys as $ck): ?>
|
|
|
|
|
<label class="inline-flex items-center gap-1">
|
|
|
|
|
<input type="checkbox" name="cat[]" value="<?= esc($ck, 'attr') ?>" class="cat-item" <?= in_array($ck, $cats, true) ? 'checked' : '' ?>/>
|
|
|
|
|
<?= esc($catLabels[$ck] ?? $ck) ?>
|
|
|
|
|
</label>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</div>
|
|
|
|
|
</fieldset>
|
|
|
|
|
<div>
|
|
|
|
|
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm">조회</button>
|
|
|
|
|
</div>
|
2026-03-25 18:29:31 +09:00
|
|
|
</form>
|
|
|
|
|
</section>
|
|
|
|
|
|
2026-06-01 16:15:15 +09:00
|
|
|
<section class="p-3 bg-white sales-ledger-report-section">
|
|
|
|
|
<style>
|
|
|
|
|
@media print {
|
|
|
|
|
/* 일계표 등 다른 리포트와 동일: 브라우저 기본 세로 A4 (landscape 지정 안 함) */
|
|
|
|
|
.sales-ledger-screen-title { display: none !important; }
|
|
|
|
|
.sales-ledger-report-section { padding: 0 !important; }
|
|
|
|
|
.sales-ledger-scroll-wrap {
|
|
|
|
|
overflow: visible !important;
|
|
|
|
|
border: 1px solid #333 !important;
|
|
|
|
|
}
|
|
|
|
|
#sales-ledger-table {
|
|
|
|
|
font-size: 7.5pt !important;
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
table-layout: fixed !important;
|
|
|
|
|
}
|
|
|
|
|
#sales-ledger-table th,
|
|
|
|
|
#sales-ledger-table td {
|
|
|
|
|
min-width: 0 !important;
|
|
|
|
|
padding: 3px 4px !important;
|
|
|
|
|
white-space: normal !important;
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
overflow-wrap: anywhere;
|
|
|
|
|
line-height: 1.35;
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
}
|
|
|
|
|
#sales-ledger-table th {
|
|
|
|
|
font-size: 7pt !important;
|
|
|
|
|
padding-top: 4px !important;
|
|
|
|
|
padding-bottom: 4px !important;
|
|
|
|
|
}
|
|
|
|
|
/* 세로 A4 폭에 맞춘 열 비율 (긴 칸은 줄바꿈) */
|
|
|
|
|
#sales-ledger-table .sl-col-date { width: 9%; }
|
|
|
|
|
#sales-ledger-table .sl-col-designation { width: 9%; }
|
|
|
|
|
#sales-ledger-table .sl-col-shop { width: 10%; }
|
|
|
|
|
#sales-ledger-table .sl-col-rep { width: 7%; }
|
|
|
|
|
#sales-ledger-table .sl-col-addr { width: 18%; }
|
|
|
|
|
#sales-ledger-table .sl-col-product { width: 12%; }
|
|
|
|
|
#sales-ledger-table .sl-col-num { width: 7%; }
|
|
|
|
|
#sales-ledger-table.sl-period .sl-col-addr { width: 22%; }
|
|
|
|
|
#sales-ledger-table.sl-period .sl-col-product { width: 14%; }
|
|
|
|
|
}
|
|
|
|
|
@media screen {
|
|
|
|
|
#sales-ledger-table th,
|
|
|
|
|
#sales-ledger-table td {
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
line-height: 1.45;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
}
|
|
|
|
|
#sales-ledger-table .sl-col-date,
|
|
|
|
|
#sales-ledger-table .sl-col-num { white-space: nowrap; }
|
|
|
|
|
#sales-ledger-table .sl-col-addr,
|
|
|
|
|
#sales-ledger-table .sl-col-shop,
|
|
|
|
|
#sales-ledger-table .sl-col-product {
|
|
|
|
|
white-space: normal;
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
<div class="mb-2 text-center sales-ledger-screen-title no-print">
|
|
|
|
|
<h1 class="text-lg font-bold m-0"><?= esc($printTitle) ?></h1>
|
|
|
|
|
<p class="text-sm text-gray-700 m-1"><?= esc(trim(($lgName ?? '') . ' / 지정판매소: ' . ($filterShopLabel ?? '') . ' / 대행소: ' . ($filterAgencyLabel ?? '전체'))) ?></p>
|
|
|
|
|
<p class="text-xs text-gray-500 m-0">(단위: 매 / 원) · <?= esc($startDate) ?> ~ <?= esc($endDate) ?></p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="sales-ledger-scroll-wrap border border-gray-300 overflow-auto">
|
|
|
|
|
<table class="w-full data-table text-sm <?= ($mode ?? 'daily') === 'period' ? 'sl-period' : '' ?>" id="sales-ledger-table">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<?php if (($mode ?? 'daily') === 'daily'): ?>
|
|
|
|
|
<th class="sl-col-date">일자</th>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
<th class="sl-col-designation">지정번호</th>
|
|
|
|
|
<th class="sl-col-shop text-left">판매소명</th>
|
|
|
|
|
<th class="sl-col-rep">대표자</th>
|
|
|
|
|
<th class="sl-col-addr text-left">소재지</th>
|
|
|
|
|
<th class="sl-col-product text-left">품명</th>
|
|
|
|
|
<th class="text-right sl-col-num">판매량</th>
|
|
|
|
|
<th class="text-right sl-col-num">판매금액</th>
|
|
|
|
|
<th class="text-right sl-col-num">수수료</th>
|
|
|
|
|
<th class="text-right sl-col-num">총액</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<?php foreach ($ledgerRows as $r): ?>
|
2026-03-25 18:29:31 +09:00
|
|
|
<?php
|
2026-06-01 16:15:15 +09:00
|
|
|
$kind = (string) ($r['kind'] ?? 'data');
|
|
|
|
|
$trClass = $kind === 'subtotal' ? 'bg-gray-50 font-semibold' : ($kind === 'grand' ? 'bg-amber-50 font-bold' : '');
|
2026-03-25 18:29:31 +09:00
|
|
|
?>
|
2026-06-01 16:15:15 +09:00
|
|
|
<tr class="<?= esc($trClass, 'attr') ?>">
|
|
|
|
|
<?php if (($mode ?? 'daily') === 'daily'): ?>
|
|
|
|
|
<td class="text-center sl-col-date"><?= esc((string) ($r['sale_date'] ?? '')) ?></td>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
<td class="text-center sl-col-designation"><?= esc((string) ($r['designation_no'] ?? '')) ?></td>
|
|
|
|
|
<td class="text-left pl-1 sl-col-shop"><?= esc((string) ($r['shop_name'] ?? '')) ?></td>
|
|
|
|
|
<td class="text-center sl-col-rep"><?= esc((string) ($r['rep_name'] ?? '')) ?></td>
|
|
|
|
|
<td class="text-left pl-1 sl-col-addr"><?= esc((string) ($r['address'] ?? '')) ?></td>
|
|
|
|
|
<td class="text-left pl-1 sl-col-product"><?= esc((string) ($r['product_name'] ?? '')) ?></td>
|
|
|
|
|
<td class="text-right tabular-nums sl-col-num"><?= esc((string) ($r['qty'] ?? '')) ?></td>
|
|
|
|
|
<td class="text-right tabular-nums sl-col-num"><?= esc((string) ($r['amount'] ?? '')) ?></td>
|
|
|
|
|
<td class="text-right tabular-nums sl-col-num"><?= esc((string) ($r['fee'] ?? '')) ?></td>
|
|
|
|
|
<td class="text-right tabular-nums sl-col-num"><?= esc((string) ($r['total'] ?? '')) ?></td>
|
|
|
|
|
</tr>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
<?php if ($ledgerRows === []): ?>
|
|
|
|
|
<tr><td colspan="<?= ($mode ?? 'daily') === 'daily' ? '10' : '9' ?>" class="text-center text-gray-400 py-6">조회된 판매 데이터가 없습니다.</td></tr>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
<p class="text-sm text-gray-700 mt-2 mb-0 no-print">판매건수(상세 행): <?= number_format((int) ($saleLineCount ?? 0)) ?>건</p>
|
|
|
|
|
</section>
|
2026-03-25 18:29:31 +09:00
|
|
|
|
2026-06-01 16:15:15 +09:00
|
|
|
<script>
|
|
|
|
|
(function () {
|
|
|
|
|
const form = document.getElementById('sales-ledger-form');
|
|
|
|
|
const catAll = document.getElementById('cat-all');
|
|
|
|
|
const items = () => Array.from(document.querySelectorAll('.cat-item'));
|
|
|
|
|
if (!form || !catAll) return;
|
|
|
|
|
catAll.addEventListener('change', () => {
|
|
|
|
|
if (catAll.checked) items().forEach((el) => { el.checked = false; });
|
|
|
|
|
});
|
|
|
|
|
items().forEach((el) => {
|
|
|
|
|
el.addEventListener('change', () => {
|
|
|
|
|
if (el.checked) catAll.checked = false;
|
|
|
|
|
if (!items().some((x) => x.checked)) catAll.checked = true;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
form.addEventListener('submit', () => {
|
|
|
|
|
if (catAll.checked) items().forEach((el) => { el.checked = false; });
|
|
|
|
|
});
|
|
|
|
|
})();
|
|
|
|
|
</script>
|