feat: 실사 재고 바코드 리스트 레이지 로딩 — 스크롤 시 추가 로딩
- 상위 2,000건 상한 제거 → 초기 200건 렌더 후 스크롤 시 청크(200건) 추가 로딩 - 서버: ajax=1&offset&limit 로 청크 JSON 반환, 합계는 SQL 집계(전체 행 미적재) - 클라: IntersectionObserver 자동 로딩 + '더 보기' 폴백 버튼 - 인쇄 시 전체 먼저 로드 후 출력(부분 인쇄 방지) - fetch는 현재 origin 기준 상대경로로 호출(baseURL/포트 불일치에도 동작) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1324,64 +1324,86 @@ SQL);
|
||||
return redirect()->to(site_url('bag/inventory'))->with('error', '지자체를 선택해 주세요.');
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$bagCode = trim((string) ($this->request->getGet('bag_code') ?? ''));
|
||||
$keyword = trim((string) ($this->request->getGet('keyword') ?? ''));
|
||||
$db = \Config\Database::connect();
|
||||
$bagCode = trim((string) ($this->request->getGet('bag_code') ?? ''));
|
||||
$keyword = trim((string) ($this->request->getGet('keyword') ?? ''));
|
||||
$pageSize = 200; // 레이지 로딩 청크 크기
|
||||
|
||||
// 품목 선택 옵션 — 현재 재고로 남아있는 품목만
|
||||
$itemOptions = $db->table('bag_receiving_pack_code')
|
||||
->select('brpc_bag_code, MAX(brpc_bag_name) AS bag_name, COUNT(*) AS pack_cnt, SUM(brpc_sheet_qty) AS sheet_qty', false)
|
||||
->where('brpc_lg_idx', $lgIdx)
|
||||
->where('brpc_state', 'in_stock')
|
||||
->where('brpc_bag_code !=', '')
|
||||
->groupBy('brpc_bag_code')
|
||||
->orderBy('brpc_bag_code', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$builder = $db->table('bag_receiving_pack_code')
|
||||
->select('brpc_bag_code, brpc_bag_name, brpc_lot_no, brpc_box_code, brpc_pack_code, brpc_sheet_start_code, brpc_sheet_end_code, brpc_sheet_qty')
|
||||
->where('brpc_lg_idx', $lgIdx)
|
||||
->where('brpc_state', 'in_stock');
|
||||
if ($bagCode !== '') {
|
||||
$builder->where('brpc_bag_code', $bagCode);
|
||||
}
|
||||
if ($keyword !== '') {
|
||||
$builder->groupStart()
|
||||
->like('brpc_box_code', $keyword)
|
||||
->orLike('brpc_pack_code', $keyword)
|
||||
->orLike('brpc_sheet_start_code', $keyword)
|
||||
->orLike('brpc_sheet_end_code', $keyword)
|
||||
->orLike('brpc_lot_no', $keyword)
|
||||
->groupEnd();
|
||||
}
|
||||
$rows = $builder
|
||||
->orderBy('brpc_bag_code', 'ASC')
|
||||
->orderBy('brpc_box_code', 'ASC')
|
||||
->orderBy('brpc_pack_code', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
// 합계 — 박스 수(고유), 팩 수, 총 매수
|
||||
$totalPacks = count($rows);
|
||||
$totalSheets = 0;
|
||||
$boxSet = [];
|
||||
foreach ($rows as $r) {
|
||||
$totalSheets += (int) ($r['brpc_sheet_qty'] ?? 0);
|
||||
$bx = trim((string) ($r['brpc_box_code'] ?? ''));
|
||||
if ($bx !== '') {
|
||||
$boxSet[$bx] = true;
|
||||
// 공통 필터(재고 상태 + 품목/키워드)를 빌더에 적용
|
||||
$applyFilter = static function ($builder) use ($lgIdx, $bagCode, $keyword) {
|
||||
$builder->where('brpc_lg_idx', $lgIdx)->where('brpc_state', 'in_stock');
|
||||
if ($bagCode !== '') {
|
||||
$builder->where('brpc_bag_code', $bagCode);
|
||||
}
|
||||
}
|
||||
$totalBoxes = count($boxSet);
|
||||
if ($keyword !== '') {
|
||||
$builder->groupStart()
|
||||
->like('brpc_box_code', $keyword)
|
||||
->orLike('brpc_pack_code', $keyword)
|
||||
->orLike('brpc_sheet_start_code', $keyword)
|
||||
->orLike('brpc_sheet_end_code', $keyword)
|
||||
->orLike('brpc_lot_no', $keyword)
|
||||
->groupEnd();
|
||||
}
|
||||
return $builder;
|
||||
};
|
||||
|
||||
// 엑셀 다운로드 — 현재 필터 그대로
|
||||
// 필터 적용된 행을 offset/limit(0=전체)로 조회
|
||||
$fetchRows = function (int $offset, int $limit) use ($db, $applyFilter): array {
|
||||
$builder = $applyFilter($db->table('bag_receiving_pack_code'))
|
||||
->select('brpc_bag_code, brpc_bag_name, brpc_lot_no, brpc_box_code, brpc_pack_code, brpc_sheet_start_code, brpc_sheet_end_code, brpc_sheet_qty')
|
||||
->orderBy('brpc_bag_code', 'ASC')
|
||||
->orderBy('brpc_box_code', 'ASC')
|
||||
->orderBy('brpc_pack_code', 'ASC');
|
||||
if ($limit > 0) {
|
||||
$builder->limit($limit, max(0, $offset));
|
||||
}
|
||||
return $builder->get()->getResultArray();
|
||||
};
|
||||
|
||||
// 합계 — 전체 기준 SQL 집계(전체 행을 메모리에 올리지 않음)
|
||||
$agg = $applyFilter($db->table('bag_receiving_pack_code'))
|
||||
->select('COUNT(*) AS total_packs, COALESCE(SUM(brpc_sheet_qty),0) AS total_sheets, COUNT(DISTINCT brpc_box_code) AS total_boxes', false)
|
||||
->get()
|
||||
->getRowArray();
|
||||
$totalPacks = (int) ($agg['total_packs'] ?? 0);
|
||||
$totalSheets = (int) ($agg['total_sheets'] ?? 0);
|
||||
$totalBoxes = (int) ($agg['total_boxes'] ?? 0);
|
||||
|
||||
// AJAX 레이지 로딩 — 다음 청크만 JSON 으로 반환
|
||||
if ((string) ($this->request->getGet('ajax') ?? '') === '1') {
|
||||
$offset = max(0, (int) ($this->request->getGet('offset') ?? 0));
|
||||
$limit = (int) ($this->request->getGet('limit') ?? $pageSize);
|
||||
$limit = $limit <= 0 ? $pageSize : min($limit, 5000);
|
||||
$chunk = $fetchRows($offset, $limit);
|
||||
$out = [];
|
||||
foreach ($chunk as $r) {
|
||||
$out[] = [
|
||||
'name' => ($r['brpc_bag_name'] ?? '') !== '' ? (string) $r['brpc_bag_name'] : (string) ($r['brpc_bag_code'] ?? ''),
|
||||
'lot' => (string) ($r['brpc_lot_no'] ?? ''),
|
||||
'box' => (string) ($r['brpc_box_code'] ?? ''),
|
||||
'pack' => (string) ($r['brpc_pack_code'] ?? ''),
|
||||
'start' => (string) ($r['brpc_sheet_start_code'] ?? ''),
|
||||
'end' => (string) ($r['brpc_sheet_end_code'] ?? ''),
|
||||
'qty' => (int) ($r['brpc_sheet_qty'] ?? 0),
|
||||
];
|
||||
}
|
||||
$next = $offset + count($chunk);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'rows' => $out,
|
||||
'offset' => $next,
|
||||
'hasMore' => $next < $totalPacks,
|
||||
'total' => $totalPacks,
|
||||
]);
|
||||
}
|
||||
|
||||
// 엑셀 다운로드 — 현재 필터 전체
|
||||
if ((string) ($this->request->getGet('export') ?? '') === '1') {
|
||||
helper('export');
|
||||
$exportRows = [];
|
||||
foreach ($rows as $r) {
|
||||
foreach ($fetchRows(0, 0) as $r) {
|
||||
$exportRows[] = [
|
||||
(string) ($r['brpc_bag_name'] ?? '') !== '' ? (string) $r['brpc_bag_name'] : (string) ($r['brpc_bag_code'] ?? ''),
|
||||
($r['brpc_bag_name'] ?? '') !== '' ? (string) $r['brpc_bag_name'] : (string) ($r['brpc_bag_code'] ?? ''),
|
||||
(string) ($r['brpc_lot_no'] ?? ''),
|
||||
(string) ($r['brpc_box_code'] ?? ''),
|
||||
(string) ($r['brpc_pack_code'] ?? ''),
|
||||
@@ -1399,21 +1421,30 @@ SQL);
|
||||
);
|
||||
}
|
||||
|
||||
// 화면 표시는 성능을 위해 상위 N건까지만(합계·엑셀 저장은 항상 전체 기준)
|
||||
$displayLimit = 2000;
|
||||
$displayTruncated = count($rows) > $displayLimit;
|
||||
$displayRows = $displayTruncated ? array_slice($rows, 0, $displayLimit) : $rows;
|
||||
// 품목 선택 옵션 — 현재 재고로 남아있는 품목만
|
||||
$itemOptions = $db->table('bag_receiving_pack_code')
|
||||
->select('brpc_bag_code, MAX(brpc_bag_name) AS bag_name, COUNT(*) AS pack_cnt, SUM(brpc_sheet_qty) AS sheet_qty', false)
|
||||
->where('brpc_lg_idx', $lgIdx)
|
||||
->where('brpc_state', 'in_stock')
|
||||
->where('brpc_bag_code !=', '')
|
||||
->groupBy('brpc_bag_code')
|
||||
->orderBy('brpc_bag_code', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
// 초기 1페이지만 렌더 — 나머지는 스크롤 시 레이지 로딩
|
||||
$rows = $fetchRows(0, $pageSize);
|
||||
|
||||
return $this->render('실사 재고 바코드 리스트', 'bag/inventory_stock_barcode_list', [
|
||||
'bagCode' => $bagCode,
|
||||
'keyword' => $keyword,
|
||||
'itemOptions' => $itemOptions,
|
||||
'rows' => $displayRows,
|
||||
'totalBoxes' => $totalBoxes,
|
||||
'totalPacks' => $totalPacks,
|
||||
'totalSheets' => $totalSheets,
|
||||
'displayTruncated' => $displayTruncated,
|
||||
'displayLimit' => $displayLimit,
|
||||
'bagCode' => $bagCode,
|
||||
'keyword' => $keyword,
|
||||
'itemOptions' => $itemOptions,
|
||||
'rows' => $rows,
|
||||
'totalBoxes' => $totalBoxes,
|
||||
'totalPacks' => $totalPacks,
|
||||
'totalSheets' => $totalSheets,
|
||||
'pageSize' => $pageSize,
|
||||
'loadedCount' => count($rows),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user