사이트·관리자 봉투 물류 기능(수불·통계·레포트·재고·발주)과 DB·메뉴·E2E를 운영 반영한다.

통계 분석(전년대비·월별·계절별), 수급계획·LOT 수불, 지정판매소·실사·메뉴 링크 등을 포함한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
taekyoungc
2026-06-01 16:15:15 +09:00
parent 21e7b91871
commit 0f1d414f37
129 changed files with 18068 additions and 1585 deletions

View File

@@ -31,7 +31,6 @@
<th>봉투코드</th>
<th>봉투명</th>
<th>수량</th>
<th class="w-20">상태</th>
<th class="w-24">작업</th>
</tr>
</thead>
@@ -47,7 +46,6 @@
<td class="text-center font-mono"><?= esc($row->bi2_bag_code) ?></td>
<td class="text-left pl-2"><?= esc($row->bi2_bag_name) ?></td>
<td><?= number_format((int) $row->bi2_qty) ?></td>
<td class="text-center"><?= esc($row->bi2_status) ?></td>
<td class="text-center">
<form action="<?= mgmt_url('bag-issues/cancel/' . (int) $row->bi2_idx) ?>" method="POST" class="inline" onsubmit="return confirm('취소하시겠습니까?');">
<?= csrf_field() ?>
@@ -57,7 +55,7 @@
</tr>
<?php endforeach; ?>
<?php if (empty($list)): ?>
<tr><td colspan="11" class="text-center text-gray-400 py-4">등록된 불출이 없습니다.</td></tr>
<tr><td colspan="10" class="text-center text-gray-400 py-4">등록된 불출이 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>

View File

@@ -21,7 +21,7 @@
return $ym;
};
?>
<?= view('components/print_header', ['printTitle' => '봉투 발주 현황', 'printShowApproval' => false]) ?>
<?= view('components/print_header', ['printTitle' => '봉투 발주 현황']) ?>
<section class="no-print border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<div class="flex flex-wrap items-center justify-between gap-y-2">
<div class="flex items-center gap-2">

View File

