From 5ef50344af8e5de6e6cbb2931e4f135b2d64d6a2 Mon Sep 17 00:00:00 2001 From: taekyoungc Date: Thu, 2 Jul 2026 11:53:31 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=EB=B0=B1(=EB=AC=B8=EC=9D=98=EC=82=AC=ED=95=AD)=20?= =?UTF-8?q?=EC=9D=BC=EA=B4=84=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #5/#6/#12 전화 주문 접수: 판매수량 입력 중 엔터키 → 다음 봉투 종류 수량칸으로 이동 - #7 주문접수 관리: 배달일을 접수일 이전으로 변경 불가(클라이언트 min + 서버 검증) - #9 주문접수 관리: 주문수정저장·영수증출력·주문취소 버튼을 접수품목내역 타이틀 옆으로 이동 - #10 주문접수 관리: 접수리스트 하단에 건수·금액 합계 표시(취소 제외) - #13 전화 주문 접수: 접수표 행·열 간격 축소(한 화면 표시) - #14 주문접수 관리: 포장명세 봉투종류~봉투코드 컬럼 간격 조정 - #16 판매대장: 품목 필터 순서 변경(일반용→재사용→공공용→음식물→폐기물) - #18 재고 현황: 종류~계 사이 간격 축소(표 최대폭 제한) - #20/#21 주문접수 관리: 화면명 '주문접수 관리'로 변경, 조회 기준 배달일→접수일, 기본 조회일=오늘(초기화 시 당일), 접수번호 매일 1번부터 시작 - #23 담당자 구분 라벨 '구·군' → '지자체' --- app/Controllers/Admin/Manager.php | 2 +- app/Controllers/Admin/SalesReport.php | 7 +-- app/Controllers/Bag.php | 33 +++++++++---- app/Views/admin/sales_report/sales_ledger.php | 5 +- app/Views/bag/inventory.php | 3 +- app/Views/bag/order_phone.php | 14 +++++- app/Views/bag/order_phone_manage.php | 47 +++++++++++++------ 7 files changed, 81 insertions(+), 30 deletions(-) diff --git a/app/Controllers/Admin/Manager.php b/app/Controllers/Admin/Manager.php index ff0d32e..f760937 100644 --- a/app/Controllers/Admin/Manager.php +++ b/app/Controllers/Admin/Manager.php @@ -29,7 +29,7 @@ class Manager extends BaseController { return [ 'company' => '제작업체', - 'district' => '구·군', + 'district' => '지자체', 'agency' => '대행소', ]; } diff --git a/app/Controllers/Admin/SalesReport.php b/app/Controllers/Admin/SalesReport.php index 41f5794..c7715f5 100644 --- a/app/Controllers/Admin/SalesReport.php +++ b/app/Controllers/Admin/SalesReport.php @@ -91,13 +91,14 @@ class SalesReport extends BaseController // (카테고리 필터까지 적용된 현재 결과 기준) $sizeCatLabels = [ 'general' => '일반용 봉투', - 'food' => '음식물 봉투', - 'waste' => '폐기물 봉투', 'reuse' => '재사용 봉투', 'public_use' => '공공용 봉투', + 'food' => '음식물 봉투', + 'waste' => '폐기물 봉투', ]; // 스티커·공동주택용(apt)·용기(container)는 판매대장 크기 필터에서 제외 - $sizeCatOrder = ['general', 'food', 'waste', 'reuse', 'public_use']; + // 순서: 봉투(일반용·재사용·공공용) → 음식물 → 폐기물 (피드백 #16) + $sizeCatOrder = ['general', 'reuse', 'public_use', 'food', 'waste']; $sizeByCat = []; // catKey => [size => true] foreach ($filtered as $row) { diff --git a/app/Controllers/Bag.php b/app/Controllers/Bag.php index 9718909..d2f8a18 100644 --- a/app/Controllers/Bag.php +++ b/app/Controllers/Bag.php @@ -7048,9 +7048,13 @@ SQL; $unitMap[$code] = $unit; } + // 접수번호 — 매일 1번부터 시작(당일 접수 건수 + 1) $receiptNo = 1; if ($lgIdx) { - $receiptNo = (int) model(ShopOrderModel::class)->where('so_lg_idx', $lgIdx)->countAllResults() + 1; + $receiptNo = (int) model(ShopOrderModel::class) + ->where('so_lg_idx', $lgIdx) + ->where('so_order_date', date('Y-m-d')) + ->countAllResults() + 1; } return $this->render('전화 주문 접수', 'bag/order_phone', compact('shops', 'bagCodes', 'priceMap', 'unitMap', 'receiptNo')); @@ -7067,10 +7071,16 @@ SQL; return redirect()->to(site_url('bag/sales'))->with('error', '지자체를 선택해 주세요.'); } - // 일자별·기간별 조회 (배달일 so_delivery_date 기준) - $startDate = trim((string) ($this->request->getGet('start_date') ?? '')); - $endDate = trim((string) ($this->request->getGet('end_date') ?? '')); - $isYmd = static fn (string $d): bool => preg_match('/^\d{4}-\d{2}-\d{2}$/', $d) === 1; + // 일자별·기간별 조회 (접수일 so_order_date 기준) — 파라미터 없이 진입하면 기본값 = 오늘 + $startRaw = $this->request->getGet('start_date'); + $endRaw = $this->request->getGet('end_date'); + $startDate = trim((string) ($startRaw ?? '')); + $endDate = trim((string) ($endRaw ?? '')); + if ($startRaw === null && $endRaw === null) { + $startDate = date('Y-m-d'); + $endDate = date('Y-m-d'); + } + $isYmd = static fn (string $d): bool => preg_match('/^\d{4}-\d{2}-\d{2}$/', $d) === 1; if ($isYmd($startDate) && $isYmd($endDate) && $startDate > $endDate) { [$startDate, $endDate] = [$endDate, $startDate]; } @@ -7082,10 +7092,10 @@ SQL; $builder->where('so_channel', 'phone'); } if ($isYmd($startDate)) { - $builder->where('so_delivery_date >=', $startDate); + $builder->where('so_order_date >=', $startDate); } if ($isYmd($endDate)) { - $builder->where('so_delivery_date <=', $endDate . ' 23:59:59'); + $builder->where('so_order_date <=', $endDate); } $orders = $builder->orderBy('so_idx', 'DESC')->limit(200)->findAll(); @@ -7181,7 +7191,7 @@ SQL; ]; } - return $this->render('전화접수 관리', 'bag/order_phone_manage', [ + return $this->render('주문접수 관리', 'bag/order_phone_manage', [ 'orders' => $payload, 'startDate' => $isYmd($startDate) ? $startDate : '', 'endDate' => $isYmd($endDate) ? $endDate : '', @@ -7343,8 +7353,15 @@ SQL; 'so_total_amount' => $sumAmt, ]; // 배달일 수정 — 입력값이 유효한 날짜면 변경 반영(수정일자로 변경 처리) + // 단, 접수일 이전 날짜로는 변경 불가. $deliveryDate = trim((string) ($this->request->getPost('so_delivery_date') ?? '')); if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $deliveryDate) === 1) { + $orderDate = substr((string) ($order->so_order_date ?? ''), 0, 10); + if ($orderDate !== '' && $deliveryDate < $orderDate) { + $db->transRollback(); + + return redirect()->back()->with('error', '배달일은 접수일(' . $orderDate . ') 이후로만 변경할 수 있습니다.'); + } $orderUpdate['so_delivery_date'] = $deliveryDate; } $orderModel->update($soIdx, $orderUpdate); diff --git a/app/Views/admin/sales_report/sales_ledger.php b/app/Views/admin/sales_report/sales_ledger.php index 8a4054c..8a77e5c 100644 --- a/app/Views/admin/sales_report/sales_ledger.php +++ b/app/Views/admin/sales_report/sales_ledger.php @@ -19,12 +19,13 @@ $printTitle = ($mode ?? 'daily') === 'daily' ? '[지정판매소] 일자별 판 $printDate = date('Y-m-d'); $printExtraLines = $printSubtitleLines ?? []; // 스티커·공동주택용(apt)·용기(container)는 품목 필터에서 제외 -$catKeys = ['general', 'food', 'reuse', 'public_use', 'waste']; +// 순서: 봉투(일반용·재사용·공공용) → 음식물 → 폐기물 (피드백 #16) +$catKeys = ['general', 'reuse', 'public_use', 'food', 'waste']; $catLabels = [ 'general' => '일반용 봉투', - 'food' => '음식물 봉투', 'reuse' => '재사용 봉투', 'public_use' => '공공용 봉투', + 'food' => '음식물 봉투', 'waste' => '폐기물 봉투', ]; diff --git a/app/Views/bag/inventory.php b/app/Views/bag/inventory.php index 8eee9cd..1f31689 100644 --- a/app/Views/bag/inventory.php +++ b/app/Views/bag/inventory.php @@ -48,7 +48,8 @@ foreach ($subtotals as $subtotal) {
- + +
diff --git a/app/Views/bag/order_phone.php b/app/Views/bag/order_phone.php index 3ae8be2..6293037 100644 --- a/app/Views/bag/order_phone.php +++ b/app/Views/bag/order_phone.php @@ -146,8 +146,11 @@ unset($g); table.phone-batch-table thead tr:last-child th { border-bottom: 1px solid #e5e7eb; } /* 수량/금액 등 하위 헤더 */ table.phone-batch-table thead tr:first-child th[rowspan] { border-bottom: 1px solid #e5e7eb; } /* 구분·봉투종류·단가 */ table.phone-batch-table tbody td { border-bottom: 1px solid #f1f3f5; } + /* 행·열 간격 축소 — 한 화면에 전체 품목이 보이도록 (피드백 #13) */ + table.phone-batch-table th, table.phone-batch-table td { padding: 0.2rem 0.35rem; } + table.phone-batch-table .item-qty-input { padding-top: 0.1rem; padding-bottom: 0.1rem; } -
+
품 목 구 분
@@ -406,6 +409,15 @@ unset($g); } }); + // 판매수량 입력 중 엔터키 → 다음 봉투 종류의 수량 입력칸으로 이동 + orderRows?.addEventListener('keydown', function (e) { + if (e.key !== 'Enter' || !e.target.classList.contains('item-qty-input')) return; + e.preventDefault(); + const inputs = Array.from(orderRows.querySelectorAll('.item-qty-input')); + const next = inputs[inputs.indexOf(e.target) + 1]; + if (next) { next.focus(); next.select(); } + }); + form?.addEventListener('submit', function (e) { let hasItem = false; document.querySelectorAll('.order-row').forEach((row) => { diff --git a/app/Views/bag/order_phone_manage.php b/app/Views/bag/order_phone_manage.php index 6fd04e7..6d9a71d 100644 --- a/app/Views/bag/order_phone_manage.php +++ b/app/Views/bag/order_phone_manage.php @@ -1,10 +1,10 @@
- 전화 주문 접수 관리 + 주문접수 관리
- + ~ @@ -37,6 +37,10 @@
+
+ 건수 0 + 금액 0 +
@@ -63,7 +67,14 @@
-
접수 품목 내역
+
+ 접수 품목 내역 +
+ + + +
+
@@ -104,10 +115,10 @@ - + - + @@ -116,16 +127,11 @@
No봉투종류봉투종류 봉투코드 수량포장포장
- -
- - -
-
+ + -
@@ -174,6 +180,9 @@ var v = order && order.so_delivery_date ? String(order.so_delivery_date) : ''; dd.value = /^\d{4}-\d{2}-\d{2}/.test(v) ? v.slice(0, 10) : ''; dd.disabled = !order || order.so_status === 'cancelled'; + // 배달일은 접수일 이후로만 선택 가능 + var od = order && order.so_order_date ? String(order.so_order_date).slice(0, 10) : ''; + if (/^\d{4}-\d{2}-\d{2}$/.test(od)) { dd.min = od; } else { dd.removeAttribute('min'); } } } @@ -300,8 +309,11 @@ if (!listScrollEl || !detailCardEl) return; const listCard = listScrollEl.closest('section'); const header = listCard ? listCard.firstElementChild : null; + const summary = document.getElementById('list-summary'); listScrollEl.style.height = '0px'; // 리스트를 접어 상세 카드의 본래 높이를 측정 - const target = detailCardEl.offsetHeight - (header ? header.offsetHeight : 0); + const target = detailCardEl.offsetHeight + - (header ? header.offsetHeight : 0) + - (summary ? summary.offsetHeight : 0); listScrollEl.style.height = Math.max(160, target) + 'px'; } window.addEventListener('resize', syncListHeight); @@ -326,7 +338,7 @@ + '' + packEsc(code) + ' ' + packEsc(name) + '' + '-' + '' + nf(qty) + '' - + '' + unit + ''); + + '' + unit + ''); }; items.forEach((it) => { const code = it.soi_bag_code || ''; @@ -389,6 +401,13 @@ : '전화 주문 데이터가 없습니다.'; document.querySelectorAll('.sort-th .sort-ind').forEach((s) => { s.textContent = ''; }); if (th) th.querySelector('.sort-ind').textContent = sortDir === 'asc' ? ' ▲' : ' ▼'; + + // 하단 합계 — 취소 주문은 제외 + const normals = arr.filter((o) => o.so_status !== 'cancelled'); + const sumCountEl = document.getElementById('list-sum-count'); + const sumAmountEl = document.getElementById('list-sum-amount'); + if (sumCountEl) sumCountEl.textContent = nf(normals.length); + if (sumAmountEl) sumAmountEl.textContent = nf(normals.reduce((s, o) => s + (Number(o.so_total_amount) || 0), 0)); } document.querySelectorAll('.sort-th').forEach((th) => {