feat: 전화접수 관리에 봉투코드 스캔 판매·포장 처리 기능 추가
지정판매소 판매의 스캔 로직(resolveDesignatedSaleBarcode 등)을 재사용해 전화접수 관리 화면에서 바로 봉투코드를 스캔하면: - 접수 리스트에서 선택된 주문의 판매소로 즉시 판매처리(bag_sale 기록, 재고 차감, 팩/박스코드 sold 전환, 전량 판매 시 so_received=1) - 해당 품목의 포장량(soi_packed_qty)에 스캔 수량 누적 반영(수정 불가 표시) - 포장 명세를 실제 스캔한 봉투코드 내역(봉투코드·수량·팩/박스/낱장)으로 표시 - 라우트: POST bag/order/phone/manage/scan → Bag::phoneOrderManageScan (JSON, 즉시 처리) - 잔량 한도는 접수량(soi_qty), 중복/미등록/재고없음 바코드 거부 - CSRF는 쿠키+regenerate 환경이라 응답에 새 토큰을 실어 다음 스캔에 갱신 - phoneOrderManage 페이로드에 주문별 스캔 내역(scans)·포장량 집계 포함
This commit is contained in:
@@ -77,6 +77,7 @@ $routes->get('bag/order/create', 'Bag::orderCreate');
|
||||
$routes->get('bag/order/phone', 'Bag::phoneOrderCreate');
|
||||
$routes->get('bag/order/phone/manage', 'Bag::phoneOrderManage');
|
||||
$routes->post('bag/order/phone/manage/update', 'Bag::phoneOrderUpdate');
|
||||
$routes->post('bag/order/phone/manage/scan', 'Bag::phoneOrderManageScan');
|
||||
$routes->post('bag/order/phone/manage/cancel/(:num)', 'Bag::phoneOrderCancel/$1');
|
||||
$routes->get('bag/order/phone/receipt/(:num)', 'Bag::phoneOrderReceipt/$1');
|
||||
$routes->get('bag/order/change', 'Bag::orderChange');
|
||||
|
||||
@@ -7144,18 +7144,46 @@ SQL;
|
||||
}
|
||||
}
|
||||
|
||||
// 스캔(=포장·판매)된 봉투코드 내역 — 포장 명세 표시 + 품목별 포장량(soi_packed_qty) 집계에 사용
|
||||
$scansByOrder = [];
|
||||
$packedByOrderBag = [];
|
||||
if ($orderIds !== [] && $db->tableExists('bag_sale_scan_code')) {
|
||||
$scanRows = $db->table('bag_sale_scan_code')
|
||||
->select('bssc_so_idx, bssc_bag_code, bssc_bag_name, bssc_code, bssc_unit, bssc_qty')
|
||||
->where('bssc_lg_idx', $lgIdx)
|
||||
->whereIn('bssc_so_idx', $orderIds)
|
||||
->where('bssc_state', 'sold')
|
||||
->orderBy('bssc_idx', 'ASC')
|
||||
->get()->getResultArray();
|
||||
foreach ($scanRows as $s) {
|
||||
$oid = (int) ($s['bssc_so_idx'] ?? 0);
|
||||
$code = (string) ($s['bssc_bag_code'] ?? '');
|
||||
$qty = (int) ($s['bssc_qty'] ?? 0);
|
||||
$scansByOrder[$oid][] = [
|
||||
'bag_code' => $code,
|
||||
'bag_name' => (string) ($s['bssc_bag_name'] ?? ''),
|
||||
'code' => (string) ($s['bssc_code'] ?? ''),
|
||||
'unit' => (string) ($s['bssc_unit'] ?? ''),
|
||||
'qty' => $qty,
|
||||
];
|
||||
$packedByOrderBag[$oid][$code] = ($packedByOrderBag[$oid][$code] ?? 0) + $qty;
|
||||
}
|
||||
}
|
||||
|
||||
$itemsByOrder = [];
|
||||
foreach ($itemRows as $item) {
|
||||
$orderId = (int) ($item->soi_so_idx ?? 0);
|
||||
$code = (string) ($item->soi_bag_code ?? '');
|
||||
$pack = $unitMap[$code] ?? ['boxSheets' => 0, 'packSheets' => 0];
|
||||
// 포장량 = 스캔 집계 우선(포장 명세와 일치), 없으면 DB 저장값
|
||||
$packedQty = $packedByOrderBag[$orderId][$code] ?? (int) ($item->soi_packed_qty ?? 0);
|
||||
$itemsByOrder[$orderId][] = [
|
||||
'soi_idx' => (int) ($item->soi_idx ?? 0),
|
||||
'soi_bag_code' => $code,
|
||||
'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_packed_qty' => (int) $packedQty,
|
||||
'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),
|
||||
@@ -7188,6 +7216,7 @@ SQL;
|
||||
'so_total_amount' => (int) ($order->so_total_amount ?? 0),
|
||||
'so_status' => (string) ($order->so_status ?? 'normal'),
|
||||
'items' => $itemsByOrder[$id] ?? [],
|
||||
'scans' => $scansByOrder[$id] ?? [],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -7371,6 +7400,154 @@ SQL;
|
||||
return redirect()->to(site_url('bag/order/phone/manage'))->with('success', '주문 수정 저장이 완료되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 전화접수 관리 — 봉투코드 스캔으로 즉시 판매·포장 처리(JSON 응답).
|
||||
* 선택된 주문의 판매소로 판매 기록(bag_sale) + 재고 차감 + 팩코드 sold 전환 +
|
||||
* 해당 품목의 포장량(soi_packed_qty) 반영. 잔량 한도는 접수량(soi_qty).
|
||||
*/
|
||||
public function phoneOrderManageScan()
|
||||
{
|
||||
helper('admin');
|
||||
$lgIdx = $this->lgIdx();
|
||||
if (! $lgIdx) {
|
||||
return $this->response->setJSON(['ok' => false, 'message' => '지자체를 선택해 주세요.']);
|
||||
}
|
||||
|
||||
$soIdx = (int) ($this->request->getPost('so_idx') ?? 0);
|
||||
$barcode = trim((string) ($this->request->getPost('barcode') ?? ''));
|
||||
if ($soIdx <= 0 || $barcode === '') {
|
||||
return $this->response->setJSON(['ok' => false, 'message' => '주문 또는 바코드 값이 올바르지 않습니다.', 'csrf' => csrf_hash()]);
|
||||
}
|
||||
|
||||
$orderModel = model(ShopOrderModel::class);
|
||||
$order = $orderModel->where('so_idx', $soIdx)->where('so_lg_idx', $lgIdx)->first();
|
||||
if (! $order || (string) ($order->so_status ?? '') !== 'normal') {
|
||||
return $this->response->setJSON(['ok' => false, 'message' => '선택한 주문을 사용할 수 없습니다.', 'csrf' => csrf_hash()]);
|
||||
}
|
||||
|
||||
$scan = $this->resolveDesignatedSaleBarcode($lgIdx, $barcode);
|
||||
if (! ($scan['ok'] ?? false)) {
|
||||
return $this->response->setJSON(array_merge($scan, ['csrf' => csrf_hash()]));
|
||||
}
|
||||
|
||||
$bagCode = (string) ($scan['bag_code'] ?? '');
|
||||
$orderItem = model(ShopOrderItemModel::class)
|
||||
->where('soi_so_idx', $soIdx)
|
||||
->where('soi_bag_code', $bagCode)
|
||||
->first();
|
||||
if (! $orderItem) {
|
||||
return $this->response->setJSON(['ok' => false, 'message' => '선택 주문에 없는 봉투 종류입니다.', 'csrf' => csrf_hash()]);
|
||||
}
|
||||
|
||||
// 이미 판매(=포장)된 순수량
|
||||
$soldRow = model(BagSaleModel::class)
|
||||
->select('COALESCE(SUM(bs_qty),0) AS sold_qty')
|
||||
->where('bs_lg_idx', $lgIdx)
|
||||
->where('bs_so_idx', $soIdx)
|
||||
->where('bs_bag_code', $bagCode)
|
||||
->whereIn('bs_type', ['sale', 'cancel', 'return', 'return_cancel'])
|
||||
->first();
|
||||
$soldQty = (int) ($soldRow->sold_qty ?? 0);
|
||||
$receiptQty = (int) ($orderItem->soi_qty ?? 0);
|
||||
$scanQty = (int) ($scan['qty'] ?? 0);
|
||||
$remain = max(0, $receiptQty - $soldQty);
|
||||
if ($scanQty <= 0) {
|
||||
return $this->response->setJSON(['ok' => false, 'message' => '수량 계산에 실패했습니다.', 'csrf' => csrf_hash()]);
|
||||
}
|
||||
if ($scanQty > $remain) {
|
||||
return $this->response->setJSON(['ok' => false, 'message' => '접수 잔량을 초과합니다. (잔량: ' . number_format($remain) . ')', 'csrf' => csrf_hash()]);
|
||||
}
|
||||
|
||||
$dsIdx = (int) ($order->so_ds_idx ?? 0);
|
||||
$bagName = (string) (($scan['bag_name'] ?? '') !== '' ? $scan['bag_name'] : ($orderItem->soi_bag_name ?? ''));
|
||||
$unit = (string) ($scan['unit'] ?? '');
|
||||
$packIds = is_array($scan['pack_ids'] ?? null) ? $scan['pack_ids'] : [];
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
$this->ensureDesignatedSaleScanCodeTable($lgIdx);
|
||||
$db = \Config\Database::connect();
|
||||
$db->transStart();
|
||||
|
||||
// 스캔 코드 기록(sold)
|
||||
$scanCodeRow = $db->table('bag_sale_scan_code')
|
||||
->where('bssc_lg_idx', $lgIdx)->where('bssc_code', $barcode)->get()->getRowArray();
|
||||
if (is_array($scanCodeRow)) {
|
||||
$db->table('bag_sale_scan_code')->where('bssc_idx', (int) ($scanCodeRow['bssc_idx'] ?? 0))->update([
|
||||
'bssc_so_idx' => $soIdx, 'bssc_ds_idx' => $dsIdx, 'bssc_bag_code' => $bagCode,
|
||||
'bssc_bag_name' => $bagName, 'bssc_unit' => $unit, 'bssc_qty' => $scanQty,
|
||||
'bssc_state' => 'sold', 'bssc_regdate' => $now,
|
||||
]);
|
||||
} else {
|
||||
$db->table('bag_sale_scan_code')->insert([
|
||||
'bssc_lg_idx' => $lgIdx, 'bssc_so_idx' => $soIdx, 'bssc_ds_idx' => $dsIdx,
|
||||
'bssc_bag_code' => $bagCode, 'bssc_bag_name' => $bagName, 'bssc_code' => $barcode,
|
||||
'bssc_unit' => $unit, 'bssc_qty' => $scanQty, 'bssc_state' => 'sold', 'bssc_regdate' => $now,
|
||||
]);
|
||||
}
|
||||
|
||||
// 팩/박스는 수신 팩 상태를 sold로 전환(낱장은 유지)
|
||||
if ($packIds !== [] && $unit !== '낱장') {
|
||||
$db->table('bag_receiving_pack_code')
|
||||
->where('brpc_lg_idx', $lgIdx)
|
||||
->whereIn('brpc_idx', array_map(static fn ($v): int => (int) $v, $packIds))
|
||||
->set(['brpc_state' => 'sold'])->update();
|
||||
}
|
||||
|
||||
// 판매 기록 + 재고 차감
|
||||
$unitPrice = (float) ($orderItem->soi_unit_price ?? 0);
|
||||
model(BagSaleModel::class)->insert([
|
||||
'bs_lg_idx' => $lgIdx, 'bs_so_idx' => $soIdx, 'bs_ds_idx' => $dsIdx,
|
||||
'bs_ds_name' => (string) ($order->so_ds_name ?? ''), 'bs_sale_date' => date('Y-m-d'),
|
||||
'bs_bag_code' => $bagCode, 'bs_bag_name' => $bagName, 'bs_qty' => $scanQty,
|
||||
'bs_unit_price' => $unitPrice, 'bs_amount' => $unitPrice * $scanQty,
|
||||
'bs_type' => 'sale', 'bs_regdate' => $now,
|
||||
]);
|
||||
model(BagInventoryModel::class)->adjustQty($lgIdx, $bagCode, $bagName, -$scanQty);
|
||||
|
||||
// 포장량(soi_packed_qty) 반영
|
||||
$newPacked = $soldQty + $scanQty;
|
||||
model(ShopOrderItemModel::class)->update((int) ($orderItem->soi_idx ?? 0), ['soi_packed_qty' => $newPacked]);
|
||||
|
||||
// 전량 판매 시 so_received=1
|
||||
$soldByBag = [];
|
||||
$sumRows = model(BagSaleModel::class)
|
||||
->select('bs_bag_code, COALESCE(SUM(bs_qty),0) AS sold_qty')
|
||||
->where('bs_lg_idx', $lgIdx)->where('bs_so_idx', $soIdx)->where('bs_type', 'sale')
|
||||
->groupBy('bs_bag_code')->findAll();
|
||||
foreach ($sumRows as $r) {
|
||||
$soldByBag[(string) ($r->bs_bag_code ?? '')] = (int) ($r->sold_qty ?? 0);
|
||||
}
|
||||
$allItems = model(ShopOrderItemModel::class)->where('soi_so_idx', $soIdx)->findAll();
|
||||
$allSold = true;
|
||||
foreach ($allItems as $it) {
|
||||
if ((int) ($soldByBag[(string) ($it->soi_bag_code ?? '')] ?? 0) < (int) ($it->soi_qty ?? 0)) {
|
||||
$allSold = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$orderModel->update($soIdx, ['so_received' => $allSold ? 1 : 0]);
|
||||
|
||||
$db->transComplete();
|
||||
if ($db->transStatus() === false) {
|
||||
return $this->response->setJSON(['ok' => false, 'message' => '판매 처리 중 오류가 발생했습니다.', 'csrf' => csrf_hash()]);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => true,
|
||||
'csrf' => csrf_hash(),
|
||||
'so_idx' => $soIdx,
|
||||
'soi_idx' => (int) ($orderItem->soi_idx ?? 0),
|
||||
'bag_code' => $bagCode,
|
||||
'bag_name' => $bagName,
|
||||
'code' => $barcode,
|
||||
'unit' => $unit,
|
||||
'qty' => $scanQty,
|
||||
'packed_qty' => $newPacked,
|
||||
'so_received' => $allSold ? 1 : 0,
|
||||
'remain' => max(0, $receiptQty - $newPacked),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 전화 주문 취소(삭제가 아닌 상태값 변경).
|
||||
*/
|
||||
|
||||
@@ -105,6 +105,15 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 봉투코드 스캔 → 즉시 판매·포장 처리 (선택된 주문의 판매소로 판매) -->
|
||||
<div class="mt-3 border border-gray-300 rounded-lg p-2 bg-gray-50">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="text-sm font-semibold text-gray-700 whitespace-nowrap"><i class="fa-solid fa-barcode text-emerald-600 mr-1"></i>봉투코드 스캔</span>
|
||||
<input type="text" id="scan-input" autocomplete="off" placeholder="봉투 바코드를 스캔/입력 후 Enter" class="border border-gray-300 rounded px-2 py-1 text-sm flex-1 min-w-[12rem]" disabled/>
|
||||
<span id="scan-msg" class="text-xs text-gray-500"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="flex items-center justify-between px-1 mb-1 text-sm">
|
||||
<span class="font-semibold text-gray-700">포장 명세</span>
|
||||
@@ -141,6 +150,12 @@
|
||||
const orders = <?= json_encode($orders ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
const orderMap = new Map(orders.map((o) => [String(o.so_idx), o]));
|
||||
|
||||
// 봉투코드 스캔(판매/포장) — CSRF 토큰(쿠키 방식, 매 요청 갱신)
|
||||
// base_url() 절대경로를 현재 문서(동일 출처) 기준 경로로 변환 → 워크스페이스 iframe/다른 호스트 접근에도 안전
|
||||
const scanApi = new URL('<?= base_url('bag/order/phone/manage/scan') ?>', window.location.href).pathname;
|
||||
const csrfName = '<?= csrf_token() ?>';
|
||||
let csrfHash = '<?= csrf_hash() ?>';
|
||||
|
||||
const listBody = document.getElementById('order-list-body');
|
||||
const form = document.getElementById('order-detail-form');
|
||||
const cancelForm = document.getElementById('order-cancel-form');
|
||||
@@ -212,8 +227,8 @@
|
||||
sheet = qty % packSheets;
|
||||
}
|
||||
|
||||
const packedInput = tr.querySelector('.item-packed-input');
|
||||
const packed = packedInput ? Math.max(0, parseInt(packedInput.value || '0', 10) || 0) : 0;
|
||||
const packedCell = tr.querySelector('.item-packed-cell');
|
||||
const packed = packedCell ? Math.max(0, parseInt((packedCell.textContent || '0').replace(/[^0-9]/g, '') || '0', 10) || 0) : 0;
|
||||
|
||||
const amount = qty * unitPrice;
|
||||
tr.querySelector('.item-amount-cell').textContent = nf(amount);
|
||||
@@ -288,9 +303,7 @@
|
||||
<td class="text-right pr-2">
|
||||
<input type="number" min="0" class="item-qty-input border border-gray-300 rounded px-2 py-1 w-24 text-right" name="item_qty[${itemId}]" value="${qty}" ${isCancelled ? 'disabled' : ''}/>
|
||||
</td>
|
||||
<td class="text-right pr-2">
|
||||
<input type="number" min="0" class="item-packed-input border border-gray-300 rounded px-2 py-1 w-24 text-right" name="item_packed[${itemId}]" value="${packedQty}" ${isCancelled ? 'disabled' : ''}/>
|
||||
</td>
|
||||
<td class="text-right pr-2 item-packed-cell font-semibold ${packedQty >= qty && qty > 0 ? 'text-emerald-600' : 'text-gray-700'}" data-bag-code="${esc(item.soi_bag_code || '')}">${nf(packedQty)}</td>
|
||||
<td class="text-right pr-2 item-amount-cell">${nf(amount)}</td>
|
||||
<td class="text-right pr-2 item-pack-cell">박스=${nf(box)}, 팩=${nf(pack)}, 낱장=${nf(sheet)}</td>
|
||||
</tr>
|
||||
@@ -298,9 +311,90 @@
|
||||
}).join('');
|
||||
|
||||
recalcTotals();
|
||||
|
||||
// 봉투코드 스캔 입력: 정상 주문일 때만 활성화
|
||||
const scanInput = document.getElementById('scan-input');
|
||||
if (scanInput) {
|
||||
scanInput.disabled = isCancelled;
|
||||
const msg = document.getElementById('scan-msg');
|
||||
if (msg) msg.textContent = '';
|
||||
}
|
||||
|
||||
syncListHeight(); // 상세 카드 높이 확정 후 접수 리스트 높이를 맞춘다
|
||||
}
|
||||
|
||||
// 봉투코드 스캔 → 즉시 판매·포장 처리 후 화면 갱신
|
||||
const scanInputEl = document.getElementById('scan-input');
|
||||
const scanMsgEl = document.getElementById('scan-msg');
|
||||
function setScanMsg(text, isErr) {
|
||||
if (!scanMsgEl) return;
|
||||
scanMsgEl.textContent = text || '';
|
||||
scanMsgEl.className = 'text-xs ' + (isErr ? 'text-red-600 font-semibold' : 'text-emerald-600');
|
||||
}
|
||||
async function submitScan() {
|
||||
if (!scanInputEl) return;
|
||||
const code = (scanInputEl.value || '').trim();
|
||||
if (!code) return;
|
||||
if (!selectedId) { setScanMsg('먼저 접수 리스트에서 주문을 선택해 주세요.', true); return; }
|
||||
|
||||
const body = new URLSearchParams();
|
||||
body.set(csrfName, csrfHash);
|
||||
body.set('so_idx', String(selectedId));
|
||||
body.set('barcode', code);
|
||||
|
||||
let data;
|
||||
try {
|
||||
const res = await fetch(scanApi, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||
credentials: 'same-origin',
|
||||
body: body.toString(),
|
||||
});
|
||||
data = await res.json();
|
||||
} catch (e) {
|
||||
setScanMsg('통신 오류가 발생했습니다.', true);
|
||||
return;
|
||||
}
|
||||
if (data && data.csrf) csrfHash = data.csrf; // 다음 스캔용 토큰 갱신
|
||||
if (!data || !data.ok) {
|
||||
setScanMsg((data && data.message) || '스캔 처리 실패', true);
|
||||
scanInputEl.select();
|
||||
return;
|
||||
}
|
||||
|
||||
// 선택된 주문 객체에 스캔 반영(재선택 시 유지)
|
||||
const order = orderMap.get(String(data.so_idx));
|
||||
if (order) {
|
||||
if (!Array.isArray(order.scans)) order.scans = [];
|
||||
order.scans.push({ bag_code: data.bag_code, bag_name: data.bag_name, code: data.code, unit: data.unit, qty: Number(data.qty || 0) });
|
||||
const it = (order.items || []).find((x) => String(x.soi_bag_code) === String(data.bag_code));
|
||||
if (it) it.soi_packed_qty = Number(data.packed_qty || 0);
|
||||
order.so_received = Number(data.so_received || 0);
|
||||
// 현재 이 주문을 보고 있으면 상세·포장명세 즉시 갱신
|
||||
if (String(selectedId) === String(data.so_idx)) {
|
||||
renderPacking(order);
|
||||
const cell = detailBody.querySelector('.item-packed-cell[data-bag-code="' + (window.CSS && CSS.escape ? CSS.escape(String(data.bag_code)) : String(data.bag_code)) + '"]');
|
||||
if (cell) {
|
||||
cell.textContent = nf(Number(data.packed_qty || 0));
|
||||
const row = cell.closest('tr');
|
||||
const qtyInp = row ? row.querySelector('.item-qty-input') : null;
|
||||
const rq = qtyInp ? (parseInt(qtyInp.value || '0', 10) || 0) : 0;
|
||||
cell.classList.toggle('text-emerald-600', Number(data.packed_qty || 0) >= rq && rq > 0);
|
||||
cell.classList.toggle('text-gray-700', !(Number(data.packed_qty || 0) >= rq && rq > 0));
|
||||
}
|
||||
recalcTotals();
|
||||
}
|
||||
// 접수 리스트의 '포장' 표시(O/X) 갱신
|
||||
renderList();
|
||||
}
|
||||
scanInputEl.value = '';
|
||||
scanInputEl.focus();
|
||||
setScanMsg('등록: ' + data.bag_code + ' / ' + data.unit + ' / ' + nf(data.qty) + ' (잔량 ' + nf(data.remain) + ')', false);
|
||||
}
|
||||
scanInputEl?.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.preventDefault(); submitScan(); }
|
||||
});
|
||||
|
||||
// 접수 리스트 스크롤 높이를 지정판매소 정보(상세) 카드 높이에 맞춘다.
|
||||
// offsetHeight는 화면 zoom(텍스트 줄이기)과 무관한 레이아웃 px라, 배율과 상관없이 두 카드가 같은 높이가 되고 하단 공백이 생기지 않는다.
|
||||
const listScrollEl = document.getElementById('list-scroll');
|
||||
@@ -326,42 +420,27 @@
|
||||
const packingBody = document.getElementById('packing-body');
|
||||
const packingTotal = document.getElementById('packing-total');
|
||||
function packEsc(s) { return String(s == null ? '' : s).replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' }[c])); }
|
||||
// 포장 명세 = 실제 스캔(=판매/포장)된 봉투코드 내역. 최근 스캔이 위로 오도록 역순 표시.
|
||||
function renderPacking(order) {
|
||||
if (!packingBody) return;
|
||||
const items = (Array.isArray(order.items) ? order.items.slice() : [])
|
||||
.sort((a, b) => (Number(b.soi_idx) || 0) - (Number(a.soi_idx) || 0)); // 내림차순
|
||||
const rows = [];
|
||||
const scans = Array.isArray(order.scans) ? order.scans.slice().reverse() : [];
|
||||
let no = 0;
|
||||
const addRow = (code, name, qty, unit) => {
|
||||
let totalQty = 0;
|
||||
const rows = scans.map((s) => {
|
||||
no++;
|
||||
rows.push('<tr><td class="text-center">' + no + '</td>'
|
||||
const code = s.bag_code || '';
|
||||
const name = s.bag_name || '';
|
||||
const qty = parseInt(s.qty || 0, 10) || 0;
|
||||
totalQty += qty;
|
||||
return '<tr><td class="text-center">' + no + '</td>'
|
||||
+ '<td class="text-left pl-2">' + packEsc(code) + ' ' + packEsc(name) + '</td>'
|
||||
+ '<td class="text-center text-gray-400">-</td>'
|
||||
+ '<td class="text-center font-mono text-gray-600">' + packEsc(s.code || '') + '</td>'
|
||||
+ '<td class="text-right pr-2">' + nf(qty) + '</td>'
|
||||
+ '<td class="text-left pl-2">' + unit + '</td></tr>');
|
||||
};
|
||||
items.forEach((it) => {
|
||||
const code = it.soi_bag_code || '';
|
||||
const name = it.soi_bag_name || '';
|
||||
let qty = parseInt(it.soi_qty || 0, 10) || 0;
|
||||
const boxSheets = parseInt(it.box_sheets || 0, 10) || 0;
|
||||
const packSheets = parseInt(it.pack_sheets || 0, 10) || 0;
|
||||
let box = 0, pack = 0, sheet = qty;
|
||||
if (boxSheets > 0) {
|
||||
box = Math.floor(qty / boxSheets);
|
||||
const rem = qty % boxSheets;
|
||||
if (packSheets > 0) { pack = Math.floor(rem / packSheets); sheet = rem % packSheets; }
|
||||
else { sheet = rem; }
|
||||
} else if (packSheets > 0) {
|
||||
pack = Math.floor(qty / packSheets); sheet = qty % packSheets;
|
||||
}
|
||||
for (let i = 0; i < box; i++) { addRow(code, name, boxSheets, '박스'); }
|
||||
for (let i = 0; i < pack; i++) { addRow(code, name, packSheets, '팩'); }
|
||||
if (sheet > 0) { addRow(code, name, sheet, '낱장'); }
|
||||
+ '<td class="text-left pl-2">' + packEsc(s.unit || '') + '</td></tr>';
|
||||
});
|
||||
packingBody.innerHTML = rows.length ? rows.join('')
|
||||
: '<tr><td colspan="5" class="text-center py-6 text-gray-400">포장할 품목이 없습니다.</td></tr>';
|
||||
if (packingTotal) packingTotal.textContent = String(no);
|
||||
: '<tr><td colspan="5" class="text-center py-6 text-gray-400">스캔된 포장 내역이 없습니다.</td></tr>';
|
||||
if (packingTotal) packingTotal.textContent = nf(totalQty);
|
||||
}
|
||||
|
||||
// ── 접수 리스트 렌더 + 헤더 클릭 정렬 ──────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user