@@ -1,4 +1,4 @@
<?= view('components/print_header', ['printTitle' => '봉투 단가 관리', 'printShowApproval' => false]) ?>
<?= view('components/print_header', ['printTitle' => '봉투 단가 관리']) ?>
<style>
@media print {
.no-print { display: none !important; }

View File

@@ -31,6 +31,13 @@ $totalPages = count($chunks);
</style>
</head>
<body>
<?= view('components/print_header', [
'printTitle' => '지정판매소 바코드',
'printExtraLines' => [
'구역: ' . $zoneLabel,
'출력일: ' . $printedAt,
],
]) ?>
<?php if ($rows === []): ?>
<div class="page">
<h1 class="title">지정판매소 바코드</h1>

View File

@@ -0,0 +1,87 @@
<?php
$readOnly = ! empty($readOnly);
$listBasePath = $readOnly ? 'designated-shops/browse' : 'designated-shops';
?>
<?= view('components/print_header', ['printTitle' => $readOnly ? '지정판매소 조회 목록' : '지정판매소 목록']) ?>
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<div class="flex flex-wrap items-center justify-between gap-y-2">
<span class="text-sm font-bold text-gray-700"><?= $readOnly ? '지정판매소 조회' : '지정판매소 관리' ?></span>
<div class="flex items-center gap-2">
<?php if ($readOnly): ?>
<a href="<?= mgmt_url('designated-shops/export') ?>" class="no-print border border-btn-excel-border text-btn-excel-text px-3 py-1 rounded-sm text-sm hover:bg-green-50 transition">엑셀저장</a>
<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>
<?php endif; ?>
<?php if (! $readOnly): ?>
<a href="<?= mgmt_url('designated-shops/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">지정판매소 등록</a>
<?php endif; ?>
</div>
</div>
</section>
<section class="p-2 bg-white border-b border-gray-200 no-print">
<form method="GET" action="<?= mgmt_url($listBasePath) ?>" class="flex flex-wrap items-center gap-2">
<span class="text-sm font-semibold text-gray-700 mr-1">지정판매소 검색</span>
<label class="text-sm text-gray-600">상호명</label>
<input type="text" name="ds_name" value="<?= esc($dsName ?? '') ?>" placeholder="상호명" class="border border-gray-300 rounded px-2 py-1 text-sm w-36"/>
<label class="text-sm text-gray-600">구군코드</label>
<select name="ds_gugun_code" class="border border-gray-300 rounded px-2 py-1 text-sm">
<option value="">전체</option>
<?php foreach (($gugunCodes ?? []) as $gc): ?>
<option value="<?= esc($gc->ds_gugun_code) ?>" <?= ($dsGugunCode ?? '') === $gc->ds_gugun_code ? 'selected' : '' ?>><?= esc($gc->ds_gugun_code) ?></option>
<?php endforeach; ?>
</select>
<label class="text-sm text-gray-600">상태</label>
<select name="ds_state" class="border border-gray-300 rounded px-2 py-1 text-sm">
<option value="">전체</option>
<option value="1" <?= ($dsState ?? '') === '1' ? 'selected' : '' ?>>정상</option>
<option value="2" <?= ($dsState ?? '') === '2' ? 'selected' : '' ?>>폐업</option>
<option value="3" <?= ($dsState ?? '') === '3' ? 'selected' : '' ?>>직권해지</option>
</select>
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
<a href="<?= mgmt_url($listBasePath) ?>" class="border border-gray-300 text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">초기화</a>
</form>
</section>
<div class="border border-gray-300 overflow-auto mt-2">
<table class="w-full data-table">
<thead>
<tr>
<th class="w-16">번호</th>
<th>지자체</th>
<th>판매소번호</th>
<th>상호명</th>
<th>대표자</th>
<th>사업자번호</th>
<th>가상계좌</th>
<th>상태</th>
<th>등록일</th>
<?php if (! $readOnly): ?>
<th class="w-28">작업</th>
<?php endif; ?>
</tr>
</thead>
<tbody class="text-right">
<?php foreach ($list as $row): ?>
<tr>
<td class="text-center"><?= esc($row->ds_idx) ?></td>
<td class="text-left pl-2"><?= esc($lgMap[$row->ds_lg_idx] ?? '') ?></td>
<td class="text-left pl-2"><?= esc($row->ds_shop_no) ?></td>
<td class="text-left pl-2"><?= esc($row->ds_name) ?></td>
<td class="text-left pl-2"><?= esc($row->ds_rep_name) ?></td>
<td class="text-left pl-2"><?= esc($row->ds_biz_no) ?></td>
<td class="text-left pl-2"><?= esc($row->ds_va_number) ?></td>
<td class="text-center"><?= (int) $row->ds_state === 1 ? '정상' : ((int) $row->ds_state === 2 ? '폐업' : '직권해지') ?></td>
<td class="text-left pl-2"><?= esc($row->ds_regdate ?? '') ?></td>
<?php if (! $readOnly): ?>
<td class="text-center">
<a href="<?= mgmt_url('designated-shops/edit/' . (int) $row->ds_idx) ?>" class="text-blue-600 hover:underline text-sm">수정</a>
<form action="<?= mgmt_url('designated-shops/delete/' . (int) $row->ds_idx) ?>" method="POST" class="inline ml-1" onsubmit="return confirm('이 지정판매소를 삭제하시겠습니까?');">
<?= csrf_field() ?>
<button type="submit" class="text-red-600 hover:underline text-sm">삭제</button>
</form>
</td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if (isset($pager)): ?><div class="mt-3"><?= $pager->links() ?></div><?php endif; ?>

View File

@@ -1,5 +1,5 @@
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<span class="text-sm font-bold text-gray-700">대상 등록</span>
<span class="text-sm font-bold text-gray-700">무료용 대상 등록</span>
</section>
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
<form action="<?= mgmt_url('free-recipients/store') ?>" method="POST" class="space-y-4">
@@ -9,29 +9,19 @@
<label class="block text-sm font-bold text-gray-700 w-28">구분 <span class="text-red-500">*</span></label>
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="fr_type_code" required>
<option value="">선택</option>
<?php foreach ($typeCodes as $cd): ?>
<option value="<?= esc($cd->cd_code) ?>" <?= old('fr_type_code') === $cd->cd_code ? 'selected' : '' ?>>
<?= esc($cd->cd_name) ?>
<?php foreach (($recipientTypeOptions ?? []) as $typeCode => $typeName): ?>
<option value="<?= esc((string) $typeCode) ?>" <?= old('fr_type_code') === (string) $typeCode ? 'selected' : '' ?>>
<?= esc((string) $typeName) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">대상자명 <span class="text-red-500">*</span></label>
<label class="block text-sm font-bold text-gray-700 w-28">명 <span class="text-red-500">*</span></label>
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="fr_name" type="text" value="<?= esc(old('fr_name')) ?>" required/>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">연락처</label>
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="fr_phone" type="text" value="<?= esc(old('fr_phone')) ?>"/>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">주소</label>
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96" name="fr_addr" type="text" value="<?= esc(old('fr_addr')) ?>"/>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">동코드</label>
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="fr_dong_code">
@@ -52,6 +42,7 @@
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">종료일</label>
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="fr_end_date" type="date" value="<?= esc(old('fr_end_date')) ?>"/>
<span class="text-xs text-gray-500">미입력 시 계속 유효</span>
</div>
<div class="flex gap-2 pt-2">

View File

@@ -1,5 +1,5 @@
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<span class="text-sm font-bold text-gray-700">대상 수정</span>
<span class="text-sm font-bold text-gray-700">무료용 대상 수정</span>
</section>
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
<form action="<?= mgmt_url('free-recipients/update/' . (int) $item->fr_idx) ?>" method="POST" class="space-y-4">
@@ -9,29 +9,19 @@
<label class="block text-sm font-bold text-gray-700 w-28">구분 <span class="text-red-500">*</span></label>
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="fr_type_code" required>
<option value="">선택</option>
<?php foreach ($typeCodes as $cd): ?>
<option value="<?= esc($cd->cd_code) ?>" <?= old('fr_type_code', $item->fr_type_code) === $cd->cd_code ? 'selected' : '' ?>>
<?= esc($cd->cd_name) ?>
<?php foreach (($recipientTypeOptions ?? []) as $typeCode => $typeName): ?>
<option value="<?= esc((string) $typeCode) ?>" <?= old('fr_type_code', $item->fr_type_code) === (string) $typeCode ? 'selected' : '' ?>>
<?= esc((string) $typeName) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">대상자명 <span class="text-red-500">*</span></label>
<label class="block text-sm font-bold text-gray-700 w-28">명 <span class="text-red-500">*</span></label>
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="fr_name" type="text" value="<?= esc(old('fr_name', $item->fr_name)) ?>" required/>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">연락처</label>
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="fr_phone" type="text" value="<?= esc(old('fr_phone', $item->fr_phone)) ?>"/>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">주소</label>
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-96" name="fr_addr" type="text" value="<?= esc(old('fr_addr', $item->fr_addr)) ?>"/>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">동코드</label>
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="fr_dong_code">
@@ -52,6 +42,7 @@
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">종료일</label>
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="fr_end_date" type="date" value="<?= esc(old('fr_end_date', $item->fr_end_date)) ?>"/>
<span class="text-xs text-gray-500">미입력 시 계속 유효</span>
</div>
<div class="flex flex-wrap items-center gap-2">

View File

@@ -13,24 +13,36 @@
<thead>
<tr>
<th class="w-16">번호</th>
<th>대상자명</th>
<th>연락처</th>
<th>주소</th>
<th>비고</th>
<th>종료일</th>
<th class="w-28">동코드</th>
<th class="w-40">구분</th>
<th>명칭</th>
<th class="w-28">종료일자</th>
<th class="w-48">비고</th>
<th class="w-20">상태</th>
<th class="w-36">작업</th>
</tr>
</thead>
<tbody>
<?php
$total = (int) ($totalCount ?? count($list));
$page = max(1, (int) ($currentPage ?? 1));
$size = max(1, (int) ($perPage ?? max(1, count($list))));
$rowNo = $total - (($page - 1) * $size);
?>
<?php foreach ($list as $row): ?>
<?php
$typeCode = (string) ($row->fr_type_code ?? '');
$typeName = (string) (($recipientTypeOptions[$typeCode] ?? '') ?: $typeCode);
$dongCode = (string) ($row->fr_dong_code ?? '');
$dongLabel = $dongCode !== '' ? (string) (($dongNameMap[$dongCode] ?? $dongCode) . ' (' . $dongCode . ')') : '-';
?>
<tr>
<td class="text-center"><?= esc($row->fr_idx) ?></td>
<td class="text-center"><?= esc((string) $rowNo) ?></td>
<td class="text-center"><?= esc($dongLabel) ?></td>
<td class="text-center"><?= esc($typeName) ?></td>
<td class="text-left pl-2"><?= esc($row->fr_name) ?></td>
<td class="text-center"><?= esc($row->fr_phone) ?></td>
<td class="text-left pl-2"><?= esc($row->fr_addr) ?></td>
<td class="text-center"><?= esc($row->fr_end_date ?: '9999.99.99') ?></td>
<td class="text-left pl-2"><?= esc($row->fr_note) ?></td>
<td class="text-center"><?= esc($row->fr_end_date) ?></td>
<td class="text-center"><?= (int) $row->fr_state === 1 ? '사용' : '미사용' ?></td>
<td class="text-center">
<a href="<?= mgmt_url('free-recipients/edit/' . (int) $row->fr_idx) ?>" class="text-blue-600 hover:underline text-sm mr-1">수정</a>
@@ -40,6 +52,7 @@
</form>
</td>
</tr>
<?php $rowNo--; ?>
<?php endforeach; ?>
<?php if (empty($list)): ?>
<tr>

View File

@@ -63,6 +63,11 @@ tailwind.config = {
}
}
</script>
<style data-purpose="global-font-scale">
/* 전체 텍스트 +2px 확대 (요청). 로고(.app-brand)는 16px 로 유지. */
html { font-size: 18px; }
.app-brand, .app-brand * { font-size: 16px; }
</style>
<style data-purpose="table-layout">
.data-table { width: 100%; border-collapse: collapse; font-family: 'Malgun Gothic', 'Noto Sans KR', sans-serif; }
.data-table th, .data-table td { border: 1px solid #ccc; padding: 4px 8px; white-space: nowrap; font-size: 13px; }

View File

@@ -8,11 +8,13 @@ $debugMode = (bool) ($debug_mode ?? false);
$debugInfo = is_array($debug_info ?? null) ? $debug_info : [];
helper('admin');
$adminMenusNavPath = current_nav_request_path();
// 사이트 메뉴(mt_code=site)는 업무 URL이 /bag/* — 관리 화면(admin/menus) 맥락으로 해석하면 admin/ 링크가 잘못 선택됨
$menuListResolvePath = ($mtCode === 'site') ? 'bag/dashboard' : $adminMenusNavPath;
/**
* 메뉴 관리 목록용: 저장된 mm_link → 실제 href (외부 http(s) 또는 base_url).
*/
$adminMenuListResolveHref = static function (string $rawLink) use ($adminMenusNavPath): string {
$adminMenuListResolveHref = static function (string $rawLink) use ($menuListResolvePath): string {
$rawLink = trim($rawLink);
if ($rawLink === '') {
return '';
@@ -20,7 +22,7 @@ $adminMenuListResolveHref = static function (string $rawLink) use ($adminMenusNa
if (preg_match('#^https?://#i', $rawLink)) {
return $rawLink;
}
$pathSeg = menu_link_preferred_href_path($rawLink, $adminMenusNavPath);
$pathSeg = menu_link_preferred_href_path($rawLink, $menuListResolvePath);
if ($pathSeg === '') {
$pathSeg = normalize_menu_link_for_url($rawLink);
}

View File

@@ -1,106 +1,140 @@
<?= 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<array<string,mixed>> $tableRows */
/** @var string $date */
/** @var string $monthStart */
/** @var int $saIdx */
/** @var string $catFilter */
/** @var list<object> $agencies */
/** @var array<string,string> $catLabels */
/** @var bool $hasBsFee */
/** @var string $lgName */
/** @var string $agencyLabel */
/** @var string $catLabelFilter */
/** @var list<string> $printExtraLines */
$exportParams = array_filter([
'date' => $date ?? '',
'sa_idx' => (int) ($saIdx ?? 0),
'cat' => (string) ($catFilter ?? ''),
'export' => '1',
], static fn ($v): bool => $v !== '' && $v !== null);
$excelUrl = mgmt_url('reports/daily-summary?' . http_build_query($exportParams));
?>
<?= view('components/print_header', [
'printTitle' => '일계표',
'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>
<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/daily-summary') ?>" class="flex flex-wrap items-center gap-2">
<label class="text-sm text-gray-600">조회일</label>
<input type="date" name="date" value="<?= esc($date ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
<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/daily-summary') ?>" class="flex flex-wrap items-end gap-3 text-sm">
<div>
<label class="block text-gray-600 mb-0.5">조회일자</label>
<input type="date" name="date" value="<?= esc($date ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
</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>
<label class="block text-gray-600 mb-0.5">구분</label>
<select name="cat" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[10rem]">
<option value="" <?= ($catFilter ?? '') === '' ? 'selected' : '' ?>>전체</option>
<?php foreach (($catLabels ?? []) as $ck => $lab): ?>
<option value="<?= esc($ck, 'attr') ?>" <?= ($catFilter ?? '') === $ck ? 'selected' : '' ?>><?= esc($lab) ?></option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm">조회</button>
</form>
</section>
<div class="flex gap-4 mt-2">
<!-- 당일 -->
<div class="flex-1 border border-gray-300 overflow-auto">
<div class="bg-gray-100 border-b border-gray-300 px-3 py-1.5">
<span class="text-sm font-bold text-gray-700">당일 (<?= esc($date ?? '') ?>)</span>
</div>
<table class="w-full data-table">
<thead>
<tr>
<th>봉투코드</th>
<th>봉투명</th>
<th>판매수량</th>
<th>판매금액</th>
</tr>
</thead>
<tbody class="text-right">
<?php
$dailySaleQtyTotal = 0;
$dailySaleAmountTotal = 0;
?>
<?php foreach ($daily as $row): ?>
<?php
$dailySaleQtyTotal += (int) $row->sale_qty;
$dailySaleAmountTotal += (int) $row->sale_amount;
?>
<tr>
<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>
</tr>
<?php endforeach; ?>
<?php if (empty($daily)): ?>
<tr><td colspan="4" class="text-center text-gray-400 py-4">조회된 데이터가 없습니다.</td></tr>
<?php endif; ?>
</tbody>
<tfoot class="bg-gray-50 font-bold text-right">
<tr>
<td colspan="2" class="text-center">합계</td>
<td><?= number_format($dailySaleQtyTotal) ?></td>
<td><?= number_format($dailySaleAmountTotal) ?></td>
</tr>
</tfoot>
</table>
<section class="p-3 bg-white">
<style>
@media print {
.daily-summary-screen-title { display: none !important; }
}
</style>
<div class="mb-2 text-center daily-summary-screen-title no-print">
<h1 class="text-lg font-bold m-0">일계표</h1>
<p class="text-sm text-gray-700 m-1"><?= esc(trim(($lgName ?? '') . ' · 조회일: ' . ($date ?? '') . ' · 대행소: ' . ($agencyLabel ?? '') . ' · 구분: ' . ($catLabelFilter ?? ''))) ?></p>
<p class="text-xs text-gray-500 m-0">누계(월): <?= esc(($monthStart ?? '') . ' ~ ' . ($date ?? '')) ?> · (단위: 매 / 원)</p>
</div>
<!-- 당월 누계 -->
<div class="flex-1 border border-gray-300 overflow-auto">
<div class="bg-gray-100 border-b border-gray-300 px-3 py-1.5">
<span class="text-sm font-bold text-gray-700">당월 누계 (<?= esc($monthStart ?? '') ?> ~ <?= esc($date ?? '') ?>)</span>
</div>
<table class="w-full data-table">
<div class="border border-gray-300 overflow-auto">
<table class="w-full data-table text-sm" id="daily-summary-table">
<thead>
<tr>
<th>봉투코드</th>
<th>봉투</th>
<th>판매수량</th>
<th>판매금액</th>
<th rowspan="2" class="align-middle">구분</th>
<th rowspan="2" class="align-middle">봉투종류</th>
<th colspan="4" class="text-center border-l border-gray-300">일계</th>
<th colspan="4" class="text-center border-l border-gray-300">누계(월)</th>
</tr>
<tr>
<th class="text-right border-l border-gray-300">수량</th>
<th class="text-right">판매금액</th>
<th class="text-right">수수료</th>
<th class="text-right">징수액</th>
<th class="text-right border-l border-gray-300">수량</th>
<th class="text-right">판매금액</th>
<th class="text-right">수수료</th>
<th class="text-right">징수액</th>
</tr>
</thead>
<tbody class="text-right">
<?php
$monthlySaleQtyTotal = 0;
$monthlySaleAmountTotal = 0;
?>
<?php foreach ($monthly as $row): ?>
<?php
$monthlySaleQtyTotal += (int) $row->sale_qty;
$monthlySaleAmountTotal += (int) $row->sale_amount;
?>
<tr>
<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>
</tr>
<?php foreach ($tableRows ?? [] as $r): ?>
<?php
$kind = (string) ($r['kind'] ?? 'data');
$trClass = $kind === 'subtotal' ? 'bg-gray-50 font-semibold' : ($kind === 'grand' ? 'bg-amber-50 font-bold' : '');
$fmtFee = static function (float $v) use ($hasBsFee): string {
if (! $hasBsFee) {
return '—';
}
return $v != 0.0 ? number_format((int) round($v)) : '';
};
?>
<tr class="<?= esc($trClass, 'attr') ?>">
<?php if ($kind === 'grand'): ?>
<td colspan="2" class="text-center"><?= esc((string) ($r['bag_name'] ?? '합 계')) ?></td>
<?php else: ?>
<td class="text-left pl-2"><?= esc((string) ($r['cat_label'] ?? '')) ?></td>
<td class="text-left pl-2"><?= esc((string) ($r['bag_name'] ?? '')) ?></td>
<?php endif; ?>
<td class="border-l border-gray-200 tabular-nums"><?= number_format((int) ($r['d_qty'] ?? 0)) ?></td>
<td class="tabular-nums"><?= number_format((int) round((float) ($r['d_amt'] ?? 0))) ?></td>
<td class="tabular-nums"><?= $fmtFee((float) ($r['d_fee'] ?? 0)) ?></td>
<td class="tabular-nums"><?= number_format((int) round((float) ($r['d_levy'] ?? 0))) ?></td>
<td class="border-l border-gray-200 tabular-nums"><?= number_format((int) ($r['m_qty'] ?? 0)) ?></td>
<td class="tabular-nums"><?= number_format((int) round((float) ($r['m_amt'] ?? 0))) ?></td>
<td class="tabular-nums"><?= $fmtFee((float) ($r['m_fee'] ?? 0)) ?></td>
<td class="tabular-nums"><?= number_format((int) round((float) ($r['m_levy'] ?? 0))) ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($monthly)): ?>
<tr><td colspan="4" class="text-center text-gray-400 py-4">조회된 데이터가 없습니다.</td></tr>
<?php if (($tableRows ?? []) === []): ?>
<tr>
<td colspan="10" class="text-center text-gray-400 py-6">조회된 데이터가 없습니다.</td>
</tr>
<?php endif; ?>
</tbody>
<tfoot class="bg-gray-50 font-bold text-right">
<tr>
<td colspan="2" class="text-center">합계</td>
<td><?= number_format($monthlySaleQtyTotal) ?></td>
<td><?= number_format($monthlySaleAmountTotal) ?></td>
</tr>
</tfoot>
</table>
</div>
</div>
</section>

View File

@@ -0,0 +1,322 @@
<?php
declare(strict_types=1);
/** @var string $startDate */
/** @var string $endDate */
/** @var string $writeDate */
/** @var bool $searched */
/** @var list<string> $headers */
/** @var list<list<string>> $displayRows */
/** @var int $totalCount */
/** @var float $totalSupplyAmount */
/** @var float $totalTaxAmount */
/** @var int $missingBizCount */
/** @var string $lgName */
/** @var list<string> $printExtraLines */
/** @var list<array{label: string, sheet_name: string, cols: list<int>}> $hometaxPrintPages */
/** @var list<int> $hometaxColMinPx */
helper('admin');
$baseParams = [
'start_date' => $startDate ?? '',
'end_date' => $endDate ?? '',
'write_date' => $writeDate ?? '',
];
$searchParams = array_merge($baseParams, ['search' => '1']);
$exportParams = array_merge($searchParams, ['export' => '1']);
$searchUrl = mgmt_url('reports/hometax-export?' . http_build_query($searchParams));
$excelUrl = mgmt_url('reports/hometax-export?' . http_build_query($exportParams));
$totalGrand = (float) ($totalSupplyAmount ?? 0) + (float) ($totalTaxAmount ?? 0);
$colCount = max(1, count($headers ?? []));
/** 홈택스 28열 — 주소·상호·이메일 등 텍스트 열을 넓게 (합계 100%) */
$hometaxColWidths = [
'4.5%', '4%', '4%', '6%', '3%', '6.5%', '3.5%', '11%', '3%', '3%', '5.5%',
'6%', '3%', '6.5%', '3.5%', '11%', '3%', '3%', '5.5%',
'4%', '4%', '3%', '5.5%', '3%', '3%', '3.5%', '3.5%', '3.5%',
];
$hometaxColMinPx = $hometaxColMinPx ?? [];
$hometaxPrintPages = $hometaxPrintPages ?? [];
$hometaxWrapColIdx = [7, 15, 5, 6, 13, 14, 10, 18, 22, 27];
$hometaxNumColIdx = [19, 20, 24, 25, 26, 27];
$hometaxNormalizeColWidths = static function (array $colIndices) use ($hometaxColWidths): array {
$sum = 0.0;
foreach ($colIndices as $ci) {
$sum += (float) str_replace('%', '', (string) ($hometaxColWidths[$ci] ?? '3'));
}
$normalized = [];
foreach ($colIndices as $ci) {
$pct = (float) str_replace('%', '', (string) ($hometaxColWidths[$ci] ?? '3'));
$normalized[$ci] = ($sum > 0 ? round($pct / $sum * 100, 2) : round(100 / max(1, count($colIndices)), 2)) . '%';
}
return $normalized;
};
$hometaxCellClass = static function (int $ci) use ($hometaxWrapColIdx, $hometaxNumColIdx): string {
$class = 'text-left px-1 py-1';
if (in_array($ci, $hometaxWrapColIdx, true)) {
$class .= ' ht-wrap';
}
if (in_array($ci, $hometaxNumColIdx, true)) {
$class .= ' ht-num';
}
return $class;
};
/**
* @param list<int> $colIndices
*/
$hometaxRenderTable = static function (
array $colIndices,
string $tableExtraClass,
string $tableId,
bool $forPrint
) use (
$headers,
$displayRows,
$searched,
$colCount,
$hometaxColWidths,
$hometaxColMinPx,
$hometaxCellClass,
$hometaxNormalizeColWidths
): void {
$sliceCount = count($colIndices);
$widthsForSet = $hometaxNormalizeColWidths($colIndices);
?>
<table
class="w-full data-table text-xs <?= esc($tableExtraClass, 'attr') ?>"
id="<?= esc($tableId, 'attr') ?>"
<?= $forPrint ? 'data-hometax-print="1"' : '' ?>
>
<colgroup>
<?php foreach ($colIndices as $ci):
$wPct = $widthsForSet[$ci] ?? (string) round(100 / max(1, $sliceCount), 2) . '%';
$wPx = (int) ($hometaxColMinPx[$ci] ?? 56);
?>
<col style="width: <?= esc($wPct, 'attr') ?>;<?= $forPrint ? '' : ' min-width: ' . $wPx . 'px' ?>"/>
<?php endforeach; ?>
</colgroup>
<thead>
<tr>
<?php foreach ($colIndices as $ci): ?>
<th class="<?= esc($hometaxCellClass($ci), 'attr') ?>"><?= esc((string) ($headers[$ci] ?? '')) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody class="text-right">
<?php if (! ($searched ?? true)): ?>
<tr>
<td colspan="<?= (int) $sliceCount ?>" class="text-center text-gray-500 py-8">조회를 건너뛴 상태입니다. <strong>조회</strong>를 눌러 주세요.</td>
</tr>
<?php elseif (($displayRows ?? []) === []): ?>
<tr>
<td colspan="<?= (int) $sliceCount ?>" class="text-center text-gray-500 py-8">조회된 판매 내역이 없습니다.</td>
</tr>
<?php else: ?>
<?php foreach ($displayRows as $row): ?>
<tr>
<?php foreach ($colIndices as $ci):
$tdClass = 'tabular-nums text-left px-1 py-0.5 border-t border-gray-100 ' . $hometaxCellClass($ci);
?>
<td class="<?= esc(trim($tdClass), 'attr') ?>"><?= esc((string) ($row[$ci] ?? '')) ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<?php
};
?>
<?= view('components/print_header', [
'printTitle' => '홈택스 처리',
'printExtraLines' => $printExtraLines ?? [],
]) ?>
<section class="border-b border-gray-300 p-3 shrink-0 bg-control-panel no-print">
<div class="flex flex-wrap items-center justify-between gap-2 mb-2">
<h1 class="text-base font-bold text-gray-800">홈택스 처리</h1>
<div class="flex flex-wrap gap-2 items-center">
<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>
<form method="get" action="<?= esc(mgmt_url('reports/hometax-export'), 'attr') ?>" id="hometax-process-form" class="flex flex-wrap items-end gap-3 text-sm">
<input type="hidden" name="search" value="1"/>
<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 class="text-gray-500">~</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>
<input type="date" name="write_date" value="<?= esc($writeDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
</div>
<div class="pt-5">
<button type="submit" class="border border-blue-600 bg-blue-50 text-blue-800 px-4 py-1 rounded-sm text-sm font-medium hover:bg-blue-100 transition">조회</button>
</div>
</form>
</section>
<section class="p-3 bg-white border-b border-gray-200 hometax-report-section">
<style>
.hometax-print-only { display: none; }
@media print {
@page {
size: A4 landscape;
margin: 4mm 5mm;
}
.hometax-report-section {
padding: 0 !important;
border: none !important;
}
.hometax-screen-only {
display: none !important;
}
.hometax-print-only {
display: block !important;
}
.hometax-print-page {
page-break-after: always;
break-after: page;
}
.hometax-print-page:last-child {
page-break-after: auto;
break-after: auto;
}
.hometax-print-page-label {
font-size: 8pt;
font-weight: 700;
margin: 0 0 4px;
}
.hometax-print-scroll {
overflow: visible !important;
border: 1px solid #333 !important;
}
.hometax-print-table {
font-size: 8pt !important;
width: 100% !important;
table-layout: fixed !important;
}
.hometax-print-table th,
.hometax-print-table td {
padding: 3px 5px !important;
white-space: normal !important;
word-break: break-word;
overflow-wrap: break-word;
line-height: 1.3;
vertical-align: top;
}
.hometax-print-table th {
font-size: 7.5pt !important;
font-weight: 600;
}
.hometax-print-table .ht-num {
white-space: nowrap !important;
word-break: normal !important;
}
.hometax-print-table thead {
display: table-header-group;
}
.hometax-print-table tr {
page-break-inside: avoid;
break-inside: avoid;
}
}
@media screen {
#hometax-result-table {
width: max(100%, 4200px);
min-width: 4200px;
table-layout: fixed;
}
#hometax-result-table th,
#hometax-result-table td {
white-space: nowrap;
padding: 4px 6px !important;
}
#hometax-result-table .ht-wrap {
white-space: normal;
word-break: break-word;
max-width: 220px;
}
}
</style>
<div class="text-sm font-semibold text-gray-700 mb-2 no-print">조회결과</div>
<div class="hometax-screen-only hometax-scroll-wrap overflow-x-auto border border-gray-300" style="max-width: 100%;">
<?php
$hometaxRenderTable(
range(0, max(0, $colCount - 1)),
'',
'hometax-result-table',
false
);
?>
</div>
<div class="hometax-print-only" aria-hidden="true">
<?php foreach ($hometaxPrintPages as $ppi => $page):
$pageCols = $page['cols'];
if ($pageCols === []) {
continue;
}
?>
<section class="hometax-print-page">
<p class="hometax-print-page-label"><?= esc((string) ($page['label'] ?? '')) ?></p>
<div class="hometax-print-scroll">
<?php
$hometaxRenderTable(
$pageCols,
'hometax-print-table',
'hometax-print-table-' . (int) $ppi,
true
);
?>
</div>
</section>
<?php endforeach; ?>
</div>
<div class="mt-4 flex flex-wrap gap-6 text-sm border-t border-gray-200 pt-3 no-print">
<div><span class="text-gray-600">총 건수</span> <strong class="tabular-nums"><?= (int) ($totalCount ?? 0) ?></strong> 건</div>
<div><span class="text-gray-600">총 금액</span> <strong class="tabular-nums"><?= esc(number_format((int) round($totalGrand))) ?></strong> 원 <span class="text-gray-400 text-xs">(공급가액+세액)</span></div>
<div><span class="text-gray-600">사업자등록번호 없음</span> <strong class="tabular-nums text-amber-800"><?= (int) ($missingBizCount ?? 0) ?></strong> 건</div>
</div>
<div class="mt-2 text-xs text-gray-500 no-print print:hidden">
인쇄·엑셀저장은 동일하게 2쪽 열 구성입니다(1쪽: 공급자·공급받는자, 2쪽: 금액·품목). 요약·결재란은 인쇄용 헤더에 포함됩니다.
</div>
</section>
<style media="print">
.no-print { display: none !important; }
</style>
<script>
(function () {
let savedTitle = document.title;
function stamp() {
const d = new Date();
const p = (n) => String(n).padStart(2, '0');
return d.getFullYear() + p(d.getMonth() + 1) + p(d.getDate()) + '_' + p(d.getHours()) + p(d.getMinutes()) + p(d.getSeconds());
}
window.addEventListener('beforeprint', function () {
savedTitle = document.title;
document.title = '홈택스처리_' + stamp();
});
window.addEventListener('afterprint', function () {
document.title = savedTitle;
});
})();
</script>

View File

@@ -1,99 +1,216 @@
<?= view('components/print_header', ['printTitle' => 'LOT 수불 조회']) ?>
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<?php
$barcode = (string) ($barcode ?? '');
$lotNo = (string) ($lotNo ?? '');
$result = is_array($result ?? null) ? $result : [];
$queried = (bool) ($queried ?? false);
$ok = (bool) ($result['ok'] ?? false);
$message = (string) ($result['message'] ?? '');
$rows = is_array($result['rows'] ?? null) ? $result['rows'] : [];
$unit = (string) ($result['unit'] ?? '');
$bagName = (string) ($result['bag_name'] ?? '');
$bagCode = (string) ($result['bag_code'] ?? '');
$lotLabel = (string) ($result['lot_no'] ?? $lotNo);
$qtyBox = (int) ($result['qty_box'] ?? 0);
$qtyPack = (int) ($result['qty_pack'] ?? 0);
$qtySheet = (int) ($result['qty_sheet'] ?? 0);
$testSamples = is_array($testSamples ?? null) ? $testSamples : [];
$printExtra = [];
if ($queried && $barcode !== '') {
$printExtra[] = '봉투번호(바코드): ' . $barcode;
}
if ($bagName !== '' || $bagCode !== '') {
$printExtra[] = '품목: ' . trim($bagName . ($bagCode !== '' ? ' (' . $bagCode . ')' : ''));
}
?>
<?= view('components/print_header', [
'printTitle' => 'LOT 수불 조회',
'printExtraLines' => $printExtra,
]) ?>
<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">LOT 수불 조회</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>
</div>
</section>
<section class="p-2 bg-white border-b border-gray-200">
<form method="GET" action="<?= mgmt_url('reports/lot-flow') ?>" class="flex flex-wrap items-center gap-2">
<label class="text-sm text-gray-600">LOT 번호</label>
<input type="text" name="lot_no" value="<?= esc($lotNo ?? '') ?>" placeholder="LOT-YYYYMMDD-XXXXXX" class="border border-gray-300 rounded px-2 py-1 text-sm w-64"/>
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
</form>
</section>
<?php if ($lotNo !== '' && $order): ?>
<!-- 발주 정보 -->
<div class="border border-gray-300 p-3 mt-2 bg-gray-50">
<h3 class="text-sm font-bold text-gray-700 mb-2">발주 정보</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-2 text-sm">
<div><span class="text-gray-500">LOT번호:</span> <span class="font-mono"><?= esc($order->bo_lot_no) ?></span></div>
<div><span class="text-gray-500">발주일:</span> <?= esc($order->bo_order_date) ?></div>
<div><span class="text-gray-500">상태:</span>
<?php $statusMap = ['normal' => '정상', 'cancelled' => '취소', 'deleted' => '삭제']; ?>
<?= esc($statusMap[$order->bo_status] ?? $order->bo_status) ?>
<div class="flex flex-wrap items-center 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">인쇄</button>
<a href="<?= base_url('dashboard') ?>" class="border border-gray-400 text-gray-700 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">종료</a>
</div>
</div>
</section>
<section class="p-2 bg-white border-b border-gray-200 no-print text-sm">
<form method="get" action="<?= mgmt_url('reports/lot-flow') ?>" class="flex flex-wrap items-end gap-x-4 gap-y-2">
<input type="hidden" name="search" value="1"/>
<div class="flex flex-wrap items-center gap-2">
<label class="font-bold text-gray-700 whitespace-nowrap">봉투번호</label>
<input type="text" name="barcode" id="lot-flow-barcode-input" value="<?= esc($barcode) ?>"
placeholder="바코드·팩·박스·낱장 코드 입력"
class="border border-gray-300 rounded px-2 py-1 w-80 font-mono text-sm" autocomplete="off"/>
<span class="text-gray-500 text-xs">(바코드 스캔 = 번호 직접 입력)</span>
</div>
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm">조회</button>
</form>
<p class="text-xs text-gray-500 mt-1">
팩·박스·낱장 바코드 또는 LOT 번호(보조: <code class="text-xs">lot_no</code> 파라미터)로 조회합니다.
</p>
</section>
<?php if ($queried && ! $ok && $message !== ''): ?>
<div class="m-2 p-3 border border-amber-300 bg-amber-50 text-sm text-amber-900 no-print">
<?= esc($message) ?>
</div>
<?php endif; ?>
<?php if (! $queried): ?>
<div class="m-2 p-3 border border-blue-200 bg-blue-50 text-sm text-blue-900 no-print">
봉투번호(바코드)를 입력한 뒤 <strong>조회</strong>를 눌러 주세요.
</div>
<?php endif; ?>
<section class="mx-2 mb-2 border border-amber-400 bg-amber-50/80 rounded-sm no-print" id="lot-flow-test-samples">
<div class="px-3 py-2 border-b border-amber-300 bg-amber-100/80">
<strong class="text-amber-950 text-sm">[개발용 임시] 등록·조회 가능 봉투번호 샘플</strong>
<span class="text-xs text-amber-800 ml-2">행 클릭 → 봉투번호 입력 후 조회 · 현재 지자체 DB 기준</span>
</div>
<div class="overflow-auto max-h-48">
<table class="w-full text-xs data-table">
<thead>
<tr>
<th class="w-14 text-center">구분</th>
<th>봉투번호(입력값)</th>
<th class="w-28">품목</th>
<th class="w-24">LOT</th>
<th class="w-14 text-center">상태</th>
<th class="w-32">비고</th>
</tr>
</thead>
<tbody>
<?php if ($testSamples === []): ?>
<tr>
<td colspan="6" class="text-center text-gray-500 py-4">
<code>bag_receiving_pack_code</code> 데이터가 없습니다. 입고 처리 후 표시됩니다.
</td>
</tr>
<?php endif; ?>
<?php foreach ($testSamples as $sample): ?>
<tr class="lot-flow-sample-row cursor-pointer hover:bg-amber-100"
data-code="<?= esc((string) ($sample['code'] ?? ''), 'attr') ?>"
title="클릭하여 조회">
<td class="text-center"><?= esc((string) ($sample['kind'] ?? '')) ?></td>
<td class="font-mono text-left pl-2 break-all"><?= esc((string) ($sample['code'] ?? '')) ?></td>
<td class="text-left pl-1 truncate max-w-[7rem]" title="<?= esc((string) ($sample['bag_name'] ?? ''), 'attr') ?>">
<?= esc((string) ($sample['bag_name'] ?? '')) ?>
</td>
<td class="font-mono text-left pl-1 truncate max-w-[6rem]"><?= esc((string) ($sample['lot_no'] ?? '')) ?></td>
<td class="text-center"><?= esc((string) ($sample['state'] ?? '')) ?></td>
<td class="text-gray-600 pl-1"><?= esc((string) ($sample['hint'] ?? '')) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</section>
<div class="lot-flow-layout m-2 flex flex-col lg:flex-row gap-3 min-h-[320px]">
<!-- 좌: 품목·단위 요약 (레거시 BOX/PACK/낱장) -->
<div class="lot-flow-summary border border-gray-300 bg-gray-50 p-3 lg:w-64 shrink-0">
<h3 class="text-sm font-bold text-gray-700 mb-2">봉투 정보</h3>
<?php if ($ok): ?>
<dl class="text-sm space-y-1.5">
<div><dt class="text-gray-500 inline">품목</dt>
<dd class="font-medium"><?= esc($bagName !== '' ? $bagName : '-') ?></dd></div>
<?php if ($bagCode !== ''): ?>
<div><dt class="text-gray-500 inline">코드</dt>
<dd class="font-mono text-xs"><?= esc($bagCode) ?></dd></div>
<?php endif; ?>
<?php if ($lotLabel !== ''): ?>
<div><dt class="text-gray-500 inline">LOT</dt>
<dd class="font-mono text-xs break-all"><?= esc($lotLabel) ?></dd></div>
<?php endif; ?>
<?php if ($unit !== ''): ?>
<div><dt class="text-gray-500 inline">조회단위</dt>
<dd><?= esc($unit) ?></dd></div>
<?php endif; ?>
</dl>
<div class="grid grid-cols-3 gap-2 mt-4 text-center text-xs">
<div class="border border-gray-300 bg-white rounded p-2">
<div class="text-gray-500 font-bold">BOX</div>
<div class="text-lg font-semibold tabular-nums"><?= $qtyBox > 0 ? number_format($qtyBox) : '—' ?></div>
</div>
<div class="border border-gray-300 bg-white rounded p-2">
<div class="text-gray-500 font-bold">PACK</div>
<div class="text-lg font-semibold tabular-nums"><?= $qtyPack > 0 ? number_format($qtyPack) : '—' ?></div>
</div>
<div class="border border-gray-300 bg-white rounded p-2">
<div class="text-gray-500 font-bold">낱장</div>
<div class="text-lg font-semibold tabular-nums"><?= $qtySheet > 0 ? number_format($qtySheet) : '—' ?></div>
</div>
</div>
<?php elseif ($queried): ?>
<p class="text-sm text-gray-500">조회 결과 없음</p>
<?php else: ?>
<p class="text-sm text-gray-400">조회 후 표시</p>
<?php endif; ?>
</div>
<!-- 우: LOT 수불 현황 -->
<div class="lot-flow-table-wrap flex-1 border border-gray-300 flex flex-col min-w-0">
<div class="bg-gray-100 border-b border-gray-300 px-3 py-1.5">
<span class="text-sm font-bold text-gray-700">LOT 수불 현황</span>
</div>
<div class="overflow-auto flex-1">
<table class="w-full data-table text-sm">
<thead>
<tr>
<th class="w-28">일자</th>
<th>입출고처</th>
<th class="w-24 text-center">구분</th>
</tr>
</thead>
<tbody>
<?php if ($queried && $ok && $rows === []): ?>
<tr>
<td colspan="3" class="text-center text-gray-500 py-8">수불 이력이 없습니다.</td>
</tr>
<?php endif; ?>
<?php if (! $queried): ?>
<tr>
<td colspan="3" class="text-center text-gray-400 py-8">봉투번호 입력 후 조회</td>
</tr>
<?php endif; ?>
<?php foreach ($rows as $row): ?>
<tr>
<td class="text-center"><?= esc((string) ($row['flow_date'] ?? '')) ?></td>
<td class="text-left pl-2"><?= esc((string) ($row['counterparty'] ?? '')) ?></td>
<td class="text-center"><?= esc((string) ($row['flow_type'] ?? '')) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div><span class="text-gray-500">등록일:</span> <?= esc($order->bo_regdate) ?></div>
</div>
</div>
<!-- 발주 품목 -->
<h3 class="text-sm font-bold text-gray-700 mt-3 mb-1">발주 품목</h3>
<div class="border border-gray-300 overflow-auto">
<table class="w-full data-table">
<thead>
<tr>
<th>봉투코드</th>
<th>봉투명</th>
<th>발주수량(박스)</th>
<th>발주수량(매)</th>
<th>단가</th>
<th>금액</th>
</tr>
</thead>
<tbody class="text-right">
<?php foreach ($items as $item): ?>
<tr>
<td class="text-center font-mono"><?= esc($item->boi_bag_code) ?></td>
<td class="text-left pl-2"><?= esc($item->boi_bag_name) ?></td>
<td><?= number_format((int) $item->boi_qty_box) ?></td>
<td><?= number_format((int) $item->boi_qty_sheet) ?></td>
<td><?= number_format((int) $item->boi_unit_price) ?></td>
<td><?= number_format((int) $item->boi_amount) ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($items)): ?>
<tr><td colspan="6" class="text-center text-gray-400 py-4">품목이 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<script>
(function () {
const input = document.getElementById('lot-flow-barcode-input');
const form = input?.closest('form');
document.querySelectorAll('.lot-flow-sample-row').forEach((row) => {
row.addEventListener('click', () => {
const code = row.getAttribute('data-code') || '';
if (!input || !form || code === '') return;
input.value = code;
form.submit();
});
});
})();
</script>
<!-- 입고 내역 -->
<h3 class="text-sm font-bold text-gray-700 mt-3 mb-1">입고 내역</h3>
<div class="border border-gray-300 overflow-auto">
<table class="w-full data-table">
<thead>
<tr>
<th>입고일</th>
<th>봉투코드</th>
<th>봉투명</th>
<th>입고수량(박스)</th>
<th>입고수량(매)</th>
<th>납품자</th>
</tr>
</thead>
<tbody class="text-right">
<?php foreach ($receivings as $recv): ?>
<tr>
<td class="text-center"><?= esc($recv->br_receive_date) ?></td>
<td class="text-center font-mono"><?= esc($recv->br_bag_code) ?></td>
<td class="text-left pl-2"><?= esc($recv->br_bag_name) ?></td>
<td><?= number_format((int) $recv->br_qty_box) ?></td>
<td><?= number_format((int) $recv->br_qty_sheet) ?></td>
<td class="text-left pl-2"><?= esc($recv->br_sender_name ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($receivings)): ?>
<tr><td colspan="6" class="text-center text-gray-400 py-4">입고 내역이 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<?php elseif ($lotNo !== '' && !$order): ?>
<div class="border border-gray-300 p-4 mt-2 text-center text-gray-400">해당 LOT 번호의 발주를 찾을 수 없습니다.</div>
<?php else: ?>
<div class="border border-gray-300 p-4 mt-2 text-center text-gray-400">LOT 번호를 입력하고 조회해 주세요.</div>
<?php endif; ?>
<style>
@media print {
.no-print { display: none !important; }
#lot-flow-test-samples { display: none !important; }
.lot-flow-layout { margin: 0; flex-direction: row; }
}
</style>

View File

@@ -1,84 +1,369 @@
<?= view('components/print_header', ['printTitle' => '기타 입출고']) ?>
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<?php
$filters = is_array($filters ?? null) ? $filters : [];
$flowYear = (string) ($filters['flow_y'] ?? '');
$flowMonthNum = (string) ($filters['flow_m'] ?? '');
$dateYearMin = (int) ($dateYearMin ?? ((int) date('Y') - 12));
$dateYearMax = (int) ($dateYearMax ?? ((int) date('Y') + 2));
$bagCodeFilter = (string) ($filters['bag_code'] ?? '');
$bagKind = (string) ($filters['bag_kind'] ?? '');
$bagCancelOnly = ! empty($filters['bag_cancel']);
$selKey = (string) ($filters['sel_key'] ?? '');
$selectedGroup = is_array($selectedGroup ?? null) ? $selectedGroup : null;
$detailLines = is_array($detailLines ?? null) ? $detailLines : [];
$groupList = is_array($groupList ?? null) ? $groupList : [];
$packagingMap = is_array($packagingMap ?? null) ? $packagingMap : [];
$bagKindOptions = is_array($bagKindOptions ?? null) ? $bagKindOptions : [];
$bagCodes = is_array($bagCodes ?? null) ? $bagCodes : [];
$miscFlowListUrl = static function (array $extra = []) use ($filters): string {
$qs = array_merge($filters, $extra);
unset($qs['sel_key']);
if (isset($extra['sel_key'])) {
$qs['sel_key'] = $extra['sel_key'];
}
return mgmt_url('reports/misc-flow') . ($qs !== [] ? '?' . http_build_query($qs) : '');
};
$detailTotalQty = 0;
foreach ($detailLines as $line) {
$detailTotalQty += (int) ($line->bmf_qty ?? 0);
}
$registerDate = $selectedGroup ? (string) ($selectedGroup['date'] ?? date('Y-m-d')) : date('Y-m-d');
$registerType = $selectedGroup ? (string) ($selectedGroup['type'] ?? 'in') : 'in';
$registerReason = $selectedGroup ? (string) ($selectedGroup['reason'] ?? '') : '';
?>
<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>
<div class="flex flex-wrap items-center 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">인쇄</button>
<a href="<?= esc(work_area_home_url()) ?>" class="border border-gray-400 text-gray-700 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">종료</a>
</div>
</div>
</section>
<?php if (!($tableExists ?? false)): ?>
<div class="border border-orange-300 bg-orange-50 p-3 mt-2 text-sm text-orange-700">
<?php if (! ($tableExists ?? false)): ?>
<div class="border border-orange-300 bg-orange-50 p-3 m-2 text-sm text-orange-700 no-print">
bag_misc_flow 테이블이 생성되지 않았습니다. <code>writable/database/bag_misc_flow_tables.sql</code>을 실행해 주세요.
</div>
<?php elseif (($totalRowsForLg ?? 0) === 0): ?>
<div class="border border-blue-200 bg-blue-50 p-3 m-2 text-sm text-blue-900 no-print">
선택한 지자체에 등록된 <strong>기타 입출고</strong> 데이터가 없습니다. 아래 <strong>품목 등록</strong>으로 첫 건을 넣으면 좌측 리스트에 표시됩니다.
</div>
<?php elseif (($groupList ?? []) === [] && ($fetchedRowCount ?? 0) === 0 && (($flowYear ?? '') !== '' || ($flowMonthNum ?? '') !== '' || ($bagCodeFilter ?? '') !== '' || ($bagKind ?? '') !== '' || ! empty($filters['bag_cancel']))): ?>
<div class="border border-amber-200 bg-amber-50 p-3 m-2 text-sm text-amber-900 no-print">
조회 조건(수불 년월·봉투코드·구분 등)에 맞는 내역이 없습니다. <strong>수불 년월을 「전체」</strong>로 두거나 조건을 넓혀 다시 조회해 주세요.
</div>
<?php endif; ?>
<!-- 등록 폼 -->
<?php if (session()->getFlashdata('success')): ?>
<div class="mx-2 mt-2 border border-green-300 bg-green-50 text-green-800 text-sm px-3 py-2 no-print"><?= esc((string) session()->getFlashdata('success')) ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<div class="mx-2 mt-2 border border-red-300 bg-red-50 text-red-800 text-sm px-3 py-2 no-print"><?= esc((string) session()->getFlashdata('error')) ?></div>
<?php endif; ?>
<!-- 조회 조건 (레거시: 수불 년월, 봉투코드, 봉투 취소, 구분, 조회) -->
<section class="p-2 bg-white border-b border-gray-200 no-print">
<form method="POST" action="<?= mgmt_url('reports/misc-flow') ?>" class="flex flex-wrap items-center gap-2">
<?= csrf_field() ?>
<label class="text-sm text-gray-600">구분</label>
<select name="bmf_type" class="border border-gray-300 rounded px-2 py-1 text-sm" required>
<option value="in">입고</option>
<option value="out">출고</option>
<form method="get" action="<?= mgmt_url('reports/misc-flow') ?>" class="flex flex-wrap items-end gap-2 text-sm">
<span class="font-bold text-gray-700 whitespace-nowrap">수불 년월</span>
<select name="flow_y" class="border border-gray-300 rounded px-2 py-1 min-w-[5.5rem]" aria-label="수불 년도">
<option value="">전체</option>
<?php for ($yy = $dateYearMax; $yy >= $dateYearMin; $yy--): ?>
<option value="<?= $yy ?>" <?= $flowYear === (string) $yy ? 'selected' : '' ?>><?= $yy ?>년</option>
<?php endfor; ?>
</select>
<label class="text-sm text-gray-600">봉투</label>
<select name="bmf_bag_code" class="border border-gray-300 rounded px-2 py-1 text-sm" required>
<option value="">선택</option>
<?php foreach ($bagCodes as $bc): ?>
<option value="<?= esc($bc->cd_code) ?>"><?= esc($bc->cd_code . ' - ' . $bc->cd_name) ?></option>
<select name="flow_m" class="border border-gray-300 rounded px-2 py-1 min-w-[4.5rem]" aria-label="수불 월" <?= $flowYear === '' ? 'disabled' : '' ?>>
<option value=""><?= $flowYear === '' ? '—' : '전체' ?></option>
<?php for ($mi = 1; $mi <= 12; $mi++): ?>
<option value="<?= $mi ?>" <?= $flowMonthNum !== '' && (int) $flowMonthNum === $mi ? 'selected' : '' ?>><?= $mi ?>월</option>
<?php endfor; ?>
</select>
<label class="font-bold text-gray-700 whitespace-nowrap">봉투코드</label>
<input type="text" name="bag_code" value="<?= esc($bagCodeFilter) ?>" placeholder="코드 일부"
class="border border-gray-300 rounded px-2 py-1 w-28 font-mono"/>
<label class="inline-flex items-center gap-1 text-gray-700 whitespace-nowrap">
<input type="checkbox" name="bag_cancel" value="1" <?= $bagCancelOnly ? 'checked' : '' ?>/>
봉투 취소
</label>
<span class="text-xs text-gray-500 hidden sm:inline" title="출고 건만 조회">(출고만)</span>
<label class="font-bold text-gray-700 whitespace-nowrap">구분</label>
<select name="bag_kind" class="border border-gray-300 rounded px-2 py-1 min-w-[8rem]">
<option value="">전체</option>
<?php foreach ($bagKindOptions as $opt): ?>
<option value="<?= esc((string) $opt->cd_code) ?>" <?= $bagKind === (string) $opt->cd_code ? 'selected' : '' ?>>
<?= esc((string) $opt->cd_name) ?>
</option>
<?php endforeach; ?>
</select>
<label class="text-sm text-gray-600">수량</label>
<input type="number" name="bmf_qty" min="1" class="border border-gray-300 rounded px-2 py-1 text-sm w-24" required/>
<label class="text-sm text-gray-600">일자</label>
<input type="date" name="bmf_date" value="<?= date('Y-m-d') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm" required/>
<label class="text-sm text-gray-600">사유</label>
<input type="text" name="bmf_reason" placeholder="입출고 사유" class="border border-gray-300 rounded px-2 py-1 text-sm w-48" required/>
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">등록</button>
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm">조회</button>
<a href="<?= mgmt_url('reports/misc-flow') ?>" class="text-gray-500 hover:text-gray-800 px-2 py-1">초기화</a>
</form>
</section>
<!-- 조회 필터 -->
<section class="p-2 bg-white border-b border-gray-200 no-print">
<form method="GET" action="<?= mgmt_url('reports/misc-flow') ?>" 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"/>
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
</form>
</section>
<div class="grid grid-cols-1 xl:grid-cols-4 gap-2 p-2">
<!-- 입출고 리스트 -->
<section class="border border-gray-300 bg-white xl:col-span-1">
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">입출고 리스트</div>
<div class="overflow-auto max-h-[520px]">
<table class="w-full data-table text-sm">
<thead>
<tr>
<th class="w-24">수불일자</th>
<th class="w-16">수량</th>
<th class="w-14">구분</th>
<th>메모</th>
</tr>
</thead>
<tbody>
<?php if ($groupList !== []): ?>
<?php foreach ($groupList as $grp): ?>
<?php
$key = (string) ($grp['key'] ?? '');
$isSelected = $key !== '' && $key === $selKey;
$listUrl = $miscFlowListUrl(['sel_key' => $key]);
?>
<tr
class="<?= $isSelected ? 'bg-blue-100 font-semibold' : '' ?> cursor-pointer hover:bg-blue-50"
onclick="window.location.href='<?= esc($listUrl, 'attr') ?>'"
>
<td class="text-center <?= $isSelected ? 'border-l-4 border-blue-600' : '' ?>"><?= esc((string) ($grp['date'] ?? '')) ?></td>
<td class="text-right pr-2"><?= number_format((int) ($grp['totalQty'] ?? 0)) ?></td>
<td class="text-center">
<?php if ((string) ($grp['type'] ?? '') === 'in'): ?>
<span class="text-blue-700">입고</span>
<?php else: ?>
<span class="text-red-700">출고</span>
<?php endif; ?>
</td>
<td class="text-left pl-2 truncate max-w-[8rem]" title="<?= esc((string) ($grp['reason'] ?? '')) ?>"><?= esc((string) ($grp['reason'] ?? '')) ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="4" class="text-center text-gray-400 py-6">
<?php if (($totalRowsForLg ?? 0) === 0): ?>
등록된 기타 입출고가 없습니다.
<?php elseif (($fetchedRowCount ?? 0) === 0): ?>
선택한 기간·조건에 해당하는 내역이 없습니다.
<?php else: ?>
조회 결과가 없습니다.
<?php endif; ?>
</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</section>
<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>
</tr>
</thead>
<tbody class="text-right">
<?php foreach ($result as $row): ?>
<tr>
<td class="text-center"><?= (int) $row->bmf_idx ?></td>
<td class="text-center"><?= $row->bmf_type === 'in' ? '<span class="text-blue-600">입고</span>' : '<span class="text-red-600">출고</span>' ?></td>
<td class="text-center"><?= esc($row->bmf_date) ?></td>
<td class="text-center font-mono"><?= esc($row->bmf_bag_code) ?></td>
<td class="text-left pl-2"><?= esc($row->bmf_bag_name) ?></td>
<td><?= number_format((int) $row->bmf_qty) ?></td>
<td class="text-left pl-2"><?= esc($row->bmf_reason) ?></td>
<td class="text-center"><?= esc($row->bmf_regdate) ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($result)): ?>
<tr><td colspan="8" class="text-center text-gray-400 py-4">조회된 데이터가 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>
<!-- 우측: 입출고 정보 + 봉투 코드 + 등록 -->
<div class="xl:col-span-3 space-y-2">
<form method="post" action="<?= mgmt_url('reports/misc-flow/delete') ?>" id="misc-flow-delete-form" class="no-print">
<?= csrf_field() ?>
<input type="hidden" name="flow_y" value="<?= esc($flowYear) ?>"/>
<input type="hidden" name="flow_m" value="<?= esc($flowMonthNum) ?>"/>
<input type="hidden" name="bag_code" value="<?= esc($bagCodeFilter) ?>"/>
<input type="hidden" name="bag_kind" value="<?= esc($bagKind) ?>"/>
<?php if ($bagCancelOnly): ?><input type="hidden" name="bag_cancel" value="1"/><?php endif; ?>
<input type="hidden" name="sel_key" value="<?= esc($selKey) ?>"/>
<div class="flex flex-wrap gap-2 mb-1">
<button type="submit" class="bg-red-600 text-white px-4 py-1 rounded-sm text-sm disabled:opacity-40"
<?= $selKey === '' ? 'disabled' : '' ?>
onclick="return confirm('선택한 입출고 건을 삭제할까요? 재고가 복원됩니다.');">삭제</button>
<?php
$cancelQs = $filters;
unset($cancelQs['sel_key']);
$cancelUrl = mgmt_url('reports/misc-flow') . ($cancelQs !== [] ? '?' . http_build_query($cancelQs) : '');
?>
<a href="<?= esc($cancelUrl) ?>" class="border border-gray-400 text-gray-700 px-4 py-1 rounded-sm text-sm hover:bg-gray-50 inline-block">취소</a>
</div>
</form>
<!-- 입출고 일자 (상세) -->
<section class="border border-gray-300 bg-white">
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">입출고 일자</div>
<div class="p-2 grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
<?php if ($selectedGroup): ?>
<div class="flex flex-wrap items-center gap-2">
<span class="font-bold text-gray-700 whitespace-nowrap">수불 일자</span>
<span class="border border-gray-200 bg-gray-50 px-2 py-1 rounded"><?= esc((string) ($selectedGroup['date'] ?? '')) ?></span>
</div>
<div class="flex flex-wrap items-center gap-2">
<span class="font-bold text-gray-700 whitespace-nowrap">선택</span>
<span class="border border-gray-200 bg-gray-50 px-2 py-1 rounded"><?= esc((string) ($selectedGroup['typeLabel'] ?? '')) ?></span>
</div>
<div class="flex flex-wrap items-center gap-2">
<span class="font-bold text-gray-700 whitespace-nowrap">분류</span>
<span class="border border-gray-200 bg-gray-50 px-2 py-1 rounded min-w-[6rem]">
<?= esc($selectedBagKindLabel ?? '') !== '' ? esc($selectedBagKindLabel) : '—' ?>
</span>
</div>
<div class="md:col-span-2">
<span class="font-bold text-gray-700 block mb-1">비고</span>
<div class="border border-gray-200 bg-gray-50 rounded px-2 py-2 min-h-[4rem] whitespace-pre-wrap"><?= esc((string) ($selectedGroup['reason'] ?? '')) ?></div>
</div>
<?php else: ?>
<p class="text-gray-400 col-span-2 py-2">좌측 입출고 리스트에서 건을 선택하거나, 아래에서 신규 등록해 주세요.</p>
<?php endif; ?>
</div>
</section>
<!-- 입출고 봉투 코드 -->
<section class="border border-gray-300 bg-white">
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">입출고 봉투 코드</div>
<div class="overflow-auto max-h-[280px]">
<table class="w-full data-table text-sm">
<thead>
<tr>
<th class="w-10">No</th>
<th>봉투 코드</th>
<th>봉투 종류</th>
<th class="w-20">수량</th>
<th class="w-14">단위</th>
</tr>
</thead>
<tbody class="text-right">
<?php if ($detailLines !== []): ?>
<?php foreach ($detailLines as $idx => $line): ?>
<?php
$code = (string) ($line->bmf_bag_code ?? '');
$pu = $packagingMap[$code] ?? null;
$unitLabel = '매';
if ($pu && (int) ($pu->pu_pack_per_sheet ?? 0) > 0) {
$unitLabel = '매';
}
?>
<tr>
<td class="text-center"><?= $idx + 1 ?></td>
<td class="text-center font-mono"><?= esc($code) ?></td>
<td class="text-left pl-2"><?= esc((string) ($line->bmf_bag_name ?? '')) ?></td>
<td class="pr-2"><?= number_format((int) ($line->bmf_qty ?? 0)) ?></td>
<td class="text-center"><?= esc($unitLabel) ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="5" class="text-center text-gray-400 py-6">봉투 코드 내역이 없습니다.</td></tr>
<?php endif; ?>
</tbody>
<tfoot>
<tr class="bg-gray-50 font-semibold">
<td colspan="3" class="text-center">합계</td>
<td class="text-right pr-2"><?= number_format($detailTotalQty) ?></td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</section>
<!-- 품목 등록 (동일 수불일자·구분·비고로 묶임) -->
<?php if ($tableExists ?? false): ?>
<section class="border border-gray-300 bg-white no-print">
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">품목 등록</div>
<form method="post" action="<?= mgmt_url('reports/misc-flow') ?>" class="p-2 flex flex-wrap items-end gap-2 text-sm">
<?= csrf_field() ?>
<input type="hidden" name="flow_y" value="<?= esc($flowYear) ?>"/>
<input type="hidden" name="flow_m" value="<?= esc($flowMonthNum) ?>"/>
<input type="hidden" name="bag_code" value="<?= esc($bagCodeFilter) ?>"/>
<input type="hidden" name="bag_kind" value="<?= esc($bagKind) ?>"/>
<?php if ($bagCancelOnly): ?><input type="hidden" name="bag_cancel" value="1"/><?php endif; ?>
<input type="hidden" name="sel_key" value="<?= esc($selKey) ?>"/>
<label class="font-bold text-gray-700">수불 일자</label>
<input type="date" name="bmf_date" value="<?= esc($registerDate) ?>" class="border border-gray-300 rounded px-2 py-1" required/>
<label class="font-bold text-gray-700">선택</label>
<select name="bmf_type" class="border border-gray-300 rounded px-2 py-1 min-w-[7.5rem] w-32">
<option value="in" <?= $registerType === 'in' ? 'selected' : '' ?>>입고</option>
<option value="out" <?= $registerType === 'out' ? 'selected' : '' ?>>출고</option>
</select>
<label class="font-bold text-gray-700">분류</label>
<select name="bmf_bag_kind" id="bmf-bag-kind" class="border border-gray-300 rounded px-2 py-1 min-w-[7rem]">
<option value="">전체</option>
<?php foreach ($bagKindOptions as $opt): ?>
<option value="<?= esc((string) $opt->cd_code) ?>" <?= ($selectedBagKind ?? '') === (string) $opt->cd_code ? 'selected' : '' ?>>
<?= esc((string) $opt->cd_name) ?>
</option>
<?php endforeach; ?>
</select>
<label class="font-bold text-gray-700">비고</label>
<input type="text" name="bmf_reason" value="<?= esc($registerReason) ?>" placeholder="입출고 메모" maxlength="200"
class="border border-gray-300 rounded px-2 py-1 w-40 max-w-[10rem] shrink-0" required/>
<label class="font-bold text-gray-700">봉투 코드</label>
<select name="bmf_bag_code" id="bmf-bag-code" class="border border-gray-300 rounded px-2 py-1 min-w-[11rem]" required>
<option value="">선택</option>
<?php foreach ($bagCodes as $bc): ?>
<?php $code = (string) $bc->cd_code; ?>
<option value="<?= esc($code) ?>" data-kind-prefix="<?= esc(substr($code, 0, 2)) ?>">
<?= esc($code . ' - ' . $bc->cd_name) ?>
</option>
<?php endforeach; ?>
</select>
<label class="font-bold text-gray-700">수량</label>
<input type="number" name="bmf_qty" min="1" class="border border-gray-300 rounded px-2 py-1 w-24" required/>
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm">등록</button>
</form>
<p class="px-2 pb-2 text-xs text-gray-500">동일 수불일자·입출고·비고로 등록한 품목은 좌측 리스트에서 한 건으로 묶여 표시됩니다.</p>
</section>
<?php endif; ?>
</div>
</div>
<script>
(function () {
const kindSelect = document.getElementById('bmf-bag-kind');
const bagSelect = document.getElementById('bmf-bag-code');
if (!kindSelect || !bagSelect) return;
const allOptions = Array.from(bagSelect.querySelectorAll('option[data-kind-prefix]'));
function filterBagCodes() {
const prefix = kindSelect.value;
const current = bagSelect.value;
allOptions.forEach(function (opt) {
const show = prefix === '' || opt.getAttribute('data-kind-prefix') === prefix;
opt.hidden = !show;
opt.disabled = !show;
});
const selected = bagSelect.querySelector('option[value="' + CSS.escape(current) + '"]');
if (selected && !selected.hidden) {
bagSelect.value = current;
} else {
bagSelect.value = '';
}
}
kindSelect.addEventListener('change', filterBagCodes);
filterBagCodes();
const flowYearSelect = document.querySelector('select[name="flow_y"]');
const flowMonthSelect = document.querySelector('form[method="get"] select[name="flow_m"]');
if (flowYearSelect && flowMonthSelect) {
const syncFlowMonthSelect = function () {
const hasYear = flowYearSelect.value !== '';
flowMonthSelect.disabled = !hasYear;
if (!hasYear) {
flowMonthSelect.value = '';
}
const firstOpt = flowMonthSelect.querySelector('option[value=""]');
if (firstOpt) {
firstOpt.textContent = hasYear ? '전체' : '—';
}
};
flowYearSelect.addEventListener('change', syncFlowMonthSelect);
syncFlowMonthSelect();
}
})();
</script>

View File

@@ -1,73 +1,184 @@
<?= 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<array<string,mixed>> $lines */
/** @var string $startDate */
/** @var string $endDate */
/** @var int $saIdx */
/** @var string $catFilter */
/** @var string $mode */
/** @var list<object> $agencies */
/** @var array<string,string> $catLabels */
/** @var bool $hasBsFee */
/** @var string $lgName */
/** @var string $agencyLabel */
/** @var string $catLabelFilter */
/** @var list<string> $printExtraLines */
$byDaily = ($mode ?? 'daily') === 'daily';
$exportParams = array_filter([
'start_date' => $startDate ?? '',
'end_date' => $endDate ?? '',
'sa_idx' => (int) ($saIdx ?? 0),
'cat' => (string) ($catFilter ?? ''),
'mode' => $byDaily ? '' : 'period',
'export' => '1',
], static fn ($v): bool => $v !== '' && $v !== null);
$excelUrl = mgmt_url('reports/period-sales?' . http_build_query($exportParams));
$fmtFee = static function (float $v) use ($hasBsFee): string {
if (! $hasBsFee) {
return '—';
}
return number_format((int) round($v));
};
$rowClass = static function (string $kind): string {
return match ($kind) {
'day_sub_all' => 'bg-gray-100 font-semibold',
'day_sub_bag' => 'bg-sky-50 font-semibold text-sky-900',
'day_sub_fs' => 'bg-violet-50 font-semibold text-violet-900',
'foot_all' => 'bg-red-50 font-bold text-red-700',
'foot_bag' => 'bg-blue-50 font-bold text-blue-700',
'foot_fs' => 'bg-purple-50 font-bold text-purple-800',
default => '',
};
};
?>
<?= view('components/print_header', [
'printTitle' => '기간별 판매현황',
'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>
<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/period-sales') ?>" 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"/>
<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/period-sales') ?>" class="flex flex-wrap items-end gap-3 text-sm">
<div>
<label class="block text-gray-600 mb-0.5">시작일</label>
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
</div>
<div>
<label class="block text-gray-600 mb-0.5">종료일</label>
<input type="date" name="end_date" value="<?= esc($endDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
</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>
<label class="block text-gray-600 mb-0.5">구분</label>
<select name="cat" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[10rem]">
<option value="" <?= ($catFilter ?? '') === '' ? 'selected' : '' ?>>전체</option>
<?php foreach (($catLabels ?? []) as $ck => $lab): ?>
<option value="<?= esc($ck, 'attr') ?>" <?= ($catFilter ?? '') === $ck ? 'selected' : '' ?>><?= esc($lab) ?></option>
<?php endforeach; ?>
</select>
</div>
<div>
<label class="block text-gray-600 mb-0.5">집계 방식</label>
<select name="mode" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[9rem]">
<option value="daily" <?= $byDaily ? 'selected' : '' ?>>일자별</option>
<option value="period" <?= ! $byDaily ? 'selected' : '' ?>>기간별</option>
</select>
</div>
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm">조회</button>
</form>
</section>
<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>
</tr>
</thead>
<tbody class="text-right">
<?php
$grandSaleQty = 0;
$grandSaleAmount = 0;
$grandReturnQty = 0;
$grandReturnAmount = 0;
?>
<?php foreach ($result as $row): ?>
<?php
$grandSaleQty += (int) $row->sale_qty;
$grandSaleAmount += (int) $row->sale_amount;
$grandReturnQty += (int) $row->return_qty;
$grandReturnAmount += (int) $row->return_amount;
?>
<tr>
<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="8" class="text-center text-gray-400 py-4">조회된 데이터가 없습니다.</td></tr>
<?php endif; ?>
</tbody>
<tfoot class="bg-gray-50 font-bold text-right">
<tr>
<td colspan="2" class="text-center">합계</td>
<td><?= number_format($grandSaleQty) ?></td>
<td><?= number_format($grandSaleAmount) ?></td>
<td><?= number_format($grandReturnQty) ?></td>
<td><?= number_format($grandReturnAmount) ?></td>
<td><?= number_format($grandSaleQty - $grandReturnQty) ?></td>
<td><?= number_format($grandSaleAmount - $grandReturnAmount) ?></td>
</tr>
</tfoot>
</table>
</div>
<section class="p-3 bg-white">
<style>
@media print {
.period-sales-screen-title { display: none !important; }
}
</style>
<div class="mb-2 text-center period-sales-screen-title no-print">
<h1 class="text-lg font-bold m-0">기간별 판매현황<?= $byDaily ? ' [일집계]' : ' [기간집계]' ?></h1>
<p class="text-sm text-gray-700 m-1"><?= esc(trim(($lgName ?? '') . ' · ' . ($startDate ?? '') . ' ~ ' . ($endDate ?? '') . ' · 대행소: ' . ($agencyLabel ?? '') . ' · 구분: ' . ($catLabelFilter ?? ''))) ?></p>
<p class="text-xs text-gray-500 m-0">집계: <?= $byDaily ? '일자별' : '기간별' ?> · (단위: 매 / 원)</p>
</div>
<div class="border border-gray-300 overflow-auto">
<table class="w-full data-table text-sm" id="period-sales-table">
<thead>
<tr>
<?php if ($byDaily): ?>
<th rowspan="2" class="align-middle whitespace-nowrap">일자</th>
<?php endif; ?>
<th rowspan="2" class="align-middle text-left pl-2">품목</th>
<th colspan="4" class="text-center border-l border-gray-300">판매</th>
<th colspan="2" class="text-center border-l border-gray-300">반품</th>
<th colspan="4" class="text-center border-l border-gray-300">계</th>
</tr>
<tr>
<th class="text-right border-l border-gray-300">수량</th>
<th class="text-right">판매금액</th>
<th class="text-right">수수료</th>
<th class="text-right">징수액</th>
<th class="text-right border-l border-gray-300">수량</th>
<th class="text-right">금액</th>
<th class="text-right border-l border-gray-300">수량</th>
<th class="text-right">판매금액</th>
<th class="text-right">수수료</th>
<th class="text-right">징수액</th>
</tr>
</thead>
<tbody class="text-right">
<?php
$emptyColspan = $byDaily ? 12 : 11;
?>
<?php foreach ($lines ?? [] as $ln): ?>
<?php
$kind = (string) ($ln['kind'] ?? 'data');
$trCls = $rowClass($kind);
$isData = $kind === 'data';
$rs = (int) ($ln['ymd_rowspan'] ?? 0);
?>
<tr class="<?= esc($trCls, 'attr') ?>">
<?php if ($byDaily): ?>
<?php if ($isData && $rs > 0): ?>
<td rowspan="<?= $rs ?>" class="text-center align-top whitespace-nowrap tabular-nums pt-1"><?= esc((string) ($ln['ymd'] ?? '')) ?></td>
<?php elseif (str_starts_with($kind, 'foot_')): ?>
<td class="bg-inherit"></td>
<?php endif; ?>
<?php endif; ?>
<td class="text-left pl-2"><?= esc((string) ($ln['name'] ?? '')) ?></td>
<td class="border-l border-gray-200 tabular-nums"><?= number_format((int) ($ln['s_qty'] ?? 0)) ?></td>
<td class="tabular-nums"><?= number_format((int) round((float) ($ln['s_amt'] ?? 0))) ?></td>
<td class="tabular-nums"><?= $fmtFee((float) ($ln['s_fee'] ?? 0)) ?></td>
<td class="tabular-nums"><?= number_format((int) round((float) ($ln['s_levy'] ?? 0))) ?></td>
<td class="border-l border-gray-200 tabular-nums"><?= number_format((int) ($ln['r_qty'] ?? 0)) ?></td>
<td class="tabular-nums"><?= number_format((int) round((float) ($ln['r_amt'] ?? 0))) ?></td>
<td class="border-l border-gray-200 tabular-nums"><?= number_format((int) ($ln['t_qty'] ?? 0)) ?></td>
<td class="tabular-nums"><?= number_format((int) round((float) ($ln['t_amt'] ?? 0))) ?></td>
<td class="tabular-nums"><?= $fmtFee((float) ($ln['t_fee'] ?? 0)) ?></td>
<td class="tabular-nums"><?= number_format((int) round((float) ($ln['t_levy'] ?? 0))) ?></td>
</tr>
<?php endforeach; ?>
<?php if (($lines ?? []) === []): ?>
<tr>
<td colspan="<?= (int) $emptyColspan ?>" class="text-center text-gray-400 py-6">조회된 데이터가 없습니다.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</section>

View File

@@ -1,59 +1,144 @@
<?= view('components/print_header', ['printTitle' => '반품/파기 현황']) ?>
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<?php
$startDate = (string) ($startDate ?? date('Y-m-01'));
$endDate = (string) ($endDate ?? date('Y-m-d'));
$ioType = (string) ($ioType ?? 'out');
$result = is_array($result ?? null) ? $result : (array) ($result ?? []);
$queried = (bool) ($queried ?? false);
$exportQuery = (string) ($exportQuery ?? 'search=1');
$fmtKrDate = static function (string $ymd): string {
$ts = strtotime($ymd);
return $ts ? date('Y년 m월 d일', $ts) : $ymd;
};
$ioLabel = $ioType === 'in' ? '입고' : '출고';
$periodLabel = $fmtKrDate($startDate) . ' ~ ' . $fmtKrDate($endDate);
$printExtraLines = [
'조회기간: ' . $periodLabel,
'입출고 구분: ' . $ioLabel,
'(단위: 매)',
];
$typeLabel = static function (string $bsType): string {
return match ($bsType) {
'return' => '반품',
'cancel' => '파기',
default => $bsType,
};
};
$kindLabel = static function (object $row): string {
$name = trim((string) ($row->bs_bag_name ?? ''));
if ($name !== '') {
return $name;
}
$code = trim((string) ($row->bs_bag_code ?? ''));
return $code !== '' ? $code : '-';
};
$totalQty = 0;
foreach ($result as $row) {
$totalQty += (int) ($row->qty ?? 0);
}
?>
<?= view('components/print_header', [
'printTitle' => '반품 / 파기 현황',
'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>
<div class="flex flex-wrap items-center gap-2">
<?php if ($queried && $exportQuery !== ''): ?>
<a href="<?= mgmt_url('reports/returns/export?' . esc($exportQuery, 'attr')) ?>"
class="bg-green-700 text-white px-3 py-1 rounded-sm text-sm hover:bg-green-800">엑셀저장</a>
<?php else: ?>
<span class="bg-gray-300 text-gray-600 px-3 py-1 rounded-sm text-sm cursor-not-allowed" title="조회 후 이용">엑셀저장</span>
<?php endif; ?>
<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">인쇄</button>
<a href="<?= base_url('dashboard') ?>" class="border border-gray-400 text-gray-700 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">종료</a>
</div>
</div>
</section>
<section class="p-2 bg-white border-b border-gray-200">
<form method="GET" action="<?= mgmt_url('reports/returns') ?>" 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"/>
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
<section class="p-2 bg-white border-b border-gray-200 no-print">
<form method="get" action="<?= mgmt_url('reports/returns') ?>" class="flex flex-wrap items-end gap-x-4 gap-y-2 text-sm">
<input type="hidden" name="search" value="1"/>
<div class="flex flex-wrap items-center gap-2">
<label class="font-bold text-gray-700 whitespace-nowrap">조회기간</label>
<input type="date" name="start_date" value="<?= esc($startDate) ?>" class="border border-gray-300 rounded px-2 py-1" required/>
<span>~</span>
<input type="date" name="end_date" value="<?= esc($endDate) ?>" class="border border-gray-300 rounded px-2 py-1" required/>
</div>
<div class="flex flex-wrap items-center gap-3">
<span class="font-bold text-gray-700 whitespace-nowrap">입출고 구분</span>
<label class="inline-flex items-center gap-1">
<input type="radio" name="io_type" value="in" <?= $ioType === 'in' ? 'checked' : '' ?>/>
입고
</label>
<label class="inline-flex items-center gap-1">
<input type="radio" name="io_type" value="out" <?= $ioType === 'out' ? 'checked' : '' ?>/>
출고
</label>
</div>
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm">조회</button>
</form>
<p class="text-xs text-gray-500 mt-1">
<strong>입고</strong> = 지정판매소 반품(재고 복귀), <strong>출고</strong> = 판매 취소·파기 처리. 조회 후 표·엑셀·인쇄에 반영됩니다.
</p>
</section>
<div class="border border-gray-300 overflow-auto mt-2">
<table class="w-full data-table">
<?php if (! $queried): ?>
<div class="m-2 p-3 border border-blue-200 bg-blue-50 text-sm text-blue-900 no-print">
조회기간과 입출고 구분을 선택한 뒤 <strong>조회</strong> 버튼을 눌러 주세요.
</div>
<?php endif; ?>
<div class="border border-gray-300 overflow-auto m-2 print:m-0">
<table class="w-full data-table text-sm">
<thead>
<tr>
<th>일자</th>
<th>판매소</th>
<th>봉투코드</th>
<th>봉투명</th>
<th>구분</th>
<th>수량</th>
<th>금액</th>
<th class="w-28">일자</th>
<th>반품처</th>
<th>종류</th>
<th class="w-24 text-right">수량</th>
<th class="w-20 text-center">구분</th>
</tr>
</thead>
<tbody class="text-right">
<?php
$totalQty = 0; $totalAmt = 0;
$typeMap = ['return' => '반품', 'cancel' => '취소/파기'];
foreach ($result as $row):
$totalQty += (int) $row->qty;
$totalAmt += (int) $row->amount;
?>
<tbody>
<?php if ($queried && $result === []): ?>
<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"><?= esc($typeMap[$row->bs_type] ?? $row->bs_type) ?></td>
<td><?= number_format((int) $row->qty) ?></td>
<td><?= number_format((int) $row->amount) ?></td>
<td colspan="5" class="text-center text-gray-500 py-8">해당 자료가 없습니다.</td>
</tr>
<?php endif; ?>
<?php foreach ($result as $row): ?>
<tr>
<td class="text-center"><?= esc((string) $row->bs_sale_date) ?></td>
<td class="text-left pl-2"><?= esc((string) ($row->bs_ds_name ?? '')) ?></td>
<td class="text-left pl-2"><?= esc($kindLabel($row)) ?></td>
<td class="text-right pr-2 tabular-nums"><?= number_format((int) ($row->qty ?? 0)) ?></td>
<td class="text-center"><?= esc($typeLabel((string) ($row->bs_type ?? ''))) ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($result)): ?>
<tr><td colspan="7" class="text-center text-gray-400 py-4">조회된 데이터가 없습니다.</td></tr>
<?php else: ?>
<?php if ($queried && $result !== []): ?>
<tr class="font-bold bg-gray-100">
<td colspan="5" class="text-center">합계</td>
<td><?= number_format($totalQty) ?></td>
<td><?= number_format($totalAmt) ?></td>
<td colspan="3" class="text-center">합계</td>
<td class="text-right pr-2 tabular-nums"><?= number_format($totalQty) ?></td>
<td></td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<style>
@media print {
.no-print { display: none !important; }
}
</style>

View File

@@ -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>

View File

@@ -1,64 +1,227 @@
<?= 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 string $startDate */
/** @var string $endDate */
/** @var string $zoneCode */
/** @var string $bagCode */
/** @var string $catFilter */
/** @var string $metric */
/** @var list<string> $zoneOptions */
/** @var array<string, string> $bagOptions */
/** @var array<string, string> $catLabels */
/** @var list<array<string, mixed>> $reportRows */
/** @var list<float> $grandMonths */
/** @var float $grandTotal */
/** @var string $lgName */
/** @var string $zoneLabel */
/** @var string $bagLabel */
/** @var string $catLabelFilter */
/** @var string $metricLabel */
/** @var list<string> $printExtraLines */
$isAmt = ($metric ?? 'qty') === 'amt';
$fmtVal = static function (float $v) use ($isAmt): string {
return number_format((int) round($v));
};
$exportParams = array_merge([
'start_date' => $startDate ?? '',
'end_date' => $endDate ?? '',
'metric' => ($metric ?? 'qty') === 'amt' ? 'amt' : 'qty',
'export' => '1',
], array_filter([
'zone_code' => (string) ($zoneCode ?? ''),
'bag_code' => (string) ($bagCode ?? ''),
'cat' => (string) ($catFilter ?? ''),
], static fn ($v): bool => $v !== '' && $v !== null));
$excelUrl = mgmt_url('reports/shop-sales?' . http_build_query($exportParams));
$colCount = 16;
?>
<?= view('components/print_header', [
'printTitle' => '지정 판매소별 판매현황',
'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>
<p class="text-xs text-gray-500 mt-2">
<?php if ($isAmt): ?>
금액은 조회기간 내 판매(sale) 건의 판매금액을 거래 월별로 합산합니다(반품·취소는 제외).
<?php else: ?>
수량은 반품·판매취소를 연초부터 판매와 품목별 선입선출로 맞추고, 반품취소는 원복합니다. 조회에 포함되지 않은 달의 수치는 조회 구간의 첫 달에 합쳐 집계됩니다.
<?php endif; ?>
</p>
</section>
<section class="p-2 bg-white border-b border-gray-200">
<form method="GET" action="<?= mgmt_url('reports/shop-sales') ?>" 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"/>
<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/shop-sales') ?>" class="flex flex-wrap items-end gap-3 text-sm">
<div>
<label class="block text-gray-600 mb-0.5">시작일</label>
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
</div>
<div>
<label class="block text-gray-600 mb-0.5">종료일</label>
<input type="date" name="end_date" value="<?= esc($endDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
</div>
<div>
<label class="block text-gray-600 mb-0.5">읍면동</label>
<select name="zone_code" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[8rem] max-w-[16rem]">
<option value="">전체</option>
<?php foreach ($zoneOptions ?? [] as $z): ?>
<option value="<?= esc($z, 'attr') ?>" <?= ($zoneCode ?? '') === $z ? 'selected' : '' ?>><?= esc($z) ?></option>
<?php endforeach; ?>
</select>
</div>
<div>
<label class="block text-gray-600 mb-0.5">봉투 종류</label>
<select name="bag_code" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[10rem] max-w-[20rem]">
<option value="">전체</option>
<?php foreach (($bagOptions ?? []) as $bc => $bn): ?>
<option value="<?= esc((string) $bc, 'attr') ?>" <?= ($bagCode ?? '') === (string) $bc ? 'selected' : '' ?>>
<?= esc(trim((string) $bc . (($bn ?? '') !== '' ? ' · ' . $bn : ''))) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label class="block text-gray-600 mb-0.5">구분</label>
<select name="cat" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[9rem]">
<option value="" <?= ($catFilter ?? '') === '' ? 'selected' : '' ?>>전체</option>
<?php foreach (($catLabels ?? []) as $ck => $lab): ?>
<option value="<?= esc($ck, 'attr') ?>" <?= ($catFilter ?? '') === $ck ? 'selected' : '' ?>><?= esc($lab) ?></option>
<?php endforeach; ?>
</select>
</div>
<fieldset class="border border-gray-200 rounded px-2 py-1">
<legend class="text-xs text-gray-600 px-1">집계 대상</legend>
<label class="inline-flex items-center gap-1 mr-3 cursor-pointer">
<input type="radio" name="metric" value="qty" <?= ! $isAmt ? 'checked' : '' ?>/>
<span>수량</span>
</label>
<label class="inline-flex items-center gap-1 cursor-pointer">
<input type="radio" name="metric" value="amt" <?= $isAmt ? 'checked' : '' ?>/>
<span>금액</span>
</label>
</fieldset>
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm">조회</button>
</form>
</section>
<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
$totSaleQty = 0; $totSaleAmt = 0; $totRetQty = 0; $totRetAmt = 0;
foreach ($result as $row):
$totSaleQty += (int) $row->sale_qty;
$totSaleAmt += (int) $row->sale_amount;
$totRetQty += (int) $row->return_qty;
$totRetAmt += (int) $row->return_amount;
?>
<tr>
<td class="text-left pl-2"><?= esc($row->bs_ds_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="7" class="text-center text-gray-400 py-4">조회된 데이터가 없습니다.</td></tr>
<?php else: ?>
<tr class="font-bold bg-gray-100">
<td class="text-center">합계</td>
<td><?= number_format($totSaleQty) ?></td>
<td><?= number_format($totSaleAmt) ?></td>
<td><?= number_format($totRetQty) ?></td>
<td><?= number_format($totRetAmt) ?></td>
<td><?= number_format($totSaleQty - $totRetQty) ?></td>
<td><?= number_format($totSaleAmt - $totRetAmt) ?></td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<section class="p-3 bg-white shop-sales-report-section">
<style>
@media print {
@page {
size: A4 landscape;
margin: 5mm 6mm;
}
.shop-sales-report-section { padding: 0 !important; }
.shop-sales-scroll-wrap {
overflow: visible !important;
border: 1px solid #333 !important;
}
#shop-sales-table {
font-size: 7pt !important;
width: 100% !important;
table-layout: fixed !important;
}
#shop-sales-table th,
#shop-sales-table td {
min-width: 0 !important;
padding: 1px 2px !important;
white-space: normal !important;
word-break: break-word;
overflow-wrap: anywhere;
line-height: 1.12;
vertical-align: top;
}
#shop-sales-table th { font-size: 6.5pt !important; }
}
@media screen {
#shop-sales-table td.num-cell { white-space: nowrap; }
}
</style>
<div class="mb-2 text-center no-print">
<h1 class="text-lg font-bold m-0">지정 판매소별 판매현황</h1>
<p class="text-sm text-gray-700 m-1"><?= esc(trim(($lgName ?? '') . ' · ' . ($startDate ?? '') . ' ~ ' . ($endDate ?? '') . ' · 읍면동: ' . ($zoneLabel ?? '') . ' · 집계: ' . ($metricLabel ?? ''))) ?></p>
<p class="text-xs text-gray-500 m-0"><?= $isAmt ? '(단위: 원)' : '(단위: 매)' ?></p>
</div>
<div class="shop-sales-scroll-wrap border border-gray-300 overflow-x-auto">
<table class="w-full data-table text-xs" id="shop-sales-table">
<colgroup>
<col style="width: 14%;"/>
<col style="width: 7%;"/>
<col style="width: 16%;"/>
<col style="width: 6%;"/>
<?php for ($i = 0; $i < 12; $i++): ?>
<col style="width: <?= esc((string) round(57 / 12, 2), 'attr') ?>%;"/>
<?php endfor; ?>
</colgroup>
<thead>
<tr>
<th class="text-left pl-2">지정판매소</th>
<th>대표자명</th>
<th class="text-left pl-1">주소</th>
<th>합계</th>
<?php for ($m = 1; $m <= 12; $m++): ?>
<th><?= $m ?>월</th>
<?php endfor; ?>
</tr>
</thead>
<tbody class="text-right">
<?php foreach ($reportRows ?? [] as $rw): ?>
<tr>
<td class="text-left pl-2 font-medium"><?= esc((string) ($rw['name'] ?? '')) ?></td>
<td class="text-center"><?= esc((string) ($rw['rep'] ?? '')) ?></td>
<td class="text-left pl-1"><?= esc((string) ($rw['address'] ?? '')) ?></td>
<td class="num-cell tabular-nums"><?= $fmtVal((float) ($rw['total'] ?? 0)) ?></td>
<?php foreach (($rw['months'] ?? []) as $mv): ?>
<td class="num-cell tabular-nums"><?= $fmtVal((float) $mv) ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
<?php if (($reportRows ?? []) === []): ?>
<tr>
<td colspan="<?= (int) $colCount ?>" class="text-center text-gray-400 py-6">조회된 데이터가 없습니다.</td>
</tr>
<?php else: ?>
<tr class="bg-gray-100 font-bold">
<td colspan="3" class="text-center">전체 합계</td>
<td class="num-cell tabular-nums"><?= $fmtVal((float) ($grandTotal ?? 0)) ?></td>
<?php foreach (($grandMonths ?? []) as $gm): ?>
<td class="num-cell tabular-nums"><?= $fmtVal((float) $gm) ?></td>
<?php endforeach; ?>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</section>
<script>
(function () {
const metric = <?= json_encode($isAmt ? 'amt' : 'qty', JSON_THROW_ON_ERROR) ?>;
const start = <?= json_encode((string) ($startDate ?? ''), JSON_THROW_ON_ERROR) ?>;
const end = <?= json_encode((string) ($endDate ?? ''), JSON_THROW_ON_ERROR) ?>;
let savedTitle = document.title;
function stamp() {
const d = new Date();
const p = (n) => String(n).padStart(2, '0');
return d.getFullYear() + p(d.getMonth() + 1) + p(d.getDate()) + '_' + p(d.getHours()) + p(d.getMinutes()) + p(d.getSeconds());
}
window.addEventListener('beforeprint', function () {
savedTitle = document.title;
document.title = '지정판매소별판매현황_' + metric + '_' + start + '_' + end + '_' + stamp();
});
window.addEventListener('afterprint', function () {
document.title = savedTitle;
});
})();
</script>

View File

@@ -1,134 +1,231 @@
<?= view('components/print_header', ['printTitle' => '봉투 수불 현황']) ?>
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<?php
$refDate = (string) ($refDate ?? date('Y-m-d'));
$leadDays = (int) ($leadDays ?? 40);
$stockScope = (string) ($stockScope ?? 'all');
$salesScope = (string) ($salesScope ?? 'all');
$rows = is_array($rows ?? null) ? $rows : [];
$queried = (bool) ($queried ?? false);
$stockLabel = (string) ($stockLabel ?? 'ALL');
$salesLabel = (string) ($salesLabel ?? 'ALL');
$fmtKrRef = static function (string $ymd): string {
$ts = strtotime($ymd);
return $ts ? date('Y.m.d', $ts) . ' 현재' : $ymd;
};
$printExtraLines = [
$fmtKrRef($refDate),
'적정재고 보유일수(제작기일): ' . $leadDays . '일',
'현재고: ' . $stockLabel . ' · 월평균판매량: ' . $salesLabel,
'※ 제작기일 ' . $leadDays . '일 기준으로 발주예정일 산정 (레거시 화면 유추)',
];
?>
<?= view('components/print_header', [
'printTitle' => '쓰레기봉투 수급 계획',
'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 items-center 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">인쇄</button>
<a href="<?= base_url('dashboard') ?>" class="border border-gray-400 text-gray-700 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">종료</a>
</div>
</div>
</section>
<section class="p-2 bg-white border-b border-gray-200">
<form method="GET" action="<?= mgmt_url('reports/supply-demand') ?>" 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"/>
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
<section class="p-2 bg-white border-b border-gray-200 no-print text-sm">
<form method="get" action="<?= mgmt_url('reports/supply-demand') ?>" class="flex flex-wrap items-end gap-x-4 gap-y-3">
<input type="hidden" name="search" value="1"/>
<div class="flex flex-wrap items-center gap-2">
<label class="font-bold text-gray-700 whitespace-nowrap">기준일</label>
<input type="date" name="ref_date" value="<?= esc($refDate) ?>" class="border border-gray-300 rounded px-2 py-1" required/>
<span class="text-gray-600"><?= esc($fmtKrRef($refDate)) ?></span>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="font-bold text-gray-700 whitespace-nowrap">적정재고 보유일수</label>
<input type="number" name="lead_days" value="<?= (int) $leadDays ?>" min="1" max="365"
class="border border-gray-300 rounded px-2 py-1 w-20 text-right" title="제작기일(발주예정일 산정)"/>
<span class="text-blue-700 text-xs">※ 제작기일 <?= (int) $leadDays ?>일 기준으로 발주예정일 산정</span>
</div>
<fieldset class="flex flex-wrap items-center gap-2 border-0 p-0 m-0">
<legend class="font-bold text-gray-700 whitespace-nowrap mr-1">현재고 선택 옵션</legend>
<?php foreach (['all' => 'ALL', 'legacy' => '기존 봉투', 'barcode' => '바코드 봉투'] as $val => $lab): ?>
<label class="inline-flex items-center gap-1">
<input type="radio" name="stock_scope" value="<?= esc($val) ?>" <?= $stockScope === $val ? 'checked' : '' ?>/>
<?= esc($lab) ?>
</label>
<?php endforeach; ?>
</fieldset>
<fieldset class="flex flex-wrap items-center gap-2 border-0 p-0 m-0">
<legend class="font-bold text-gray-700 whitespace-nowrap mr-1">월 평균판매량 선택 옵션</legend>
<?php foreach (['all' => 'ALL', 'legacy' => '기존 봉투', 'barcode' => '바코드 봉투'] as $val => $lab): ?>
<label class="inline-flex items-center gap-1">
<input type="radio" name="sales_scope" value="<?= esc($val) ?>" <?= $salesScope === $val ? 'checked' : '' ?>/>
<?= esc($lab) ?>
</label>
<?php endforeach; ?>
</fieldset>
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm">조회</button>
</form>
<p class="text-xs text-gray-500 mt-2 max-w-4xl">
<strong>기존 봉투</strong> = 입고 팩 바코드 미등록 품목(수기 재고),
<strong>바코드 봉투</strong> = <code class="text-xs">bag_receiving_pack_code</code> 등록 품목.
월판매량은 최근 12개월 순판매(또는 바코드 판매 스캔)의 월평균입니다.
소진일수 = (총재고÷월판매량)×30, 발주예정일 = 기준일+소진일수−보유일수, 과거일은 빨간색.
</p>
</section>
<div class="grid grid-cols-2 gap-4 mt-2">
<!-- 현재 재고 -->
<div class="border border-gray-300 rounded overflow-auto">
<div class="bg-gray-100 border-b border-gray-300 px-3 py-1.5">
<span class="text-sm font-bold text-gray-700">현재 재고</span>
</div>
<table class="w-full data-table">
<thead>
<tr>
<th>봉투코드</th>
<th>봉투명</th>
<th>재고수량</th>
</tr>
</thead>
<tbody class="text-right">
<?php foreach ($inventory as $row): ?>
<tr>
<td class="text-center font-mono"><?= esc($row->bi_bag_code) ?></td>
<td class="text-left pl-2"><?= esc($row->bi_bag_name) ?></td>
<td class="font-bold"><?= number_format((int) $row->bi_qty) ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($inventory)): ?>
<tr><td colspan="3" class="text-center text-gray-400 py-4">데이터가 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- 기간 입고 -->
<div class="border border-gray-300 rounded overflow-auto">
<div class="bg-gray-100 border-b border-gray-300 px-3 py-1.5">
<span class="text-sm font-bold text-gray-700">기간 입고</span>
</div>
<table class="w-full data-table">
<thead>
<tr>
<th>봉투코드</th>
<th>봉투명</th>
<th>입고수량</th>
</tr>
</thead>
<tbody class="text-right">
<?php foreach ($receiving as $row): ?>
<tr>
<td class="text-center font-mono"><?= esc($row->br_bag_code) ?></td>
<td class="text-left pl-2"><?= esc($row->br_bag_name) ?></td>
<td><?= number_format((int) $row->recv_qty) ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($receiving)): ?>
<tr><td colspan="3" class="text-center text-gray-400 py-4">데이터가 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- 기간 판매 -->
<div class="border border-gray-300 rounded overflow-auto">
<div class="bg-gray-100 border-b border-gray-300 px-3 py-1.5">
<span class="text-sm font-bold text-gray-700">기간 판매</span>
</div>
<table class="w-full data-table">
<thead>
<tr>
<th>봉투코드</th>
<th>봉투명</th>
<th>판매수량</th>
<th>반품수량</th>
<th>순판매</th>
</tr>
</thead>
<tbody class="text-right">
<?php foreach ($sales as $row): ?>
<tr>
<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->return_qty) ?></td>
<td class="font-bold"><?= number_format((int) $row->sale_qty - (int) $row->return_qty) ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($sales)): ?>
<tr><td colspan="5" class="text-center text-gray-400 py-4">데이터가 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- 기간 불출 -->
<div class="border border-gray-300 rounded overflow-auto">
<div class="bg-gray-100 border-b border-gray-300 px-3 py-1.5">
<span class="text-sm font-bold text-gray-700">기간 불출</span>
</div>
<table class="w-full data-table">
<thead>
<tr>
<th>봉투코드</th>
<th>봉투명</th>
<th>불출수량</th>
</tr>
</thead>
<tbody class="text-right">
<?php foreach ($issues as $row): ?>
<tr>
<td class="text-center font-mono"><?= esc($row->bi2_bag_code) ?></td>
<td class="text-left pl-2"><?= esc($row->bi2_bag_name) ?></td>
<td><?= number_format((int) $row->issue_qty) ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($issues)): ?>
<tr><td colspan="3" class="text-center text-gray-400 py-4">데이터가 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<?php if (! $queried): ?>
<div class="m-2 p-3 border border-blue-200 bg-blue-50 text-sm text-blue-900 no-print">
기준일·옵션을 선택한 뒤 <strong>조회</strong>를 눌러 주세요.
</div>
<?php endif; ?>
<div class="supply-plan-print-sheet">
<div class="supply-plan-print m-2 border border-gray-300 overflow-auto print:m-0">
<table class="w-full data-table text-sm supply-plan-table">
<thead>
<tr class="bg-gray-100">
<th colspan="4" class="text-center border-b border-gray-300 sp-group-h">최근 발주 내역</th>
<th colspan="5" class="text-center border-b border-gray-300 border-l sp-group-h">현재고 및 예상 판매일수</th>
<th colspan="2" class="text-center border-b border-gray-300 border-l sp-group-h">추가발주 예정내역</th>
</tr>
<tr>
<th class="sp-col-date">발주일자</th>
<th class="sp-col-name">봉투종류</th>
<th class="sp-col-num text-right">발주량</th>
<th class="sp-col-num text-right">발주시재고</th>
<th class="sp-col-num text-right border-l">현재고</th>
<th class="sp-col-num text-right">입고예정량</th>
<th class="sp-col-num text-right">총재고</th>
<th class="sp-col-num text-right">월판매량</th>
<th class="sp-col-num text-right">소진일수(일)</th>
<th class="sp-col-date text-center border-l">발주예정일</th>
<th class="sp-col-num text-right">발주수량</th>
</tr>
</thead>
<tbody>
<?php if ($queried && $rows === []): ?>
<tr>
<td colspan="11" class="text-center text-gray-500 py-8">표시할 품목이 없습니다.</td>
</tr>
<?php endif; ?>
<?php foreach ($rows as $row): ?>
<?php
$depl = (int) ($row['depletion_days'] ?? 0);
$deplDisplay = $depl <= 0 ? '—' : number_format($depl);
$sched = (string) ($row['schedule_date'] ?? '');
$schedOver = (bool) ($row['schedule_overdue'] ?? false);
$schedDisplay = '—';
if (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $sched, $m)) {
$y = (int) $m[1];
if ($y >= 1990 && $y <= 2200) {
$schedDisplay = $m[1] . '.' . $m[2] . '.' . $m[3];
}
}
?>
<tr>
<td class="sp-col-date text-center"><?= ($row['last_order_date'] ?? '') !== '' ? esc(str_replace('-', '.', (string) $row['last_order_date'])) : '—' ?></td>
<td class="sp-col-name text-left"><?= esc((string) ($row['bag_name'] ?? $row['bag_code'] ?? '')) ?></td>
<td class="sp-col-num text-right tabular-nums"><?= (int) ($row['last_order_qty'] ?? 0) > 0 ? number_format((int) $row['last_order_qty']) : '—' ?></td>
<td class="sp-col-num text-right tabular-nums"><?= (int) ($row['stock_at_order'] ?? 0) > 0 ? number_format((int) $row['stock_at_order']) : '—' ?></td>
<td class="sp-col-num text-right tabular-nums border-l"><?= number_format((int) ($row['current_stock'] ?? 0)) ?></td>
<td class="sp-col-num text-right tabular-nums"><?= number_format((int) ($row['pending_inbound'] ?? 0)) ?></td>
<td class="sp-col-num text-right tabular-nums font-semibold"><?= number_format((int) ($row['total_stock'] ?? 0)) ?></td>
<td class="sp-col-num text-right tabular-nums"><?= number_format((int) ($row['monthly_avg_sales'] ?? 0)) ?></td>
<td class="sp-col-num text-right tabular-nums"><?= esc($deplDisplay) ?></td>
<td class="sp-col-date text-center border-l <?= $schedOver ? 'text-red-600 font-bold' : '' ?>"><?= esc($schedDisplay) ?></td>
<td class="sp-col-num text-right tabular-nums <?= (int) ($row['order_qty'] ?? 0) > 0 ? 'text-red-600 font-bold' : '' ?>">
<?= number_format((int) ($row['order_qty'] ?? 0)) ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<style>
.supply-plan-table thead th { font-size: 0.75rem; line-height: 1.2; padding: 0.35rem 0.25rem; }
.supply-plan-table tbody td { padding: 0.25rem 0.35rem; }
@media screen {
.supply-plan-print { overflow-x: auto; }
.supply-plan-table { min-width: 960px; }
}
@media print {
@page {
size: A4 portrait;
margin: 10mm 8mm;
}
.no-print { display: none !important; }
.supply-plan-print-sheet {
width: 100% !important;
max-width: 100% !important;
box-sizing: border-box;
}
.supply-plan-print {
border: none !important;
margin: 0 !important;
padding: 0 !important;
overflow: hidden !important;
width: 100% !important;
max-width: 100% !important;
}
.supply-plan-table.data-table {
min-width: 0 !important;
width: 100% !important;
max-width: 100% !important;
table-layout: fixed !important;
font-size: 6px !important;
}
.supply-plan-table.data-table th,
.supply-plan-table.data-table td {
white-space: normal !important;
word-break: keep-all;
overflow-wrap: anywhere;
padding: 1px 1px !important;
line-height: 1.1;
vertical-align: middle;
}
.supply-plan-table .sp-group-h {
font-size: 5px !important;
padding: 1px !important;
}
/* 세로 A4: 날짜 2×4.5% + 품목 11% + 수치 8×10% = 100% */
.supply-plan-table .sp-col-date {
width: 4.5%;
font-size: 5px !important;
text-align: center;
}
.supply-plan-table .sp-col-name {
width: 11%;
text-align: left !important;
font-size: 5px !important;
line-height: 1.2;
}
.supply-plan-table .sp-col-num {
width: 10%;
font-size: 5px !important;
text-align: right !important;
}
}
</style>

View File

@@ -1,62 +1,232 @@
<?= 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 int $year */
/** @var string $gugunCode */
/** @var int $saIdx */
/** @var list<object> $agencies */
/** @var list<array{code: string, name: string}> $gugunOptions */
/** @var list<array{id: string, label: string}> $colSpec */
/** @var list<array{name: string, lines: list<array<string,mixed>>}> $itemBlocks */
/** @var array{name: string, lines: list<array<string,mixed>>} $footerBlock */
/** @var bool $hasBsFee */
/** @var string $lgName */
/** @var string $gugunLabel */
/** @var string $agencyLabel */
/** @var list<string> $printExtraLines */
/** @var bool $hasYearlyData */
$yMax = (int) date('Y') + 1;
$yMin = 2020;
$exportParams = array_merge([
'year' => (string) ($year ?? date('Y')),
'export' => '1',
], array_filter([
'gugun_code' => (string) ($gugunCode ?? ''),
'sa_idx' => (int) ($saIdx ?? 0) > 0 ? (string) (int) ($saIdx ?? 0) : '',
], static fn ($v): bool => $v !== '' && $v !== null && $v !== 0));
$excelUrl = mgmt_url('reports/yearly-sales?' . http_build_query($exportParams));
$colCount = 2 + count($colSpec ?? []);
$nMetricCols = max(1, count($colSpec ?? []));
$metricColPct = round(86 / $nMetricCols, 4);
$fmtMeasureCell = static function (array $cell, string $measureKey, bool $hasBsFee): string {
if ($measureKey === 'fee' && ! $hasBsFee) {
return '—';
}
if ($measureKey === 'qty') {
return number_format((int) ($cell['qty'] ?? 0));
}
return number_format((int) round((float) ($cell[$measureKey] ?? 0)));
};
?>
<?= view('components/print_header', [
'printTitle' => ((int) ($year ?? date('Y'))) . '년 판매 현황',
'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/yearly-sales') ?>" class="flex flex-wrap items-center gap-2">
<label class="text-sm text-gray-600">연도</label>
<select name="year" class="border border-gray-300 rounded px-2 py-1 text-sm">
<?php for ($y = (int) date('Y'); $y >= 2020; $y--): ?>
<option value="<?= $y ?>" <?= (int)($year ?? date('Y')) === $y ? 'selected' : '' ?>><?= $y ?>년</option>
<?php endfor; ?>
</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/yearly-sales') ?>" class="flex flex-wrap items-end gap-3 text-sm">
<div>
<label class="block text-gray-600 mb-0.5">조회 년도</label>
<select name="year" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[7rem]">
<?php for ($y = $yMax; $y >= $yMin; $y--): ?>
<option value="<?= $y ?>" <?= (int) ($year ?? date('Y')) === $y ? 'selected' : '' ?>><?= $y ?>년</option>
<?php endfor; ?>
</select>
</div>
<div>
<label class="block text-gray-600 mb-0.5">구·군</label>
<select name="gugun_code" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[10rem] max-w-[18rem]">
<option value="">전체</option>
<?php foreach ($gugunOptions ?? [] as $g): ?>
<?php $gc = (string) ($g['code'] ?? ''); ?>
<option value="<?= esc($gc, 'attr') ?>" <?= ($gugunCode ?? '') === $gc ? 'selected' : '' ?>><?= esc((string) ($g['name'] ?? $gc)) ?></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>
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm">조회</button>
</form>
</section>
<div class="border border-gray-300 overflow-auto mt-2">
<table class="w-full data-table">
<thead>
<tr>
<th>봉투코드</th>
<th>봉투명</th>
<th>1월</th><th>2월</th><th>3월</th><th>4월</th><th>5월</th><th>6월</th>
<th>7월</th><th>8월</th><th>9월</th><th>10월</th><th>11월</th><th>12월</th>
<th class="bg-gray-100">합계</th>
</tr>
</thead>
<tbody class="text-right">
<?php
$grandTotal = array_fill(1, 13, 0); // 1~12 + 13=total
foreach ($result as $row):
?>
<tr>
<td class="text-center font-mono"><?= esc($row->bs_bag_code) ?></td>
<td class="text-left pl-2"><?= esc($row->bs_bag_name) ?></td>
<?php for ($m = 1; $m <= 12; $m++):
$key = 'm' . sprintf('%02d', $m);
$val = (int) $row->$key;
$grandTotal[$m] += $val;
?>
<td><?= $val > 0 ? number_format($val) : '-' ?></td>
<?php endfor; ?>
<?php $grandTotal[13] += (int) $row->total; ?>
<td class="font-bold bg-gray-50"><?= number_format((int) $row->total) ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($result)): ?>
<tr><td colspan="15" class="text-center text-gray-400 py-4">조회된 데이터가 없습니다.</td></tr>
<?php else: ?>
<tr class="font-bold bg-gray-100">
<td colspan="2" class="text-center">합계</td>
<?php for ($m = 1; $m <= 12; $m++): ?>
<td><?= $grandTotal[$m] > 0 ? number_format($grandTotal[$m]) : '-' ?></td>
<?php endfor; ?>
<td class="bg-gray-200"><?= number_format($grandTotal[13]) ?></td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<section class="p-3 bg-white yearly-sales-report-section">
<style>
/* 화면: 가로 스크롤. 인쇄: 가로 용지 + 작은 글자 + 셀 줄바꿈으로 한 페이지에 맞춤 */
@media print {
@page {
size: A4 landscape;
margin: 5mm 6mm;
}
.yearly-sales-report-section {
padding: 0 !important;
}
.yearly-sales-scroll-wrap {
overflow: visible !important;
border: 1px solid #333 !important;
max-width: none !important;
}
#yearly-sales-table {
font-size: 7pt !important;
width: 100% !important;
max-width: 100% !important;
table-layout: fixed !important;
}
#yearly-sales-table th,
#yearly-sales-table td {
min-width: 0 !important;
max-width: none !important;
padding: 1px 2px !important;
white-space: normal !important;
word-break: break-word;
overflow-wrap: anywhere;
line-height: 1.15;
vertical-align: top;
}
#yearly-sales-table th {
font-size: 6.5pt !important;
font-weight: 700;
}
}
@media screen {
#yearly-sales-table td.tabular-nums {
white-space: nowrap;
}
}
</style>
<div class="mb-2 text-center no-print">
<h1 class="text-lg font-bold m-0"><?= (int) ($year ?? date('Y')) ?>년 판매 현황</h1>
<p class="text-sm text-gray-700 m-1"><?= esc(trim(($lgName ?? '') . ' · 구·군: ' . ($gugunLabel ?? '') . ' · 대행소: ' . ($agencyLabel ?? ''))) ?></p>
<p class="text-xs text-gray-500 m-0">(단위: 매 / 원)</p>
</div>
<div class="yearly-sales-scroll-wrap border border-gray-300 overflow-x-auto">
<table class="w-full data-table text-xs sm:text-sm" id="yearly-sales-table">
<colgroup>
<col style="width: 9%;"/>
<col style="width: 5%;"/>
<?php foreach (($colSpec ?? []) as $_): ?>
<col style="width: <?= esc((string) $metricColPct, 'attr') ?>%;"/>
<?php endforeach; ?>
</colgroup>
<thead>
<tr>
<th class="align-middle min-w-0 sm:min-w-[7rem] max-w-[10rem] sm:max-w-[12rem] text-left pl-2">품목</th>
<th class="align-middle min-w-0 sm:min-w-[4.5rem]">구분</th>
<?php foreach ($colSpec ?? [] as $col): ?>
<th class="align-middle text-center min-w-0 sm:min-w-[4.5rem] border-l border-gray-200"><?= esc((string) ($col['label'] ?? '')) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody class="text-right">
<?php if (! ($hasYearlyData ?? false)): ?>
<tr>
<td colspan="<?= (int) $colCount ?>" class="text-center text-gray-400 py-6">조회된 데이터가 없습니다.</td>
</tr>
<?php else: ?>
<?php foreach ($itemBlocks ?? [] as $block): ?>
<?php $lines = $block['lines'] ?? []; ?>
<?php foreach ($lines as $liIdx => $li): ?>
<tr class="odd:bg-white even:bg-gray-50/80">
<?php if ($liIdx === 0): ?>
<td rowspan="4" class="text-left align-top pl-2 pt-1 font-medium border-r border-gray-200"><?= esc((string) ($block['name'] ?? '')) ?></td>
<?php endif; ?>
<td class="text-left pl-2 border-r border-gray-100"><?= esc((string) ($li['measure'] ?? '')) ?></td>
<?php
$cells = (array) ($li['cells'] ?? []);
$mk = (string) ($li['measureKey'] ?? '');
?>
<?php foreach ($colSpec ?? [] as $col): ?>
<?php $cid = (string) ($col['id'] ?? ''); ?>
<?php $cell = (array) ($cells[$cid] ?? ['qty' => 0, 'amt' => 0.0, 'fee' => 0.0, 'levy' => 0.0]); ?>
<td class="border-l border-gray-100 tabular-nums"><?= $fmtMeasureCell($cell, $mk, (bool) ($hasBsFee ?? false)) ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
<?php endforeach; ?>
<?php $fLines = $footerBlock['lines'] ?? []; ?>
<?php foreach ($fLines as $fIdx => $li): ?>
<tr class="bg-amber-50 font-semibold border-t-2 border-amber-200">
<?php if ($fIdx === 0): ?>
<td rowspan="4" class="text-center align-middle text-amber-900 border-r border-amber-200"><?= esc((string) ($footerBlock['name'] ?? '전체 합계')) ?></td>
<?php endif; ?>
<td class="text-left pl-2 border-r border-amber-100"><?= esc((string) ($li['measure'] ?? '')) ?></td>
<?php
$cells = (array) ($li['cells'] ?? []);
$mk = (string) ($li['measureKey'] ?? '');
?>
<?php foreach ($colSpec ?? [] as $col): ?>
<?php $cid = (string) ($col['id'] ?? ''); ?>
<?php $cell = (array) ($cells[$cid] ?? ['qty' => 0, 'amt' => 0.0, 'fee' => 0.0, 'levy' => 0.0]); ?>
<td class="border-l border-amber-100 tabular-nums"><?= $fmtMeasureCell($cell, $mk, (bool) ($hasBsFee ?? false)) ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</section>
<script>
(function () {
const year = <?= json_encode((int) ($year ?? (int) date('Y')), JSON_THROW_ON_ERROR) ?>;
let savedTitle = document.title;
function stamp() {
const d = new Date();
const p = (n) => String(n).padStart(2, '0');
return d.getFullYear() + p(d.getMonth() + 1) + p(d.getDate()) + '_' + p(d.getHours()) + p(d.getMinutes()) + p(d.getSeconds());
}
window.addEventListener('beforeprint', function () {
savedTitle = document.title;
document.title = '년판매현황_' + year + '_' + stamp();
});
window.addEventListener('afterprint', function () {
document.title = savedTitle;
});
})();
</script>

