사이트·관리자 봉투 물류 기능(수불·통계·레포트·재고·발주)과 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

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