feat: 전화접수 관리·주문 개선 + 매뉴얼 화면 바로가기

- 전화접수 관리: 배달일 기준 조회, 컬럼 보강(구분/포장 O·X/결제/수령/입금/총금액/취소),
  헤더 정렬, 배달일 수정 저장, 포장 명세·포장량 수정, 입금자 영수증 출력
- 전화 주문 접수: 행추가 → 일괄표(카테고리 그룹·포장 헤더)
- 매뉴얼 소제목에 '화면 열기' 바로가기(실제 GET 라우트만 연결)
- shop_order_item.soi_packed_qty 컬럼 추가(SQL)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
taekyoungc
2026-06-25 14:31:21 +09:00
parent 8e767ff234
commit acce968f9c
8 changed files with 435 additions and 143 deletions

View File

@@ -3614,11 +3614,36 @@ SQL);
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound('매뉴얼 문서를 찾을 수 없습니다.');
}
// 이 매뉴얼 페이지가 설명하는 화면 → 바로가기 링크(screenHelp 역방향).
// screenHelp 에는 "현재경로 매칭"용 비(非)라우트 prefix(bag/sale, bag/order 등)도 섞여 있어
// 그대로 쓰면 404가 난다. 같은 소제목(hint)의 후보 중 "실제 GET 라우트"인 첫 경로만 채택.
$routeCollection = service('routes');
$routeCollection->loadRoutes();
$validGet = $routeCollection->getRoutes('GET'); // [경로 => 핸들러] (정적 경로는 키가 곧 경로)
$screenHelp = config(\Config\Manual::class)->screenHelp ?? [];
$jumpByHint = [];
foreach ($screenHelp as $path => $val) {
[$pSlug, $hint] = array_pad(explode('#', (string) $val, 2), 2, null);
if ($pSlug !== $slug || $hint === null || trim($hint) === '') {
continue;
}
if (isset($jumpByHint[$hint]) || ! isset($validGet[(string) $path])) {
continue; // 이미 채택했거나, 실제 라우트가 아니면 건너뜀
}
$jumpByHint[$hint] = (string) $path;
}
$jumps = [];
foreach ($jumpByHint as $hint => $path) {
$jumps[] = ['hint' => $hint, 'url' => base_url($path)];
}
return $this->render('사용자 매뉴얼 · ' . $page['title'], 'bag/manual', [
'pages' => $renderer->pages(),
'current' => $slug,
'title' => $page['title'],
'body' => $body,
'jumps' => $jumps,
]);
}
@@ -7022,7 +7047,7 @@ SQL;
return redirect()->to(site_url('bag/sales'))->with('error', '지자체를 선택해 주세요.');
}
// 일자별·기간별 조회 (접수일 so_order_date 기준)
// 일자별·기간별 조회 (배달일 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;
@@ -7037,10 +7062,10 @@ SQL;
$builder->where('so_channel', 'phone');
}
if ($isYmd($startDate)) {
$builder->where('so_order_date >=', $startDate);
$builder->where('so_delivery_date >=', $startDate);
}
if ($isYmd($endDate)) {
$builder->where('so_order_date <=', $endDate . ' 23:59:59');
$builder->where('so_delivery_date <=', $endDate . ' 23:59:59');
}
$orders = $builder->orderBy('so_idx', 'DESC')->limit(200)->findAll();
@@ -7100,6 +7125,7 @@ SQL;
'soi_bag_name' => (string) ($item->soi_bag_name ?? ''),
'soi_unit_price' => (int) ($item->soi_unit_price ?? 0),
'soi_qty' => (int) ($item->soi_qty ?? 0),
'soi_packed_qty' => (int) ($item->soi_packed_qty ?? 0),
'soi_amount' => (int) ($item->soi_amount ?? 0),
'soi_box_count' => (int) ($item->soi_box_count ?? 0),
'soi_pack_count' => (int) ($item->soi_pack_count ?? 0),
@@ -7124,7 +7150,10 @@ SQL;
'ds_addr' => $shop['addr'],
'so_order_date' => (string) ($order->so_order_date ?? ''),
'so_delivery_date' => (string) ($order->so_delivery_date ?? ''),
'so_channel' => (string) ($order->so_channel ?? ''),
'so_payment_type' => (string) ($order->so_payment_type ?? ''),
'so_received' => (int) ($order->so_received ?? 0),
'so_paid' => (int) ($order->so_paid ?? 0),
'so_total_qty' => (int) ($order->so_total_qty ?? 0),
'so_total_amount' => (int) ($order->so_total_amount ?? 0),
'so_status' => (string) ($order->so_status ?? 'normal'),
@@ -7139,6 +7168,47 @@ SQL;
]);
}
/**
* 입금자 영수증 출력용 — 단일 접수건의 인쇄용 영수증(독립 HTML, 자동 인쇄).
*/
public function phoneOrderReceipt(int $id): string|RedirectResponse
{
helper('admin');
$lgIdx = $this->lgIdx();
if (! $lgIdx) {
return redirect()->to(site_url('bag/order/phone/manage'))->with('error', '지자체를 선택해 주세요.');
}
$order = model(ShopOrderModel::class)->where('so_idx', $id)->where('so_lg_idx', $lgIdx)->first();
if (! $order) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
$items = model(ShopOrderItemModel::class)
->where('soi_so_idx', $id)
->orderBy('soi_idx', 'ASC')
->findAll();
$shop = null;
$dsIdx = (int) ($order->so_ds_idx ?? 0);
if ($dsIdx > 0) {
$shop = \Config\Database::connect()->table('designated_shop')
->select('ds_shop_no, ds_name, ds_rep_name, ds_tel, ds_addr, ds_addr_detail')
->where('ds_idx', $dsIdx)
->get()->getRowArray();
}
$lgRow = model(\App\Models\LocalGovernmentModel::class)->find($lgIdx);
$lgName = $lgRow ? (string) $lgRow->lg_name : '';
return view('bag/order_phone_receipt', [
'order' => $order,
'items' => $items,
'shop' => $shop,
'lgName' => $lgName,
]);
}
/**
* 전화 주문 상세 품목 수량 수정 저장.
*/
@@ -7174,6 +7244,10 @@ SQL;
if (! is_array($qtyInput)) {
$qtyInput = [];
}
$packedInput = $this->request->getPost('item_packed') ?? [];
if (! is_array($packedInput)) {
$packedInput = [];
}
$codeSet = [];
foreach ($items as $item) {
@@ -7230,8 +7304,10 @@ SQL;
$sheetCount = $qty % $packSheets;
}
$packedQty = isset($packedInput[$itemId]) ? max(0, (int) $packedInput[$itemId]) : (int) ($item->soi_packed_qty ?? 0);
$itemModel->update($itemId, [
'soi_qty' => $qty,
'soi_packed_qty' => $packedQty,
'soi_amount' => $amount,
'soi_box_count' => $boxCount,
'soi_pack_count' => $packCount,