사이트·관리자 봉투 물류 기능(수불·통계·레포트·재고·발주)과 DB·메뉴·E2E를 운영 반영한다.
통계 분석(전년대비·월별·계절별), 수급계획·LOT 수불, 지정판매소·실사·메뉴 링크 등을 포함한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,97 +1,258 @@
|
||||
<?= view('components/print_header', ['printTitle' => '판매 대장']) ?>
|
||||
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||
<?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">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<span class="text-sm font-bold text-gray-700">판매 대장</span>
|
||||
<button onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
<section class="p-2 bg-white border-b border-gray-200">
|
||||
<form method="GET" action="<?= mgmt_url('reports/sales-ledger') ?>" class="flex flex-wrap items-center gap-2">
|
||||
<label class="text-sm text-gray-600">시작일</label>
|
||||
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">~</label>
|
||||
<input type="date" name="end_date" value="<?= esc($endDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||
<label class="text-sm text-gray-600">조회방식</label>
|
||||
<select name="mode" class="border border-gray-300 rounded px-2 py-1 text-sm">
|
||||
<option value="daily" <?= ($mode ?? '') === 'daily' ? 'selected' : '' ?>>일자별</option>
|
||||
<option value="period" <?= ($mode ?? '') === 'period' ? 'selected' : '' ?>>기간별</option>
|
||||
</select>
|
||||
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||
|
||||
<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>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<?php if (($mode ?? 'daily') === 'daily'): ?>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>판매일</th>
|
||||
<th>판매소</th>
|
||||
<th>봉투코드</th>
|
||||
<th>봉투명</th>
|
||||
<th>구분</th>
|
||||
<th>수량</th>
|
||||
<th>금액</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-right">
|
||||
<?php foreach ($result as $row): ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= esc($row->bs_sale_date) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->bs_ds_name) ?></td>
|
||||
<td class="text-center font-mono"><?= esc($row->bs_bag_code) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->bs_bag_name) ?></td>
|
||||
<td class="text-center">
|
||||
<?php
|
||||
$typeMap = ['sale' => '판매', 'return' => '반품'];
|
||||
echo esc($typeMap[$row->bs_type] ?? $row->bs_type);
|
||||
?>
|
||||
</td>
|
||||
<td><?= number_format((int) $row->total_qty) ?></td>
|
||||
<td><?= number_format((int) $row->total_amount) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($result)): ?>
|
||||
<tr><td colspan="7" class="text-center text-gray-400 py-4">조회된 데이터가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<?php else: ?>
|
||||
<div class="border border-gray-300 overflow-auto mt-2">
|
||||
<table class="w-full data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>판매소</th>
|
||||
<th>봉투코드</th>
|
||||
<th>봉투명</th>
|
||||
<th>판매수량</th>
|
||||
<th>판매금액</th>
|
||||
<th>반품수량</th>
|
||||
<th>반품금액</th>
|
||||
<th>계(수량)</th>
|
||||
<th>계(금액)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-right">
|
||||
<?php foreach ($result as $row): ?>
|
||||
<tr>
|
||||
<td class="text-left pl-2"><?= esc($row->bs_ds_name) ?></td>
|
||||
<td class="text-center font-mono"><?= esc($row->bs_bag_code) ?></td>
|
||||
<td class="text-left pl-2"><?= esc($row->bs_bag_name) ?></td>
|
||||
<td><?= number_format((int) $row->sale_qty) ?></td>
|
||||
<td><?= number_format((int) $row->sale_amount) ?></td>
|
||||
<td><?= number_format((int) $row->return_qty) ?></td>
|
||||
<td><?= number_format((int) $row->return_amount) ?></td>
|
||||
<td class="font-bold"><?= number_format((int) $row->sale_qty - (int) $row->return_qty) ?></td>
|
||||
<td class="font-bold"><?= number_format((int) $row->sale_amount - (int) $row->return_amount) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($result)): ?>
|
||||
<tr><td colspan="9" class="text-center text-gray-400 py-4">조회된 데이터가 없습니다.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<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): ?>
|
||||
<?php
|
||||
$kind = (string) ($r['kind'] ?? 'data');
|
||||
$trClass = $kind === 'subtotal' ? 'bg-gray-50 font-semibold' : ($kind === 'grand' ? 'bg-amber-50 font-bold' : '');
|
||||
?>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user