사용자 매뉴얼·번호알기·gov-portal 대시보드와 메뉴 동선·수불 리포트를 보강한다.

- 사용자 매뉴얼: league/commonmark 기반 bag/manual(로그인 전용),
  ManualRenderer + Config\Manual manifest, 콘텐츠 8종, E2E
- 번호알기(봉투번호확인): bag/number-lookup, BagNumberLookup, E2E
- gov-portal 대시보드 시안(기본/strip)·기본코드관리 화면
- 메뉴 관리: 등록·수정 후 메뉴 화면 유지, 수정 버튼 클릭 시 상단 스크롤
- 수불/분석 리포트(LOT 수불·반품/파기·수급계획·추이) 표시 보강
- .gitignore: docs/ → /docs/ 앵커링(최상위 개발문서만 제외, app/Docs는 추적)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
taekyoungc
2026-06-08 00:46:51 +09:00
parent 0f1d414f37
commit 8763876f19
77 changed files with 6139 additions and 182 deletions

View File

@@ -2582,7 +2582,6 @@ class SalesReport extends BaseController
'endDate' => $endDate,
'ioType' => $ioType,
'queried' => $queried,
'exportQuery' => $this->returnsExportQueryString(),
]);
}
@@ -2594,12 +2593,24 @@ class SalesReport extends BaseController
return redirect()->to(mgmt_url('reports/returns'))->with('error', '지자체를 선택해 주세요.');
}
if ($this->request->getGet('search') !== '1') {
$startDate = trim((string) ($this->request->getGet('start_date') ?? ''));
$endDate = trim((string) ($this->request->getGet('end_date') ?? ''));
$hasQuery = $this->request->getGet('search') === '1'
|| ($startDate !== '' && $endDate !== '');
if (! $hasQuery) {
return redirect()->to(mgmt_url('reports/returns'))->with('error', '조회 후 엑셀 저장을 이용해 주세요.');
}
$startDate = (string) ($this->request->getGet('start_date') ?? date('Y-m-01'));
$endDate = (string) ($this->request->getGet('end_date') ?? date('Y-m-d'));
if ($startDate === '') {
$startDate = date('Y-m-01');
}
if ($endDate === '') {
$endDate = date('Y-m-d');
}
if ($startDate > $endDate) {
[$startDate, $endDate] = [$endDate, $startDate];
}
$ioType = (string) ($this->request->getGet('io_type') ?? 'out');
if (! in_array($ioType, ['in', 'out'], true)) {
$ioType = 'out';
@@ -2628,22 +2639,77 @@ class SalesReport extends BaseController
}
/**
* 출고 = 지정판매소 반품(designated-return · bag_return_scan_code)
* 입고 = 물류 입고분 파기(bag_dispose)
*
* @return list<object>
*/
private function fetchReturnDisposeRows(int $lgIdx, string $startDate, string $endDate, string $ioType): array
{
$bsTypes = $ioType === 'in' ? ['return'] : ['cancel'];
$typePlaceholders = implode(',', array_fill(0, count($bsTypes), '?'));
$db = \Config\Database::connect();
if ($ioType === 'out') {
return $this->fetchDesignatedReturnRows($db, $lgIdx, $startDate, $endDate);
}
return $this->fetchBagDisposeRows($db, $lgIdx, $startDate, $endDate);
}
/**
* @return list<object>
*/
private function fetchDesignatedReturnRows($db, int $lgIdx, string $startDate, string $endDate): array
{
if ($db->tableExists('bag_return_scan_code')) {
return $db->query("
SELECT r.brsc_return_date AS bs_sale_date,
COALESCE(ds.ds_name, '') AS bs_ds_name,
r.brsc_bag_code AS bs_bag_code,
r.brsc_bag_name AS bs_bag_name,
'return' AS bs_type,
SUM(r.brsc_qty) AS qty
FROM bag_return_scan_code r
LEFT JOIN designated_shop ds
ON ds.ds_idx = r.brsc_ds_idx AND ds.ds_lg_idx = r.brsc_lg_idx
WHERE r.brsc_lg_idx = ?
AND r.brsc_return_date BETWEEN ? AND ?
AND r.brsc_state = 'returned'
GROUP BY r.brsc_return_date, r.brsc_ds_idx, ds.ds_name,
r.brsc_bag_code, r.brsc_bag_name
ORDER BY r.brsc_return_date ASC, bs_ds_name ASC, r.brsc_bag_code ASC
", [$lgIdx, $startDate, $endDate])->getResult();
}
return $db->query("
SELECT bs_sale_date, bs_ds_name, bs_bag_code, bs_bag_name, bs_type,
ABS(bs_qty) AS qty
FROM bag_sale
WHERE bs_lg_idx = ? AND bs_sale_date BETWEEN ? AND ?
AND bs_type IN ({$typePlaceholders})
AND bs_type = 'return'
ORDER BY bs_sale_date ASC, bs_ds_name ASC, bs_bag_code ASC
", array_merge([$lgIdx, $startDate, $endDate], $bsTypes))->getResult();
", [$lgIdx, $startDate, $endDate])->getResult();
}
/**
* @return list<object>
*/
private function fetchBagDisposeRows($db, int $lgIdx, string $startDate, string $endDate): array
{
if (! $db->tableExists('bag_dispose')) {
return [];
}
return $db->query("
SELECT bd_dispose_date AS bs_sale_date,
bd_location AS bs_ds_name,
bd_bag_code AS bs_bag_code,
bd_bag_name AS bs_bag_name,
'dispose' AS bs_type,
bd_qty AS qty
FROM bag_dispose
WHERE bd_lg_idx = ? AND bd_dispose_date BETWEEN ? AND ?
ORDER BY bd_dispose_date ASC, bd_location ASC, bd_bag_code ASC
", [$lgIdx, $startDate, $endDate])->getResult();
}
private function returnDisposeKindLabel(object $row): string
@@ -2660,24 +2726,13 @@ class SalesReport extends BaseController
private function returnDisposeTypeLabel(string $bsType): string
{
return match ($bsType) {
'return' => '반품',
'cancel' => '파기',
default => $bsType,
'return' => '반품',
'dispose' => '파기',
'cancel' => '파기',
default => $bsType,
};
}
private function returnsExportQueryString(): string
{
$params = array_filter([
'search' => '1',
'start_date' => $this->request->getGet('start_date'),
'end_date' => $this->request->getGet('end_date'),
'io_type' => $this->request->getGet('io_type'),
], static fn ($v) => $v !== null && $v !== '');
return http_build_query($params);
}
/**
* P5-10: LOT 수불 조회 (레거시 w_gd033a — 바코드/봉투번호)
*/