Files
jongryangje/app/Views/bag/create_bag_order.php
taekyoungc 215d4d2c7c 발주 변경·입고 화면을 사이트 흐름에 맞게 반영한다.
발주 등록/변경 및 스캐너·일괄·입고현황 화면의 라우팅과 화면 구성을 운영과 동일한 최신 형태로 정리한다.

Made-with: Cursor
2026-04-23 15:53:33 +09:00

702 lines
32 KiB
PHP

<?php
$editDefaults = is_array($editDefaults ?? null) ? $editDefaults : [];
$editMode = (bool) ($editMode ?? false);
$oldBagCodes = old('item_bag_code', $editDefaults['item_bag_code'] ?? []);
$oldQtySheets = old('item_qty_sheet', $editDefaults['item_qty_sheet'] ?? []);
$oldQtyBoxes = old('item_qty_box', $editDefaults['item_qty_box'] ?? []);
$oldBagCodes = is_array($oldBagCodes) ? $oldBagCodes : [];
$oldQtySheets = is_array($oldQtySheets) ? $oldQtySheets : [];
$oldQtyBoxes = is_array($oldQtyBoxes) ? $oldQtyBoxes : [];
$itemRowCount = max(8, count($oldBagCodes), count($oldQtySheets), count($oldQtyBoxes));
$defaultOrderDate = old('bo_order_date', $editDefaults['bo_order_date'] ?? date('Y-m-d'));
$defaultOrderMonth = old('bo_order_month_ui', substr($defaultOrderDate, 0, 7));
$defaultOrderYear = (int) substr($defaultOrderMonth, 0, 4);
$monthOptionValues = [];
for ($year = $defaultOrderYear - 2; $year <= $defaultOrderYear + 2; $year++) {
for ($month = 1; $month <= 12; $month++) {
$monthValue = sprintf('%04d-%02d', $year, $month);
$monthLabel = $year . '년 ' . $month . '월';
$monthOptionValues[] = ['value' => $monthValue, 'label' => $monthLabel];
}
}
$bagMeta = [];
foreach (($bagReferenceRows ?? []) as $row) {
$bagMeta[$row['code']] = [
'name' => $row['name'],
'orderPrice' => (float) $row['orderPrice'],
'boxPerPack' => (int) $row['boxPerPack'],
'packPerSheet' => (int) $row['packPerSheet'],
'totalPerBox' => max(1, (int) $row['totalPerBox']),
];
}
$initialSelectedItems = [];
$maxOldCount = max(count($oldBagCodes), count($oldQtySheets), count($oldQtyBoxes));
for ($i = 0; $i < $maxOldCount; $i++) {
$code = trim((string) ($oldBagCodes[$i] ?? ''));
if ($code === '' || !isset($bagMeta[$code])) {
continue;
}
$fallbackQtyBox = (int) ($oldQtyBoxes[$i] ?? 0);
$rawQtySheet = (int) ($oldQtySheets[$i] ?? 0);
$fallbackTotalPerBox = (int) ($bagMeta[$code]['totalPerBox'] ?? 1);
if ($fallbackQtyBox <= 0 && $rawQtySheet > 0) {
$fallbackQtyBox = intdiv($rawQtySheet, max(1, $fallbackTotalPerBox));
}
$initialSelectedItems[] = [
'code' => $code,
'qtyBox' => max(0, $fallbackQtyBox),
];
}
$statusMap = ['normal' => '정상', 'cancelled' => '취소', 'deleted' => '삭제'];
$hubReturn = (bool) ($hubReturn ?? false);
$changeMode = $editMode ? (string) ($changeMode ?? 'meta') : 'meta';
if (! in_array($changeMode, ['price', 'meta', 'delete'], true)) {
$changeMode = 'meta';
}
$orderLotNo = (string) ($orderLotNo ?? '');
$orderReturnMonth = (string) ($orderReturnMonth ?? '');
$deleteMode = $editMode && $changeMode === 'delete';
$defaultDeleteBoIdx = (int) ($editDefaults['bo_source_idx'] ?? 0);
$firstNormalBoIdx = null;
foreach (($recentOrders ?? []) as $_h) {
if ((string) ($_h->bo_status ?? '') === 'normal') {
$firstNormalBoIdx = (int) $_h->bo_idx;
break;
}
}
$defaultDeleteOk = false;
foreach (($recentOrders ?? []) as $_h) {
if ((int) $_h->bo_idx === $defaultDeleteBoIdx && (string) ($_h->bo_status ?? '') === 'normal') {
$defaultDeleteOk = true;
break;
}
}
$selectedDeleteBoIdx = $defaultDeleteOk ? $defaultDeleteBoIdx : (int) ($firstNormalBoIdx ?? 0);
?>
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel flex flex-wrap items-center justify-between gap-2">
<span class="text-sm font-bold text-gray-700"><?= $editMode ? '발주 변경' : '발주 등록' ?></span>
<?php if ($hubReturn && $orderReturnMonth !== ''): ?>
<a href="<?= base_url('bag/order/change?month=' . rawurlencode($orderReturnMonth) . '&hub_mode=' . rawurlencode($changeMode)) ?>" class="text-sm text-blue-600 hover:underline">발주 변경 목록</a>
<?php endif; ?>
</section>
<?php if ($deleteMode): ?>
<form action="<?= base_url('bag/order/delete') ?>" method="post" class="mt-2 space-y-2">
<?= csrf_field() ?>
<div class="border border-gray-300 bg-white p-2 text-sm">
<span class="font-bold text-gray-700">발주월</span>
<span class="ml-2"><?= esc($defaultOrderMonth) ?></span>
<span class="text-gray-600 ml-2">왼쪽 목록에서 삭제할 발주를 선택한 뒤 「삭제 실행」을 누르세요.</span>
</div>
<div class="grid grid-cols-1 xl:grid-cols-12 gap-2">
<section class="xl:col-span-5 border border-gray-300 bg-white">
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">발주 이력</div>
<div class="overflow-auto max-h-[410px]">
<table class="w-full data-table text-sm">
<thead>
<tr>
<th class="w-10">선택</th>
<th class="w-28">발주일</th>
<th>제작업체</th>
<th>입고처</th>
<th class="w-16">상태</th>
</tr>
</thead>
<tbody>
<?php $deleteRadioFirst = true; ?>
<?php foreach (($recentOrders ?? []) as $history): ?>
<?php $rowNormal = (string) ($history->bo_status ?? '') === 'normal'; ?>
<tr class="<?= $rowNormal ? '' : 'opacity-60' ?>">
<td class="text-center align-middle">
<?php if ($rowNormal): ?>
<input type="radio" name="bo_idx" value="<?= (int) $history->bo_idx ?>" class="accent-red-600" <?= (int) $history->bo_idx === $selectedDeleteBoIdx ? 'checked' : '' ?> <?= $deleteRadioFirst ? 'required' : '' ?> />
<?php $deleteRadioFirst = false; ?>
<?php else: ?>
<span class="text-gray-400" title="삭제할 수 없는 상태">—</span>
<?php endif; ?>
</td>
<td class="text-center"><?= esc((string) $history->bo_order_date) ?></td>
<td class="text-left pl-2"><?= esc((string) ($companyMap[(int) $history->bo_company_idx] ?? '-')) ?></td>
<td class="text-left pl-2"><?= esc((string) ($agencyMap[(int) $history->bo_agency_idx] ?? '-')) ?></td>
<td class="text-center"><?= esc((string) ($statusMap[(string) $history->bo_status] ?? $history->bo_status)) ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($recentOrders)): ?>
<tr><td colspan="5" class="text-center text-gray-400 py-4">발주 이력이 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</section>
<section class="xl:col-span-7 border border-red-200 bg-red-50 p-4">
<p class="text-sm font-bold text-red-800 mb-2">발주 삭제</p>
<p class="text-sm text-gray-700 mb-4">목록에서 선택한 발주를 삭제 처리합니다. 계속하시겠습니까?</p>
<div class="flex flex-wrap gap-2 items-center">
<button type="submit" class="bg-red-600 text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90" <?= $firstNormalBoIdx === null ? 'disabled' : '' ?>>삭제 실행</button>
<a href="<?= base_url('bag/order/change?month=' . rawurlencode($orderReturnMonth !== '' ? $orderReturnMonth : substr($defaultOrderDate, 0, 7))) ?>" class="bg-gray-200 text-gray-800 px-6 py-1.5 rounded-sm text-sm">취소</a>
</div>
</section>
</div>
</form>
<?php else: ?>
<form action="<?= base_url('bag/order/store') ?>" method="POST" class="mt-2 space-y-2" id="bag-order-store-form">
<?= csrf_field() ?>
<?php if ($editMode): ?>
<input type="hidden" name="bo_source_idx" value="<?= esc((string) ($editDefaults['bo_source_idx'] ?? 0)) ?>" />
<fieldset class="border border-gray-200 rounded px-3 py-2 mb-1 text-sm bg-white">
<legend class="text-xs font-bold text-gray-600 px-1">변경 구분</legend>
<div class="flex flex-wrap gap-x-4 gap-y-1">
<label class="inline-flex items-center gap-1 cursor-pointer">
<input type="radio" name="bo_change_mode" value="price" <?= $changeMode === 'price' ? 'checked' : '' ?> />
<span>발주·도매·판매 단가</span>
</label>
<label class="inline-flex items-center gap-1 cursor-pointer">
<input type="radio" name="bo_change_mode" value="meta" <?= $changeMode === 'meta' ? 'checked' : '' ?> />
<span>업체·수수료·협회·발주</span>
</label>
</div>
<p class="text-xs text-gray-500 mt-1">
발주 삭제는 <a class="text-blue-600 hover:underline" href="<?= base_url('bag/order/revise/' . (int) ($editDefaults['bo_source_idx'] ?? 0) . '?change_mode=delete') ?>">발주 삭제 화면</a>으로 이동합니다.
</p>
</fieldset>
<?php endif; ?>
<?php if ($hubReturn && $orderReturnMonth !== ''): ?>
<input type="hidden" name="order_return_hub" value="1" />
<input type="hidden" name="order_return_month" value="<?= esc($orderReturnMonth) ?>" />
<?php endif; ?>
<div class="border border-gray-300 bg-white p-2">
<div class="flex flex-wrap items-center gap-4 text-sm">
<div class="flex items-center gap-2">
<label for="bo_order_month_ui" class="font-bold text-gray-700">발주월</label>
<select id="bo_order_month_ui" name="bo_order_month_ui" class="border border-gray-300 rounded px-2 py-1 w-40">
<?php foreach ($monthOptionValues as $monthOption): ?>
<option value="<?= esc($monthOption['value']) ?>" <?= $monthOption['value'] === $defaultOrderMonth ? 'selected' : '' ?>>
<?= esc($monthOption['label']) ?>
</option>
<?php endforeach; ?>
</select>
<span id="bo_order_month_ko" class="text-sm text-gray-700"></span>
</div>
<p class="text-blue-600 font-bold">※ 발주수량을 박스단위로 떨어지게 입력해 주세요.</p>
</div>
</div>
<div class="grid grid-cols-1 xl:grid-cols-12 gap-2">
<section class="xl:col-span-5 border border-gray-300 bg-white">
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">발주 이력</div>
<div class="overflow-auto max-h-[410px]">
<table class="w-full data-table text-sm">
<thead>
<tr>
<th class="w-28">발주일</th>
<th>제작업체</th>
<th>입고처</th>
<th class="w-16">상태</th>
</tr>
</thead>
<tbody>
<?php foreach (($recentOrders ?? []) as $history): ?>
<tr>
<td class="text-center">
<a href="<?= base_url('bag/order/revise/' . (int) $history->bo_idx) ?>" class="text-blue-600 hover:underline">
<?= esc((string) $history->bo_order_date) ?>
</a>
</td>
<td class="text-left pl-2"><?= esc((string) ($companyMap[(int) $history->bo_company_idx] ?? '-')) ?></td>
<td class="text-left pl-2"><?= esc((string) ($agencyMap[(int) $history->bo_agency_idx] ?? '-')) ?></td>
<td class="text-center"><?= esc((string) ($statusMap[(string) $history->bo_status] ?? $history->bo_status)) ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($recentOrders)): ?>
<tr><td colspan="4" class="text-center text-gray-400 py-4">발주 이력이 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</section>
<section class="xl:col-span-7 border border-gray-300 bg-white">
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700"><?= $editMode ? '발주 변경 수정' : '발주 Form' ?></div>
<div class="p-2 space-y-2">
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
<div class="flex items-center gap-2 text-sm">
<label for="bo_order_date" class="w-20 font-bold text-gray-700">발주일 <span class="text-red-500">*</span></label>
<input id="bo_order_date" name="bo_order_date" type="date" value="<?= esc($defaultOrderDate) ?>" required class="border border-gray-300 rounded px-2 py-1 w-full" />
</div>
<div class="flex items-center gap-2 text-sm">
<label for="bo_association_idx" class="w-20 font-bold text-gray-700">협회</label>
<select id="bo_association_idx" name="bo_association_idx" class="border border-gray-300 rounded px-2 py-1 w-full">
<option value="">선택</option>
<?php foreach (($associations ?? []) as $association): ?>
<option value="<?= esc((string) $association->cp_idx) ?>" <?= (int) old('bo_association_idx', $editDefaults['bo_association_idx'] ?? 0) === (int) $association->cp_idx ? 'selected' : '' ?>>
<?= esc((string) $association->cp_name) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="flex items-center gap-2 text-sm">
<label for="bo_company_idx" class="w-20 font-bold text-gray-700">제작업체</label>
<select id="bo_company_idx" name="bo_company_idx" class="border border-gray-300 rounded px-2 py-1 w-full">
<option value="">선택</option>
<?php foreach (($companies ?? []) as $company): ?>
<option value="<?= esc((string) $company->cp_idx) ?>" <?= (int) old('bo_company_idx', $editDefaults['bo_company_idx'] ?? 0) === (int) $company->cp_idx ? 'selected' : '' ?>>
<?= esc((string) $company->cp_name) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="flex items-center gap-2 text-sm">
<label for="bo_agency_idx" class="w-20 font-bold text-gray-700">입고처</label>
<select id="bo_agency_idx" name="bo_agency_idx" class="border border-gray-300 rounded px-2 py-1 w-full">
<option value="">선택</option>
<?php foreach (($agencies ?? []) as $agency): ?>
<option value="<?= esc((string) $agency->sa_idx) ?>" <?= (int) old('bo_agency_idx', $editDefaults['bo_agency_idx'] ?? 0) === (int) $agency->sa_idx ? 'selected' : '' ?>>
[<?= esc((string) ($agency->sa_kind ?? '')) ?>] <?= esc((string) ($agency->sa_code ?? '')) ?> — <?= esc((string) $agency->sa_name) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="border border-gray-300 overflow-auto">
<table class="w-full data-table text-sm order-input-table" id="order-item-table">
<thead>
<tr>
<th class="w-12">번호</th>
<th class="w-16">선택</th>
<th>품명</th>
<th class="w-24">수량</th>
<th class="w-24">단가</th>
<th class="w-24">낱장환산</th>
<?php if ($editMode): ?>
<th class="w-24">선정수량</th>
<th class="w-20">LOT</th>
<?php endif; ?>
<th class="w-28">금액</th>
</tr>
</thead>
<tbody id="selected-order-items-body"></tbody>
<tfoot>
<tr>
<th colspan="3" class="text-center">계</th>
<th class="text-right pr-2" id="sum-box-qty">0</th>
<th></th>
<th class="text-right pr-2" id="sum-sheet-qty">0</th>
<?php if ($editMode): ?>
<th></th>
<th></th>
<?php endif; ?>
<th class="text-right pr-2" id="sum-amount">0</th>
</tr>
</tfoot>
</table>
</div>
<div class="border border-gray-300 bg-gray-50 p-2 text-sm">
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-1">
<div class="flex items-center justify-between">
<span class="text-gray-700">품목 금액 합계</span>
<strong id="sum-item-amount">0</strong>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-700">조달수수료</span>
<span class="flex items-center gap-1">
<input id="bo_fee_rate_summary" name="bo_fee_rate" type="number" step="0.01" value="<?= esc(old('bo_fee_rate', $editDefaults['bo_fee_rate'] ?? '0')) ?>" class="border border-gray-300 rounded px-2 py-0.5 w-20 text-right" />
<strong>%</strong>
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-700">조달수수료액</span>
<strong id="sum-fee-amount">0</strong>
</div>
<div class="flex items-center justify-between border-t border-red-300 pt-1">
<span class="font-bold text-gray-800">품목 + 수수료 합계</span>
<strong id="sum-grand-amount" class="text-red-700">0</strong>
</div>
</div>
</div>
<div class="flex gap-2 pt-1">
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition"><?= $editMode ? '변경 저장' : '발주' ?></button>
<a href="<?= base_url('bag/purchase-inbound') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
</div>
</div>
</section>
</div>
<section class="border border-gray-300 bg-white">
<div class="border-b border-gray-300 bg-gray-50 px-2 py-1 text-sm font-bold text-gray-700">발주 등록 종류</div>
<div class="overflow-auto">
<table class="w-full data-table text-sm order-reference-table">
<thead>
<tr>
<th class="w-12">번호</th>
<th class="w-20">선택</th>
<th>봉투 종류</th>
<th class="w-24">발주단가</th>
<th class="w-24">Box당 팩</th>
<th class="w-24">팩당 낱장</th>
<th class="w-28">1박스 총 낱장</th>
</tr>
</thead>
<tbody>
<?php foreach (($bagReferenceRows ?? []) as $idx => $row): ?>
<tr data-reference-row data-code="<?= esc((string) $row['code']) ?>" class="cursor-pointer">
<td class="text-center"><?= $idx + 1 ?></td>
<td class="text-center">
<button type="button" class="js-toggle-bag border border-gray-300 rounded px-2 py-0.5 text-xs hover:bg-gray-100" data-code="<?= esc((string) $row['code']) ?>">선택</button>
</td>
<td class="text-left pl-2"><?= esc((string) $row['name']) ?></td>
<td class="text-right pr-2"><?= number_format((float) $row['orderPrice'], 2) ?></td>
<td class="text-right pr-2"><?= number_format((int) $row['boxPerPack']) ?></td>
<td class="text-right pr-2"><?= number_format((int) $row['packPerSheet']) ?></td>
<td class="text-right pr-2"><?= number_format((int) $row['totalPerBox']) ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($bagReferenceRows)): ?>
<tr><td colspan="6" class="text-center text-gray-400 py-4">표시할 봉투 기준 데이터가 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</section>
</form>
<?php endif; ?>
<?php if (! $deleteMode): ?>
<style>
.order-input-table tbody tr,
.order-reference-table tbody tr {
height: 34px;
}
.order-input-table tbody td,
.order-reference-table tbody td {
padding-top: 4px;
padding-bottom: 4px;
}
</style>
<script>
(() => {
const bagMeta = <?= json_encode($bagMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const initialSelectedItems = <?= json_encode($initialSelectedItems, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const selectedBody = document.getElementById('selected-order-items-body');
const referenceRows = Array.from(document.querySelectorAll('[data-reference-row]'));
const sumBoxQtyEl = document.getElementById('sum-box-qty');
const sumSheetQtyEl = document.getElementById('sum-sheet-qty');
const sumAmountEl = document.getElementById('sum-amount');
const sumItemAmountEl = document.getElementById('sum-item-amount');
const sumFeeAmountEl = document.getElementById('sum-fee-amount');
const sumGrandAmountEl = document.getElementById('sum-grand-amount');
const monthInput = document.getElementById('bo_order_month_ui');
const monthKoLabel = document.getElementById('bo_order_month_ko');
const orderDateInput = document.getElementById('bo_order_date');
const feeRateSummaryInput = document.getElementById('bo_fee_rate_summary');
const orderForm = document.querySelector('form[action*="bag/order/store"]');
const selectedItems = new Map();
let activeCode = null;
const editMode = <?= $editMode ? 'true' : 'false' ?>;
const changeMode = <?= json_encode($changeMode, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const orderLotNo = <?= json_encode($orderLotNo, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const colEmpty = editMode ? 9 : 7;
const formatNumber = (value) => new Intl.NumberFormat('ko-KR').format(Number.isFinite(value) ? value : 0);
const escapeHtml = (value) => String(value ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
const ensureMonthOption = (monthValue) => {
if (!monthInput || !monthValue) return;
const hasOption = Array.from(monthInput.options || []).some((option) => option.value === monthValue);
if (hasOption) return;
const [year, month] = monthValue.split('-');
const label = `${Number(year)}년 ${Number(month)}월`;
const option = new Option(label, monthValue, false, false);
monthInput.add(option);
};
const syncMonthFromDate = () => {
if (!orderDateInput || !monthInput || !orderDateInput.value) return;
const monthValue = orderDateInput.value.substring(0, 7);
ensureMonthOption(monthValue);
monthInput.value = monthValue;
updateMonthKoLabel();
};
const syncDateFromMonth = () => {
if (!orderDateInput || !monthInput || !monthInput.value) return;
const parts = monthInput.value.split('-');
if (parts.length !== 2) return;
const year = Number(parts[0]);
const month = Number(parts[1]);
if (!Number.isFinite(year) || !Number.isFinite(month)) return;
const currentDay = orderDateInput.value ? Number(orderDateInput.value.split('-')[2]) : 1;
const lastDay = new Date(year, month, 0).getDate();
const day = String(Math.min(Math.max(currentDay, 1), lastDay)).padStart(2, '0');
orderDateInput.value = `${String(year)}-${String(month).padStart(2, '0')}-${day}`;
updateMonthKoLabel();
};
const updateMonthKoLabel = () => {
if (!monthKoLabel || !monthInput || !monthInput.value) {
if (monthKoLabel) monthKoLabel.textContent = '';
return;
}
const [year, month] = monthInput.value.split('-');
if (!year || !month) {
monthKoLabel.textContent = '';
return;
}
monthKoLabel.textContent = `${Number(year)}년 ${Number(month)}월`;
};
const updateReferenceSelectionUi = () => {
referenceRows.forEach((row) => {
const code = row.dataset.code || '';
const button = row.querySelector('.js-toggle-bag');
const isSelected = selectedItems.has(code);
row.classList.toggle('bg-blue-50', isSelected);
if (button) {
button.textContent = isSelected ? '선택됨' : '선택';
button.classList.toggle('bg-blue-600', isSelected);
button.classList.toggle('text-white', isSelected);
button.classList.toggle('border-blue-600', isSelected);
}
});
};
const applyModeLock = () => {
if (!editMode) return;
const isPrice = changeMode === 'price';
selectedBody.querySelectorAll('.item-qty-box').forEach((el) => {
el.readOnly = isPrice;
});
};
const updateTotals = () => {
let sumBoxQty = 0;
let sumSheetQty = 0;
let sumAmount = 0;
selectedBody.querySelectorAll('tr[data-item-row]').forEach((row) => {
const code = row.dataset.code || '';
const qtyInput = row.querySelector('.item-qty-box');
const qtyBox = Math.max(0, parseInt(qtyInput?.value || '0', 10));
const meta = bagMeta[code] || { orderPrice: 0, totalPerBox: 1 };
const priceInput = row.querySelector('.item-unit-price-input');
let unitPrice = Number(meta.orderPrice || 0);
if (priceInput) {
unitPrice = Math.max(0, parseFloat(priceInput.value || '0') || 0);
}
const totalPerBox = Math.max(1, Number(meta.totalPerBox || 1));
const qtySheet = qtyBox * totalPerBox;
const amount = qtySheet * unitPrice;
const unitPriceEl = row.querySelector('.item-unit-price');
const qtySheetEl = row.querySelector('.item-qty-sheet');
const selQtyEl = row.querySelector('.item-sel-qty');
const sheetHelpEl = row.querySelector('.item-sheet-help');
const amountEl = row.querySelector('.item-amount');
if (unitPriceEl) unitPriceEl.textContent = formatNumber(unitPrice);
if (qtySheetEl) qtySheetEl.textContent = formatNumber(qtySheet);
if (selQtyEl) selQtyEl.textContent = formatNumber(qtySheet);
if (sheetHelpEl) sheetHelpEl.textContent = `낱장 ${formatNumber(qtySheet)}장`;
if (amountEl) amountEl.textContent = formatNumber(amount);
selectedItems.set(code, { qtyBox });
sumBoxQty += qtyBox;
sumSheetQty += qtySheet;
sumAmount += amount;
});
if (sumBoxQtyEl) sumBoxQtyEl.textContent = formatNumber(sumBoxQty);
if (sumSheetQtyEl) sumSheetQtyEl.textContent = formatNumber(sumSheetQty);
if (sumAmountEl) sumAmountEl.textContent = formatNumber(sumAmount);
if (sumItemAmountEl) sumItemAmountEl.textContent = formatNumber(sumAmount);
const feeRateSource = feeRateSummaryInput?.value ?? '0';
const feeRate = Math.max(0, parseFloat(feeRateSource) || 0);
const feeAmount = Math.round(sumAmount * (feeRate / 100));
const grandAmount = sumAmount + feeAmount;
if (feeRateSummaryInput && feeRateSummaryInput.value !== feeRate.toString()) {
feeRateSummaryInput.value = feeRate.toString();
}
if (sumFeeAmountEl) sumFeeAmountEl.textContent = formatNumber(feeAmount);
if (sumGrandAmountEl) sumGrandAmountEl.textContent = formatNumber(grandAmount);
};
const setActiveRow = (code) => {
activeCode = code || null;
selectedBody.querySelectorAll('tr[data-item-row]').forEach((row) => {
row.classList.toggle('bg-amber-50', row.dataset.code === activeCode);
});
};
const renderSelectedRows = () => {
const codes = Object.keys(bagMeta).filter((code) => selectedItems.has(code));
if (codes.length === 0) {
selectedBody.innerHTML = `<tr><td colspan="${colEmpty}" class="text-center text-gray-400 py-4">아래 "발주 등록 종류"에서 봉투를 선택해 주세요.</td></tr>`;
setActiveRow(null);
updateTotals();
updateReferenceSelectionUi();
applyModeLock();
return;
}
selectedBody.innerHTML = codes.map((code, idx) => {
const meta = bagMeta[code];
const qtyBox = Math.max(0, parseInt(String(selectedItems.get(code)?.qtyBox ?? 0), 10));
const name = meta?.name || code;
const up = Number(meta.orderPrice || 0);
const priceCell = editMode && changeMode === 'price'
? `<td class="text-right pr-1"><input type="number" name="item_unit_price[]" step="0.01" min="0" class="item-unit-price-input border border-gray-300 rounded px-1 py-0.5 w-full text-sm text-right" value="${up}" /></td>`
: `<td class="text-right pr-2 item-unit-price">0</td>`;
const extraCols = editMode
? `<td class="text-right pr-2 item-sel-qty">0</td><td class="text-center text-[11px] item-lot-cell leading-tight text-gray-700">생성<br /><span class="font-mono">${escapeHtml(orderLotNo || '—')}</span></td>`
: '';
return `
<tr data-item-row data-code="${escapeHtml(code)}" class="cursor-pointer">
<td class="text-center">${idx + 1}</td>
<td class="text-center">
<button type="button" class="js-remove-selected text-xs text-red-600 hover:underline" data-code="${escapeHtml(code)}">해제</button>
</td>
<td class="text-left pl-2">
${escapeHtml(name)}
<input type="hidden" name="item_bag_code[]" value="${escapeHtml(code)}" />
</td>
<td>
<input name="item_qty_box[]" type="number" min="0" step="1" value="${qtyBox}" class="item-qty-box border border-gray-300 rounded px-2 py-1 text-sm w-full text-right leading-tight" />
<p class="text-[11px] text-gray-500 mt-1 item-sheet-help">낱장 0장</p>
</td>
${priceCell}
<td class="text-right pr-2 item-qty-sheet">0</td>
${extraCols}
<td class="text-right pr-2 item-amount">0</td>
</tr>
`;
}).join('');
if (!activeCode || !selectedItems.has(activeCode)) {
activeCode = codes[0];
}
setActiveRow(activeCode);
updateTotals();
updateReferenceSelectionUi();
applyModeLock();
};
const toggleSelection = (code) => {
if (!code || !bagMeta[code]) return;
if (selectedItems.has(code)) {
selectedItems.delete(code);
if (activeCode === code) activeCode = null;
} else {
selectedItems.set(code, { qtyBox: 0 });
activeCode = code;
}
renderSelectedRows();
};
initialSelectedItems.forEach((item) => {
if (!item || !item.code || !bagMeta[item.code]) return;
selectedItems.set(item.code, { qtyBox: Math.max(0, parseInt(String(item.qtyBox ?? 0), 10)) });
activeCode = item.code;
});
selectedBody.addEventListener('click', (event) => {
const removeButton = event.target.closest('.js-remove-selected');
if (removeButton) {
toggleSelection(removeButton.dataset.code || '');
return;
}
const row = event.target.closest('tr[data-item-row]');
if (!row) return;
const code = row.dataset.code || '';
setActiveRow(code);
const qtyInput = row.querySelector('.item-qty-box');
if (qtyInput) qtyInput.focus();
});
selectedBody.addEventListener('input', (event) => {
const qtyInput = event.target.closest('.item-qty-box');
const priceField = event.target.closest('.item-unit-price-input');
if (qtyInput) {
const row = qtyInput.closest('tr[data-item-row]');
if (!row) return;
const code = row.dataset.code || '';
selectedItems.set(code, { qtyBox: Math.max(0, parseInt(qtyInput.value || '0', 10)) });
updateTotals();
return;
}
if (priceField) {
updateTotals();
}
});
referenceRows.forEach((row) => {
row.addEventListener('click', (event) => {
const button = event.target.closest('.js-toggle-bag');
if (button) {
toggleSelection(button.dataset.code || '');
return;
}
if (event.target.closest('td')) {
toggleSelection(row.dataset.code || '');
}
});
});
if (monthInput) monthInput.addEventListener('change', () => { syncDateFromMonth(); updateTotals(); });
if (orderDateInput) orderDateInput.addEventListener('change', syncMonthFromDate);
if (feeRateSummaryInput) {
feeRateSummaryInput.addEventListener('input', () => {
updateTotals();
});
}
if (orderForm) {
orderForm.addEventListener('submit', (event) => {
const hasValidItem = Array.from(selectedBody.querySelectorAll('tr[data-item-row]')).some((row) => {
const qtyInput = row.querySelector('.item-qty-box');
return Math.max(0, parseInt(qtyInput?.value || '0', 10)) > 0;
});
if (!hasValidItem) {
event.preventDefault();
alert('봉투를 선택하고 수량을 1 이상 입력해 주세요.');
}
});
}
syncMonthFromDate();
updateMonthKoLabel();
renderSelectedRows();
if (editMode) {
document.querySelectorAll('input[name="bo_change_mode"]').forEach((r) => {
r.addEventListener('change', () => {
const u = new URL(window.location.href);
u.searchParams.set('change_mode', r.value);
window.location.href = u.toString();
});
});
}
})();
</script>
<?php endif; ?>