2026-06-01 16:15:15 +09:00
|
|
|
<?php
|
|
|
|
|
$bagMeta = is_array($bagMeta ?? null) ? $bagMeta : [];
|
|
|
|
|
$inventoryMap = is_array($inventoryMap ?? null) ? $inventoryMap : [];
|
|
|
|
|
$availableBagRows = is_array($availableBagRows ?? null) ? $availableBagRows : [];
|
|
|
|
|
$recentIssueRows = is_array($recentIssueRows ?? null) ? $recentIssueRows : [];
|
|
|
|
|
$dongCodes = is_array($dongCodes ?? null) ? $dongCodes : [];
|
|
|
|
|
$freeDongSet = is_array($freeDongSet ?? null) ? $freeDongSet : [];
|
|
|
|
|
$destTypeOptions = is_array($destTypeOptions ?? null) ? $destTypeOptions : ['동사무소', '구청', '기타'];
|
|
|
|
|
$defaultDestType = (string) old('bi2_dest_type', (string) ($destTypeOptions[0] ?? '구청'));
|
|
|
|
|
|
|
|
|
|
$oldItemCodes = old('item_bag_code');
|
|
|
|
|
$oldItemQtys = old('item_qty');
|
|
|
|
|
$oldItemPacks = old('item_pack');
|
|
|
|
|
$oldItemCodes = is_array($oldItemCodes) ? $oldItemCodes : [];
|
|
|
|
|
$oldItemQtys = is_array($oldItemQtys) ? $oldItemQtys : [];
|
|
|
|
|
$oldItemPacks = is_array($oldItemPacks) ? $oldItemPacks : [];
|
|
|
|
|
|
|
|
|
|
$initialRows = [];
|
|
|
|
|
$oldCount = max(count($oldItemCodes), count($oldItemQtys), count($oldItemPacks));
|
|
|
|
|
for ($i = 0; $i < $oldCount; $i++) {
|
|
|
|
|
$initialRows[] = [
|
|
|
|
|
'code' => trim((string) ($oldItemCodes[$i] ?? '')),
|
|
|
|
|
'qty' => max(0, (int) ($oldItemQtys[$i] ?? 0)),
|
|
|
|
|
'pack' => (string) ($oldItemPacks[$i] ?? 'sheet'),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
if ($initialRows === []) {
|
|
|
|
|
$initialRows[] = ['code' => '', 'qty' => 0, 'pack' => 'sheet'];
|
|
|
|
|
}
|
|
|
|
|
?>
|
|
|
|
|
|
2026-03-26 16:13:07 +09:00
|
|
|
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
|
|
|
|
<span class="text-sm font-bold text-gray-700">무료용 불출 처리</span>
|
|
|
|
|
</section>
|
2026-06-01 16:15:15 +09:00
|
|
|
|
|
|
|
|
<div class="border border-gray-300 p-3 mt-2 bg-white">
|
|
|
|
|
<form action="<?= base_url('bag/issue/store') ?>" method="POST" class="space-y-3" id="bag-issue-form">
|
2026-03-26 16:13:07 +09:00
|
|
|
<?= csrf_field() ?>
|
|
|
|
|
|
2026-06-01 16:15:15 +09:00
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-6 gap-2 text-sm">
|
|
|
|
|
<div class="flex items-center gap-2 min-w-0">
|
|
|
|
|
<label class="font-bold text-gray-700 whitespace-nowrap shrink-0">불출년도</label>
|
|
|
|
|
<input class="border border-gray-300 rounded px-2 py-1 w-24 max-w-full text-right" name="bi2_year" type="number" min="2000" max="2099" value="<?= esc(old('bi2_year', date('Y'))) ?>" required/>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center gap-2 min-w-0">
|
|
|
|
|
<label class="font-bold text-gray-700 whitespace-nowrap shrink-0">분기</label>
|
|
|
|
|
<select class="border border-gray-300 rounded px-2 py-1 w-24 max-w-full" name="bi2_quarter" required>
|
|
|
|
|
<option value="1" <?= old('bi2_quarter', (string) ceil((int) date('n') / 3)) === '1' ? 'selected' : '' ?>>1/4</option>
|
|
|
|
|
<option value="2" <?= old('bi2_quarter', (string) ceil((int) date('n') / 3)) === '2' ? 'selected' : '' ?>>2/4</option>
|
|
|
|
|
<option value="3" <?= old('bi2_quarter', (string) ceil((int) date('n') / 3)) === '3' ? 'selected' : '' ?>>3/4</option>
|
|
|
|
|
<option value="4" <?= old('bi2_quarter', (string) ceil((int) date('n') / 3)) === '4' ? 'selected' : '' ?>>4/4</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center gap-2 min-w-0">
|
|
|
|
|
<label class="font-bold text-gray-700 whitespace-nowrap shrink-0">불출구분</label>
|
|
|
|
|
<select class="border border-gray-300 rounded px-2 py-1 w-28 max-w-full" name="bi2_issue_type" id="bi2_issue_type" required>
|
|
|
|
|
<option value="무료용" <?= old('bi2_issue_type', '무료용') === '무료용' ? 'selected' : '' ?>>무료용</option>
|
|
|
|
|
<option value="공공용" <?= old('bi2_issue_type') === '공공용' ? 'selected' : '' ?>>공공용</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center gap-2 min-w-0">
|
|
|
|
|
<label class="font-bold text-gray-700 whitespace-nowrap shrink-0">불출일</label>
|
|
|
|
|
<input class="border border-gray-300 rounded px-2 py-1 w-36 max-w-full" name="bi2_issue_date" type="date" value="<?= esc(old('bi2_issue_date', date('Y-m-d'))) ?>" required/>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center gap-2 min-w-0">
|
|
|
|
|
<label class="font-bold text-gray-700 whitespace-nowrap shrink-0">불출처구분</label>
|
|
|
|
|
<select class="border border-gray-300 rounded px-2 py-1 w-28 max-w-full" name="bi2_dest_type" id="bi2_dest_type">
|
|
|
|
|
<?php foreach ($destTypeOptions as $option): ?>
|
|
|
|
|
<option value="<?= esc($option) ?>" <?= $defaultDestType === $option ? 'selected' : '' ?>><?= esc($option) ?></option>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center gap-2 min-w-0 md:col-span-2 xl:col-span-2">
|
|
|
|
|
<label class="font-bold text-gray-700 whitespace-nowrap shrink-0">불출처(동)</label>
|
|
|
|
|
<select class="border border-gray-300 rounded px-2 py-1 w-full min-w-[20rem]" id="bi2_dest_name" name="bi2_dest_name" required>
|
|
|
|
|
<option value="">선택</option>
|
|
|
|
|
<?php foreach ($dongCodes as $dong): ?>
|
|
|
|
|
<?php $dCode = (string) ($dong->cd_code ?? ''); ?>
|
|
|
|
|
<?php $dName = (string) ($dong->cd_name ?? $dCode); ?>
|
|
|
|
|
<?php $hasFree = isset($freeDongSet[$dCode]); ?>
|
|
|
|
|
<?php $oldDest = (string) old('bi2_dest_name'); ?>
|
|
|
|
|
<option
|
|
|
|
|
value="<?= esc($dName) ?>"
|
|
|
|
|
data-dong-code="<?= esc($dCode) ?>"
|
|
|
|
|
data-has-free="<?= $hasFree ? '1' : '0' ?>"
|
|
|
|
|
<?= $oldDest === $dName ? 'selected' : '' ?>
|
|
|
|
|
>
|
|
|
|
|
<?= esc($dName) ?><?= $hasFree ? ' (무료용 가능)' : ' (무료용 없음)' ?>
|
|
|
|
|
</option>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</select>
|
|
|
|
|
<input type="hidden" name="bi2_dest_dong_code" id="bi2_dest_dong_code" value="<?= esc((string) old('bi2_dest_dong_code')) ?>" />
|
|
|
|
|
</div>
|
2026-03-26 16:13:07 +09:00
|
|
|
</div>
|
|
|
|
|
|
2026-06-01 16:15:15 +09:00
|
|
|
<div class="border border-gray-300 rounded p-2 bg-gray-50 space-y-2">
|
|
|
|
|
<div class="flex flex-wrap items-center gap-2 text-sm">
|
|
|
|
|
<label class="font-bold text-gray-700">바코드 스캔</label>
|
|
|
|
|
<input
|
|
|
|
|
id="barcode_input"
|
|
|
|
|
type="text"
|
|
|
|
|
class="border border-gray-300 rounded px-2 py-1 w-72"
|
|
|
|
|
placeholder="스캐너로 바코드를 입력 후 Enter"
|
|
|
|
|
autocomplete="off"
|
|
|
|
|
/>
|
|
|
|
|
<button type="button" id="add-row-btn" class="border border-gray-300 text-gray-700 px-3 py-1 rounded-sm hover:bg-white">행 추가</button>
|
|
|
|
|
<span class="text-xs text-gray-500">동일 바코드 연속 스캔은 무시됩니다.</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="text-xs text-gray-600">
|
|
|
|
|
입고 재고가 있는 봉투/스티커만 불출 가능합니다. 저장 시 포장단위가 낱장으로 환산되어 재고가 차감됩니다.
|
|
|
|
|
</div>
|
2026-03-26 16:13:07 +09:00
|
|
|
</div>
|
|
|
|
|
|
2026-06-01 16:15:15 +09:00
|
|
|
<div class="border border-gray-300 overflow-auto">
|
|
|
|
|
<table class="w-full data-table text-sm" id="issue-item-table">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th class="w-14">No</th>
|
|
|
|
|
<th class="w-44">봉투코드</th>
|
|
|
|
|
<th>봉투종류</th>
|
|
|
|
|
<th class="w-28">수량</th>
|
|
|
|
|
<th class="w-28">포장</th>
|
|
|
|
|
<th class="w-32">재고(낱장)</th>
|
|
|
|
|
<th class="w-36">환산(낱장)</th>
|
|
|
|
|
<th class="w-20">작업</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody id="issue-item-body"></tbody>
|
|
|
|
|
<tfoot>
|
|
|
|
|
<tr class="bg-gray-50 font-semibold">
|
|
|
|
|
<td colspan="6" class="text-right pr-2">합계(환산 낱장)</td>
|
|
|
|
|
<td class="text-right pr-2" id="sum_sheet_qty">0</td>
|
|
|
|
|
<td></td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tfoot>
|
|
|
|
|
</table>
|
2026-03-26 16:13:07 +09:00
|
|
|
</div>
|
|
|
|
|
|
2026-06-01 16:15:15 +09:00
|
|
|
<div class="flex gap-2 pt-2">
|
|
|
|
|
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">저장</button>
|
|
|
|
|
<a href="<?= base_url('bag/issue/cancel') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
2026-03-26 16:13:07 +09:00
|
|
|
</div>
|
2026-06-01 16:15:15 +09:00
|
|
|
</form>
|
|
|
|
|
</div>
|
2026-03-26 16:13:07 +09:00
|
|
|
|
2026-06-01 16:15:15 +09:00
|
|
|
<div class="grid grid-cols-1 xl:grid-cols-2 gap-2 mt-2">
|
|
|
|
|
<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 max-h-[300px]">
|
|
|
|
|
<table class="w-full data-table text-sm">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th class="w-12">No</th>
|
|
|
|
|
<th class="w-28">봉투코드</th>
|
|
|
|
|
<th>봉투명</th>
|
|
|
|
|
<th class="w-24">재고(낱장)</th>
|
|
|
|
|
<th class="w-24">팩당 낱장</th>
|
|
|
|
|
<th class="w-24">박스당 낱장</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<?php if ($availableBagRows !== []): ?>
|
|
|
|
|
<?php foreach ($availableBagRows as $idx => $row): ?>
|
|
|
|
|
<tr>
|
|
|
|
|
<td class="text-center"><?= $idx + 1 ?></td>
|
|
|
|
|
<td class="text-center"><?= esc((string) ($row['bag_code'] ?? '')) ?></td>
|
|
|
|
|
<td class="pl-2"><?= esc((string) ($row['bag_name'] ?? '')) ?></td>
|
|
|
|
|
<td class="text-right pr-2"><?= number_format((int) ($row['inventory_qty'] ?? 0)) ?></td>
|
|
|
|
|
<td class="text-right pr-2"><?= number_format((int) ($row['pack_per_sheet'] ?? 1)) ?></td>
|
|
|
|
|
<td class="text-right pr-2"><?= number_format((int) ($row['total_per_box'] ?? 1)) ?></td>
|
|
|
|
|
</tr>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
<tr>
|
|
|
|
|
<td colspan="6" class="text-center text-gray-400 py-4">불출 가능한 재고가 없습니다.</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
2026-03-26 16:13:07 +09:00
|
|
|
</div>
|
2026-06-01 16:15:15 +09:00
|
|
|
</section>
|
2026-03-26 16:13:07 +09:00
|
|
|
|
2026-06-01 16:15:15 +09:00
|
|
|
<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 max-h-[300px]">
|
|
|
|
|
<table class="w-full data-table text-sm">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th class="w-12">No</th>
|
|
|
|
|
<th class="w-24">불출일</th>
|
|
|
|
|
<th class="w-20">구분</th>
|
|
|
|
|
<th class="w-28">불출처</th>
|
|
|
|
|
<th class="w-24">봉투코드</th>
|
|
|
|
|
<th>봉투명</th>
|
|
|
|
|
<th class="w-24">수량(낱장)</th>
|
|
|
|
|
<th class="w-16">상태</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<?php if ($recentIssueRows !== []): ?>
|
|
|
|
|
<?php foreach ($recentIssueRows as $idx => $row): ?>
|
|
|
|
|
<tr>
|
|
|
|
|
<td class="text-center"><?= $idx + 1 ?></td>
|
|
|
|
|
<td class="text-center"><?= esc((string) ($row->bi2_issue_date ?? '')) ?></td>
|
|
|
|
|
<td class="text-center"><?= esc((string) ($row->bi2_issue_type ?? '')) ?></td>
|
|
|
|
|
<td class="pl-2"><?= esc((string) ($row->bi2_dest_name ?? '')) ?></td>
|
|
|
|
|
<td class="text-center"><?= esc((string) ($row->bi2_bag_code ?? '')) ?></td>
|
|
|
|
|
<td class="pl-2"><?= esc((string) ($row->bi2_bag_name ?? '')) ?></td>
|
|
|
|
|
<td class="text-right pr-2"><?= number_format((int) ($row->bi2_qty ?? 0)) ?></td>
|
|
|
|
|
<td class="text-center">
|
|
|
|
|
<?php if ((string) ($row->bi2_status ?? 'normal') === 'cancelled'): ?>
|
|
|
|
|
<span class="text-orange-600">취소</span>
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
정상
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
<tr>
|
|
|
|
|
<td colspan="8" class="text-center text-gray-400 py-4">최근 불출 내역이 없습니다.</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
2026-03-26 16:13:07 +09:00
|
|
|
</div>
|
2026-06-01 16:15:15 +09:00
|
|
|
</section>
|
|
|
|
|
</div>
|
2026-03-26 16:13:07 +09:00
|
|
|
|
2026-06-01 16:15:15 +09:00
|
|
|
<script>
|
|
|
|
|
(() => {
|
|
|
|
|
const bagMeta = <?= json_encode($bagMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
|
|
|
|
const initialRows = <?= json_encode($initialRows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
|
|
|
|
const knownCodes = Object.keys(bagMeta);
|
|
|
|
|
const body = document.getElementById('issue-item-body');
|
|
|
|
|
const sumSheetQtyEl = document.getElementById('sum_sheet_qty');
|
|
|
|
|
const barcodeInput = document.getElementById('barcode_input');
|
|
|
|
|
const addRowBtn = document.getElementById('add-row-btn');
|
|
|
|
|
const issueTypeEl = document.getElementById('bi2_issue_type');
|
|
|
|
|
const destTypeEl = document.getElementById('bi2_dest_type');
|
|
|
|
|
const destNameEl = document.getElementById('bi2_dest_name');
|
|
|
|
|
const destDongCodeEl = document.getElementById('bi2_dest_dong_code');
|
|
|
|
|
const form = document.getElementById('bag-issue-form');
|
|
|
|
|
let lastScannedCode = '';
|
|
|
|
|
const rows = [];
|
2026-03-26 16:13:07 +09:00
|
|
|
|
2026-06-01 16:15:15 +09:00
|
|
|
const createBagTypeOptions = (selectedCode) => {
|
|
|
|
|
const opts = ['<option value="">선택</option>'];
|
|
|
|
|
knownCodes.forEach((code) => {
|
|
|
|
|
const name = bagMeta[code]?.name || code;
|
|
|
|
|
opts.push(`<option value="${code}" ${code === selectedCode ? 'selected' : ''}>${code} - ${name}</option>`);
|
|
|
|
|
});
|
|
|
|
|
return opts.join('');
|
|
|
|
|
};
|
2026-03-26 16:13:07 +09:00
|
|
|
|
2026-06-01 16:15:15 +09:00
|
|
|
const resolveBagCode = (raw) => {
|
|
|
|
|
const src = String(raw || '').trim();
|
|
|
|
|
if (src === '') return '';
|
|
|
|
|
if (bagMeta[src]) return src;
|
|
|
|
|
|
|
|
|
|
const noSpace = src.replace(/\s+/g, '');
|
|
|
|
|
if (bagMeta[noSpace]) return noSpace;
|
|
|
|
|
|
|
|
|
|
const compact = noSpace.replace(/[^0-9A-Za-z]/g, '');
|
|
|
|
|
if (bagMeta[compact]) return compact;
|
|
|
|
|
|
|
|
|
|
for (const code of knownCodes) {
|
|
|
|
|
if (compact.includes(code)) {
|
|
|
|
|
return code;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return '';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const toSheetQty = (code, qty, pack) => {
|
|
|
|
|
const n = Math.max(0, parseInt(String(qty || 0), 10) || 0);
|
|
|
|
|
const meta = bagMeta[code] || { packPerSheet: 1, totalPerBox: 1 };
|
|
|
|
|
if (pack === 'box') return n * Math.max(1, parseInt(meta.totalPerBox, 10) || 1);
|
|
|
|
|
if (pack === 'pack') return n * Math.max(1, parseInt(meta.packPerSheet, 10) || 1);
|
|
|
|
|
return n;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const recompute = () => {
|
|
|
|
|
let sum = 0;
|
|
|
|
|
rows.forEach((row, idx) => {
|
|
|
|
|
const tr = row.tr;
|
|
|
|
|
tr.querySelector('.col-no').textContent = String(idx + 1);
|
|
|
|
|
const code = row.codeInput.value.trim();
|
|
|
|
|
const qty = row.qtyInput.value;
|
|
|
|
|
const pack = row.packSelect.value;
|
|
|
|
|
const sheetQty = toSheetQty(code, qty, pack);
|
|
|
|
|
const invQty = parseInt((bagMeta[code]?.inventoryQty || 0), 10) || 0;
|
|
|
|
|
row.invCell.textContent = new Intl.NumberFormat('ko-KR').format(invQty);
|
|
|
|
|
row.sheetCell.textContent = new Intl.NumberFormat('ko-KR').format(sheetQty);
|
|
|
|
|
if (code && sheetQty > invQty) {
|
|
|
|
|
row.sheetCell.classList.add('text-red-600', 'font-semibold');
|
|
|
|
|
} else {
|
|
|
|
|
row.sheetCell.classList.remove('text-red-600', 'font-semibold');
|
|
|
|
|
}
|
|
|
|
|
sum += sheetQty;
|
|
|
|
|
});
|
|
|
|
|
sumSheetQtyEl.textContent = new Intl.NumberFormat('ko-KR').format(sum);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const syncCode = (row, code, setAsUserSelection = false) => {
|
|
|
|
|
const resolved = resolveBagCode(code);
|
|
|
|
|
if (resolved === '') return false;
|
|
|
|
|
row.codeInput.value = resolved;
|
|
|
|
|
row.typeSelect.value = resolved;
|
|
|
|
|
if (setAsUserSelection || row.packSelect.value === '') {
|
|
|
|
|
row.packSelect.value = (bagMeta[resolved]?.totalPerBox || 1) > 1 ? 'box' : 'sheet';
|
|
|
|
|
}
|
|
|
|
|
recompute();
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const addRow = (seed = {}) => {
|
|
|
|
|
const tr = document.createElement('tr');
|
|
|
|
|
tr.innerHTML = `
|
|
|
|
|
<td class="text-center col-no"></td>
|
|
|
|
|
<td class="px-1 py-1">
|
|
|
|
|
<input type="text" name="item_bag_code[]" class="code-input border border-gray-300 rounded px-2 py-1 w-full text-sm" value="" placeholder="봉투코드"/>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="px-1 py-1">
|
|
|
|
|
<select name="item_bag_type[]" class="type-select border border-gray-300 rounded px-2 py-1 w-full text-sm">
|
|
|
|
|
${createBagTypeOptions('')}
|
|
|
|
|
</select>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="px-1 py-1">
|
|
|
|
|
<input type="number" min="0" step="1" name="item_qty[]" class="qty-input border border-gray-300 rounded px-2 py-1 w-full text-sm text-right" value="0"/>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="px-1 py-1">
|
|
|
|
|
<select name="item_pack[]" class="pack-select border border-gray-300 rounded px-2 py-1 w-full text-sm">
|
|
|
|
|
<option value="box">박스</option>
|
|
|
|
|
<option value="pack">팩</option>
|
|
|
|
|
<option value="sheet">낱장</option>
|
|
|
|
|
</select>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="text-right pr-2 inv-cell">0</td>
|
|
|
|
|
<td class="text-right pr-2 sheet-cell">0</td>
|
|
|
|
|
<td class="text-center">
|
|
|
|
|
<button type="button" class="remove-btn text-red-600 hover:underline text-xs">삭제</button>
|
|
|
|
|
</td>
|
|
|
|
|
`;
|
|
|
|
|
body.appendChild(tr);
|
|
|
|
|
const row = {
|
|
|
|
|
tr,
|
|
|
|
|
codeInput: tr.querySelector('.code-input'),
|
|
|
|
|
typeSelect: tr.querySelector('.type-select'),
|
|
|
|
|
qtyInput: tr.querySelector('.qty-input'),
|
|
|
|
|
packSelect: tr.querySelector('.pack-select'),
|
|
|
|
|
invCell: tr.querySelector('.inv-cell'),
|
|
|
|
|
sheetCell: tr.querySelector('.sheet-cell'),
|
|
|
|
|
removeBtn: tr.querySelector('.remove-btn'),
|
|
|
|
|
};
|
|
|
|
|
rows.push(row);
|
|
|
|
|
|
|
|
|
|
row.codeInput.addEventListener('change', () => {
|
|
|
|
|
const ok = syncCode(row, row.codeInput.value, true);
|
|
|
|
|
if (!ok) {
|
|
|
|
|
row.codeInput.value = '';
|
|
|
|
|
row.typeSelect.value = '';
|
|
|
|
|
alert('입고 재고가 있는 봉투코드만 입력할 수 있습니다.');
|
|
|
|
|
}
|
|
|
|
|
recompute();
|
|
|
|
|
});
|
|
|
|
|
row.typeSelect.addEventListener('change', () => {
|
|
|
|
|
row.codeInput.value = row.typeSelect.value;
|
|
|
|
|
recompute();
|
|
|
|
|
});
|
|
|
|
|
row.qtyInput.addEventListener('input', recompute);
|
|
|
|
|
row.packSelect.addEventListener('change', recompute);
|
|
|
|
|
row.removeBtn.addEventListener('click', () => {
|
|
|
|
|
if (rows.length <= 1) return;
|
|
|
|
|
tr.remove();
|
|
|
|
|
const i = rows.indexOf(row);
|
|
|
|
|
if (i >= 0) rows.splice(i, 1);
|
|
|
|
|
recompute();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (seed.code) {
|
|
|
|
|
syncCode(row, seed.code, true);
|
|
|
|
|
}
|
|
|
|
|
row.qtyInput.value = String(Math.max(0, parseInt(String(seed.qty || 0), 10) || 0));
|
|
|
|
|
if (['box', 'pack', 'sheet'].includes(String(seed.pack || ''))) {
|
|
|
|
|
row.packSelect.value = String(seed.pack);
|
|
|
|
|
}
|
|
|
|
|
recompute();
|
|
|
|
|
return row;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const appendScannedRow = (code) => {
|
|
|
|
|
const row = addRow({ code, qty: 1, pack: (bagMeta[code]?.totalPerBox || 1) > 1 ? 'box' : 'sheet' });
|
|
|
|
|
row.qtyInput.focus();
|
|
|
|
|
row.qtyInput.select();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const syncDestDongCode = () => {
|
|
|
|
|
const opt = destNameEl.options[destNameEl.selectedIndex];
|
|
|
|
|
if (!opt) {
|
|
|
|
|
destDongCodeEl.value = '';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
destDongCodeEl.value = opt.getAttribute('data-dong-code') || '';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateIssueTypeUi = () => {
|
|
|
|
|
const isPublic = issueTypeEl.value === '공공용';
|
|
|
|
|
if (isPublic) {
|
|
|
|
|
destTypeEl.value = '구청';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
addRowBtn.addEventListener('click', () => addRow({ code: '', qty: 0, pack: 'sheet' }));
|
|
|
|
|
|
|
|
|
|
barcodeInput.addEventListener('keydown', (event) => {
|
|
|
|
|
if (event.key !== 'Enter') return;
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
const resolved = resolveBagCode(barcodeInput.value);
|
|
|
|
|
barcodeInput.value = '';
|
|
|
|
|
if (!resolved) {
|
|
|
|
|
alert('인식 가능한 봉투코드가 아닙니다.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (resolved === lastScannedCode) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
lastScannedCode = resolved;
|
|
|
|
|
appendScannedRow(resolved);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
destNameEl.addEventListener('change', syncDestDongCode);
|
|
|
|
|
issueTypeEl.addEventListener('change', updateIssueTypeUi);
|
|
|
|
|
|
|
|
|
|
form.addEventListener('submit', (event) => {
|
|
|
|
|
syncDestDongCode();
|
|
|
|
|
updateIssueTypeUi();
|
|
|
|
|
|
|
|
|
|
const validRows = rows.filter((row) => {
|
|
|
|
|
const code = resolveBagCode(row.codeInput.value);
|
|
|
|
|
const qty = parseInt(row.qtyInput.value || '0', 10) || 0;
|
|
|
|
|
return code !== '' && qty > 0;
|
|
|
|
|
});
|
|
|
|
|
if (validRows.length === 0) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
alert('불출 품목을 1건 이상 입력해 주세요.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const row of validRows) {
|
|
|
|
|
const code = resolveBagCode(row.codeInput.value);
|
|
|
|
|
const sheetQty = toSheetQty(code, row.qtyInput.value, row.packSelect.value);
|
|
|
|
|
const inv = parseInt((bagMeta[code]?.inventoryQty || 0), 10) || 0;
|
|
|
|
|
if (sheetQty > inv) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
alert(`재고 부족: ${code} (재고 ${inv}, 요청 ${sheetQty})`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
row.codeInput.value = code;
|
|
|
|
|
row.typeSelect.value = code;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (issueTypeEl.value === '무료용') {
|
|
|
|
|
const selected = destNameEl.options[destNameEl.selectedIndex];
|
|
|
|
|
const hasFree = selected ? selected.getAttribute('data-has-free') === '1' : false;
|
|
|
|
|
if (!hasFree) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
alert('무료용 불출은 "무료용 가능" 불출처(동)만 선택할 수 있습니다.');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
initialRows.forEach((row) => addRow(row));
|
|
|
|
|
if (rows.length > 1 && !initialRows[0]?.code && !initialRows[0]?.qty) {
|
|
|
|
|
rows[0].tr.remove();
|
|
|
|
|
rows.shift();
|
|
|
|
|
}
|
|
|
|
|
syncDestDongCode();
|
|
|
|
|
updateIssueTypeUi();
|
|
|
|
|
recompute();
|
|
|
|
|
})();
|
|
|
|
|
</script>
|