View File

@@ -1,74 +1,301 @@
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<span class="text-sm font-bold text-gray-700">주문 접수</span>
</section>
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-4xl">
<div class="border border-gray-300 p-4 mt-2 bg-white">
<form action="<?= mgmt_url('shop-orders/store') ?>" method="POST" class="space-y-4">
<?= csrf_field() ?>
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">판매소 <span class="text-red-500">*</span></label>
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="so_ds_idx" required>
<option value="">선택</option>
<?php foreach ($shops as $shop): ?>
<option value="<?= esc($shop->ds_idx) ?>" <?= (int) old('so_ds_idx') === (int) $shop->ds_idx ? 'selected' : '' ?>>
<?= esc($shop->ds_name) ?>
</option>
<?php endforeach; ?>
</select>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div class="space-y-3">
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">판매소 검색 <span class="text-red-500">*</span></label>
<input id="shop-search" class="border border-gray-300 rounded px-3 py-1.5 text-sm w-72" type="text" list="shop-search-list" placeholder="코드/사업자번호/대표자명/상호/전화/주소"/>
<datalist id="shop-search-list">
<?php foreach ($shops as $shop): ?>
<option value="<?= esc(trim(($shop->ds_shop_no ?? '') . ' ' . ($shop->ds_name ?? '') . ' ' . ($shop->ds_rep_name ?? '') . ' ' . ($shop->ds_biz_no ?? '') . ' ' . ($shop->ds_tel ?? '') . ' ' . ($shop->ds_addr ?? ''))) ?>"></option>
<?php endforeach; ?>
</datalist>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">판매소 선택 <span class="text-red-500">*</span></label>
<select id="shop-select" class="border border-gray-300 rounded px-3 py-1.5 text-sm w-72" name="so_ds_idx" required>
<option value="">선택</option>
<?php foreach ($shops as $shop): ?>
<option
value="<?= esc($shop->ds_idx) ?>"
data-shop-no="<?= esc((string) ($shop->ds_shop_no ?? '')) ?>"
data-name="<?= esc((string) ($shop->ds_name ?? '')) ?>"
data-rep-name="<?= esc((string) ($shop->ds_rep_name ?? '')) ?>"
data-rep-phone="<?= esc((string) ($shop->ds_rep_phone ?? '')) ?>"
data-tel="<?= esc((string) ($shop->ds_tel ?? '')) ?>"
data-address="<?= esc(trim((string) ($shop->ds_addr ?? '') . ' ' . (string) ($shop->ds_addr_detail ?? ''))) ?>"
data-va-bank="<?= esc((string) ($shop->ds_va_bank ?? '')) ?>"
data-va-account="<?= esc((string) ($shop->ds_va_account ?? '')) ?>"
<?= (int) old('so_ds_idx') === (int) $shop->ds_idx ? 'selected' : '' ?>
>
<?= esc(($shop->ds_shop_no ? '[' . $shop->ds_shop_no . '] ' : '') . $shop->ds_name) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="border border-gray-300 p-2 bg-gray-50">
<div class="text-sm font-bold text-gray-700 mb-2">지정판매소 정보</div>
<table class="w-full text-sm">
<tr><th class="text-left w-28 py-1">판매소 코드</th><td id="shop-info-code" class="py-1 text-gray-700">-</td></tr>
<tr><th class="text-left py-1">상호</th><td id="shop-info-name" class="py-1 text-gray-700">-</td></tr>
<tr><th class="text-left py-1">대표자명</th><td id="shop-info-rep" class="py-1 text-gray-700">-</td></tr>
<tr><th class="text-left py-1">연락처</th><td id="shop-info-tel" class="py-1 text-gray-700">-</td></tr>
<tr><th class="text-left py-1">주소</th><td id="shop-info-addr" class="py-1 text-gray-700">-</td></tr>
<tr><th class="text-left py-1">가상계좌</th><td id="shop-info-va" class="py-1 text-gray-700">-</td></tr>
</table>
</div>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">접수일</label>
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44 bg-gray-100" type="date" value="<?= esc(date('Y-m-d')) ?>" readonly/>
<span class="text-xs text-gray-500">배달일 기본값은 접수일 다음날입니다.</span>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">배달일 <span class="text-red-500">*</span></label>
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="so_delivery_date" type="date" value="<?= esc(old('so_delivery_date', date('Y-m-d', strtotime('+1 day')))) ?>" required/>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="block text-sm font-bold text-gray-700 w-28">결제방법 <span class="text-red-500">*</span></label>
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="so_payment_type" required>
<select id="payment-type" class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="so_payment_type" required>
<option value="">선택</option>
<option value="이체" <?= old('so_payment_type') === '이체' ? 'selected' : '' ?>>이체</option>
<option value="가상계좌" <?= old('so_payment_type') === '가상계좌' ? 'selected' : '' ?>>가상계좌</option>
</select>
<span id="payment-guide" class="text-xs text-gray-500"></span>
</div>
<div class="mt-4">
<label class="block text-sm font-bold text-gray-700 mb-2">주문 품목</label>
<div class="flex items-center justify-between mb-2">
<label class="block text-sm font-bold text-gray-700">전화 주문 접수표</label>
<button type="button" id="add-order-row" class="border border-gray-300 bg-white px-3 py-1 rounded-sm text-xs text-gray-700 hover:bg-gray-50">행 추가</button>
</div>
<div class="border border-gray-300 overflow-auto">
<table class="w-full data-table">
<table class="w-full data-table text-sm">
<thead>
<tr>
<th class="w-16">순번</th>
<th>봉투</th>
<th class="w-32">수량</th>
<th class="w-14">순번</th>
<th class="w-48">품목</th>
<th class="w-36">1박스(낱장/판매가)</th>
<th class="w-36">1팩(낱장/판매가)</th>
<th class="w-24">단가</th>
<th class="w-28">주문수량</th>
<th class="w-28">금액</th>
<th class="w-32">포장(박스/팩/낱장)</th>
<th class="w-20">행삭제</th>
</tr>
</thead>
<tbody>
<tbody id="order-rows">
<?php for ($i = 0; $i < 3; $i++): ?>
<tr>
<td class="text-center"><?= $i + 1 ?></td>
<tr class="order-row">
<td class="text-center row-no"><?= $i + 1 ?></td>
<td>
<select class="border border-gray-300 rounded px-2 py-1 text-sm w-full" name="item_bag_code[]">
<select class="border border-gray-300 rounded px-2 py-1 text-sm w-full bag-code-select" name="item_bag_code[]">
<option value="">선택</option>
<?php foreach ($bagCodes as $cd): ?>
<option value="<?= esc($cd->cd_code) ?>">
<?php $code = (string) $cd->cd_code; $price = $priceMap[$code] ?? null; $unit = $unitMap[$code] ?? null; ?>
<option value="<?= esc($code) ?>" data-unit-price="<?= esc((string) (int) ($price->bp_consumer ?? 0)) ?>" data-box-sheets="<?= esc((string) (int) ($unit->pu_total_per_box ?? 0)) ?>" data-box-packs="<?= esc((string) (int) ($unit->pu_box_per_pack ?? 0)) ?>" data-pack-sheets="<?= esc((string) (int) ($unit->pu_pack_per_sheet ?? 0)) ?>">
<?= esc($cd->cd_code) ?> — <?= esc($cd->cd_name) ?>
</option>
<?php endforeach; ?>
</select>
</td>
<td>
<input class="border border-gray-300 rounded px-2 py-1 text-sm w-full text-right" name="item_qty[]" type="number" min="0" value="0"/>
<td class="text-right px-2 box-info-cell">0 / 0</td>
<td class="text-right px-2 pack-info-cell">0 / 0</td>
<td class="text-right px-2 unit-price-cell">0</td>
<td><input class="border border-gray-300 rounded px-2 py-1 text-sm w-full text-right item-qty-input" name="item_qty[]" type="number" min="0" value="0"/></td>
<td><input class="border border-gray-300 rounded px-2 py-1 text-sm w-full text-right item-amount-input" type="number" min="0" step="1" value="0"/></td>
<td class="text-right px-2 pack-result-cell">박스=0, 팩=0, 낱장=0</td>
<td class="text-center px-2">
<button type="button" class="remove-order-row border border-red-300 text-red-600 px-2 py-0.5 rounded text-xs hover:bg-red-50">삭제</button>
</td>
</tr>
<?php endfor; ?>
</tbody>
<tfoot>
<tr class="font-semibold bg-gray-50">
<td colspan="5" class="text-right px-2 py-1">합계</td>
<td class="text-right px-2 py-1" id="sum-qty">0</td>
<td class="text-right px-2 py-1" id="sum-amount">0</td>
<td class="text-right px-2 py-1" id="sum-pack">박스=0, 팩=0, 낱장=0</td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="flex gap-2 pt-2">
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">저장</button>
<a href="<?= mgmt_url('shop-orders') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
</div>
</form>
</div>
<template id="order-row-template">
<tr class="order-row">
<td class="text-center row-no">1</td>
<td>
<select class="border border-gray-300 rounded px-2 py-1 text-sm w-full bag-code-select" name="item_bag_code[]">
<option value="">선택</option>
<?php foreach ($bagCodes as $cd): ?>
<?php $code = (string) $cd->cd_code; $price = $priceMap[$code] ?? null; $unit = $unitMap[$code] ?? null; ?>
<option value="<?= esc($code) ?>" data-unit-price="<?= esc((string) (int) ($price->bp_consumer ?? 0)) ?>" data-box-sheets="<?= esc((string) (int) ($unit->pu_total_per_box ?? 0)) ?>" data-box-packs="<?= esc((string) (int) ($unit->pu_box_per_pack ?? 0)) ?>" data-pack-sheets="<?= esc((string) (int) ($unit->pu_pack_per_sheet ?? 0)) ?>">
<?= esc($cd->cd_code) ?> — <?= esc($cd->cd_name) ?>
</option>
<?php endforeach; ?>
</select>
</td>
<td class="text-right px-2 box-info-cell">0 / 0</td>
<td class="text-right px-2 pack-info-cell">0 / 0</td>
<td class="text-right px-2 unit-price-cell">0</td>
<td><input class="border border-gray-300 rounded px-2 py-1 text-sm w-full text-right item-qty-input" name="item_qty[]" type="number" min="0" value="0"/></td>
<td><input class="border border-gray-300 rounded px-2 py-1 text-sm w-full text-right item-amount-input" type="number" min="0" step="1" value="0"/></td>
<td class="text-right px-2 pack-result-cell">박스=0, 팩=0, 낱장=0</td>
<td class="text-center px-2">
<button type="button" class="remove-order-row border border-red-300 text-red-600 px-2 py-0.5 rounded text-xs hover:bg-red-50">삭제</button>
</td>
</tr>
</template>
<script>
(function () {
const shopSearch = document.getElementById('shop-search');
const shopSelect = document.getElementById('shop-select');
const paymentType = document.getElementById('payment-type');
const paymentGuide = document.getElementById('payment-guide');
const addRowButton = document.getElementById('add-order-row');
const orderRows = document.getElementById('order-rows');
const rowTemplate = document.getElementById('order-row-template');
const form = shopSelect.closest('form');
function nf(n) { return new Intl.NumberFormat('ko-KR').format(n || 0); }
function updateShopInfo() {
const opt = shopSelect.options[shopSelect.selectedIndex];
const bank = opt?.dataset?.vaBank || '';
const account = opt?.dataset?.vaAccount || '';
const va = bank || account ? [bank, account].filter(Boolean).join(' ') : '-';
document.getElementById('shop-info-code').textContent = opt?.dataset?.shopNo || '-';
document.getElementById('shop-info-name').textContent = opt?.dataset?.name || '-';
document.getElementById('shop-info-rep').textContent = opt?.dataset?.repName || '-';
document.getElementById('shop-info-tel').textContent = opt?.dataset?.tel || opt?.dataset?.repPhone || '-';
document.getElementById('shop-info-addr').textContent = opt?.dataset?.address || '-';
document.getElementById('shop-info-va').textContent = va;
paymentGuide.textContent = paymentType.value === '가상계좌' ? ('가상계좌 안내: ' + va) : '';
}
function matchShopByKeyword(keyword) {
const q = (keyword || '').trim().toLowerCase();
if (!q) { return; }
for (let i = 0; i < shopSelect.options.length; i++) {
const opt = shopSelect.options[i];
const merged = [opt.dataset.shopNo || '', opt.dataset.name || '', opt.dataset.repName || '', opt.dataset.tel || '', opt.dataset.address || '', opt.text || ''].join(' ').toLowerCase();
if (merged.includes(q)) { shopSelect.selectedIndex = i; updateShopInfo(); return; }
}
}
function calcRow(row, source) {
const select = row.querySelector('.bag-code-select');
const qtyInput = row.querySelector('.item-qty-input');
const amountInput = row.querySelector('.item-amount-input');
const selected = select.options[select.selectedIndex];
let qty = parseInt(qtyInput.value || '0', 10) || 0;
const unitPrice = parseInt(selected?.dataset?.unitPrice || '0', 10) || 0;
const boxSheets = parseInt(selected?.dataset?.boxSheets || '0', 10) || 0;
const boxPacks = parseInt(selected?.dataset?.boxPacks || '0', 10) || 0;
const packSheets = parseInt(selected?.dataset?.packSheets || '0', 10) || 0;
const rawAmount = parseInt(amountInput?.value || '0', 10) || 0;
if (source === 'amount' && unitPrice > 0) {
qty = Math.max(0, Math.round(rawAmount / unitPrice));
qtyInput.value = String(qty);
}
let box = 0, pack = 0, sheet = qty;
if (boxSheets > 0) {
box = Math.floor(qty / boxSheets);
const remain = qty % boxSheets;
if (packSheets > 0) { pack = Math.floor(remain / packSheets); sheet = remain % packSheets; } else { sheet = remain; }
} else if (packSheets > 0) {
pack = Math.floor(qty / packSheets);
sheet = qty % packSheets;
}
const amount = unitPrice * qty;
const boxPrice = boxSheets * unitPrice;
const packPrice = packSheets * unitPrice;
row.querySelector('.box-info-cell').textContent = nf(boxSheets) + ' / ' + nf(boxPrice);
row.querySelector('.pack-info-cell').textContent = nf(packSheets) + ' / ' + nf(packPrice);
row.querySelector('.unit-price-cell').textContent = nf(unitPrice);
if (amountInput && source !== 'amount') {
amountInput.value = String(amount);
}
const innerPackCount = box * boxPacks;
const innerSheetCount = box * boxSheets;
row.querySelector('.pack-result-cell').textContent = '박스=' + nf(box) + '(내부 팩=' + nf(innerPackCount) + ', 내부 낱장=' + nf(innerSheetCount) + '), 잔여 팩=' + nf(pack) + ', 잔여 낱장=' + nf(sheet);
return { qty, amount, box, pack, sheet };
}
function recalcAllRows(sourceRow, sourceType) {
let sumQty = 0, sumAmount = 0, sumBox = 0, sumPack = 0, sumSheet = 0;
document.querySelectorAll('.order-row').forEach((row, index) => {
const noCell = row.querySelector('.row-no');
if (noCell) {
noCell.textContent = String(index + 1);
}
const source = row === sourceRow ? sourceType : 'qty';
const r = calcRow(row, source);
sumQty += r.qty; sumAmount += r.amount; sumBox += r.box; sumPack += r.pack; sumSheet += r.sheet;
});
document.getElementById('sum-qty').textContent = nf(sumQty);
document.getElementById('sum-amount').textContent = nf(sumAmount);
document.getElementById('sum-pack').textContent = '박스=' + nf(sumBox) + ', 팩=' + nf(sumPack) + ', 낱장=' + nf(sumSheet);
}
shopSearch?.addEventListener('change', (e) => matchShopByKeyword(e.target.value));
shopSearch?.addEventListener('blur', (e) => matchShopByKeyword(e.target.value));
shopSelect?.addEventListener('change', updateShopInfo);
paymentType?.addEventListener('change', updateShopInfo);
orderRows?.addEventListener('change', function (e) {
const row = e.target.closest('.order-row');
if (e.target.closest('.bag-code-select') || e.target.closest('.item-qty-input')) {
recalcAllRows(row, 'qty');
} else if (e.target.closest('.item-amount-input')) {
recalcAllRows(row, 'amount');
}
});
orderRows?.addEventListener('input', function (e) {
const row = e.target.closest('.order-row');
if (e.target.closest('.item-qty-input')) {
recalcAllRows(row, 'qty');
}
});
orderRows?.addEventListener('click', function (e) {
const removeButton = e.target.closest('.remove-order-row');
if (!removeButton) {
return;
}
const row = removeButton.closest('.order-row');
if (!row) {
return;
}
if (orderRows.querySelectorAll('.order-row').length <= 1) {
alert('최소 1개 행은 유지해야 합니다.');
return;
}
row.remove();
recalcAllRows(null, 'qty');
});
addRowButton?.addEventListener('click', function () {
if (!rowTemplate || !orderRows) {
return;
}
const fragment = rowTemplate.content.cloneNode(true);
orderRows.appendChild(fragment);
recalcAllRows(null, 'qty');
});
form?.addEventListener('submit', function (e) {
let hasItem = false;
document.querySelectorAll('.order-row').forEach((row) => {
const code = row.querySelector('.bag-code-select').value;
const qty = parseInt(row.querySelector('.item-qty-input').value || '0', 10) || 0;
if (code && qty > 0) { hasItem = true; }
});
if (!hasItem) { e.preventDefault(); alert('주문 품목과 수량을 1개 이상 입력해 주세요.'); }
});
updateShopInfo();
recalcAllRows(null, 'qty');
})();
</script>

View File

@@ -26,6 +26,7 @@
<th>판매소</th>
<th>접수일</th>
<th>배달일</th>
<th>접수채널</th>
<th>결제</th>
<th>입금</th>
<th>수령</th>
@@ -42,6 +43,12 @@
<td class="text-left pl-2"><?= esc($row->so_ds_name) ?></td>
<td class="text-center"><?= esc($row->so_order_date) ?></td>
<td class="text-center"><?= esc($row->so_delivery_date) ?></td>
<td class="text-center">
<?php
$channelMap = ['phone' => '전화', 'web' => '웹', 'app' => '앱', 'counter' => '창구'];
echo esc($channelMap[$row->so_channel ?? ''] ?? ($row->so_channel ?? '전화'));
?>
</td>
<td class="text-center"><?= esc($row->so_payment_type) ?></td>
<td class="text-center">
<?php
@@ -72,7 +79,7 @@
</tr>
<?php endforeach; ?>
<?php if (empty($list)): ?>
<tr><td colspan="11" class="text-center text-gray-400 py-4">등록된 주문이 없습니다.</td></tr>
<tr><td colspan="12" class="text-center text-gray-400 py-4">등록된 주문이 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>