- 사용자 매뉴얼: league/commonmark 기반 bag/manual(로그인 전용), ManualRenderer + Config\Manual manifest, 콘텐츠 8종, E2E - 번호알기(봉투번호확인): bag/number-lookup, BagNumberLookup, E2E - gov-portal 대시보드 시안(기본/strip)·기본코드관리 화면 - 메뉴 관리: 등록·수정 후 메뉴 화면 유지, 수정 버튼 클릭 시 상단 스크롤 - 수불/분석 리포트(LOT 수불·반품/파기·수급계획·추이) 표시 보강 - .gitignore: docs/ → /docs/ 앵커링(최상위 개발문서만 제외, app/Docs는 추적) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
308 lines
10 KiB
PHP
308 lines
10 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
$code = (string) ($code ?? '');
|
|
$result = is_array($result ?? null) ? $result : null;
|
|
$barcodeText = (string) ($result['barcode_text'] ?? '- - - -');
|
|
$printText = (string) ($result['print_text'] ?? '- - -');
|
|
$recognition = (string) ($result['recognition_text'] ?? '- -');
|
|
$error = (string) ($error ?? ($result['ok'] ?? true ? '' : ($result['message'] ?? '')));
|
|
$unit = (string) ($result['unit'] ?? '');
|
|
$bagName = (string) ($result['bag_name'] ?? '');
|
|
$bagCode = (string) ($result['bag_code'] ?? '');
|
|
$hasResult = $result !== null && ($result['ok'] ?? false);
|
|
?>
|
|
<style>
|
|
.num-lookup-wrap {
|
|
max-width: 28rem;
|
|
margin: 1.5rem auto 2rem;
|
|
border: 1px solid #9ca3af;
|
|
background: #ece9d8;
|
|
box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.08);
|
|
font-family: 'Malgun Gothic', 'Noto Sans KR', sans-serif;
|
|
}
|
|
.num-lookup-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
padding: 0.35rem 0.55rem;
|
|
background: linear-gradient(180deg, #3a6ea5 0%, #2c5282 100%);
|
|
color: #fff;
|
|
font-size: 0.8125rem;
|
|
font-weight: 700;
|
|
}
|
|
.num-lookup-body { padding: 0.85rem 0.9rem 1rem; }
|
|
.num-lookup-label {
|
|
display: block;
|
|
font-size: 0.8125rem;
|
|
font-weight: 600;
|
|
color: #1f2937;
|
|
margin-bottom: 0.35rem;
|
|
}
|
|
.num-lookup-input {
|
|
width: 100%;
|
|
border: 1px solid #9ca3af;
|
|
background: #fff;
|
|
padding: 0.35rem 0.45rem;
|
|
font-size: 0.875rem;
|
|
font-family: inherit;
|
|
}
|
|
.num-lookup-input:focus {
|
|
outline: 1px solid #2563eb;
|
|
border-color: #2563eb;
|
|
}
|
|
.num-lookup-sep {
|
|
border: none;
|
|
border-top: 1px solid #b8b4a8;
|
|
margin: 0.85rem 0;
|
|
}
|
|
.num-lookup-row {
|
|
display: grid;
|
|
grid-template-columns: 5.5rem 1fr;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin-bottom: 0.55rem;
|
|
}
|
|
.num-lookup-row label {
|
|
font-size: 0.8125rem;
|
|
font-weight: 600;
|
|
color: #1f2937;
|
|
text-align: right;
|
|
}
|
|
.num-lookup-out {
|
|
min-height: 1.75rem;
|
|
border: 1px solid #9ca3af;
|
|
background: #fffef0;
|
|
padding: 0.35rem 0.5rem;
|
|
font-size: 0.875rem;
|
|
font-family: 'Consolas', 'Courier New', monospace;
|
|
letter-spacing: 0.04em;
|
|
color: #111827;
|
|
}
|
|
.num-lookup-actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 0.4rem;
|
|
margin-top: 0.75rem;
|
|
}
|
|
.num-lookup-btn {
|
|
border: 1px solid #6b7280;
|
|
background: linear-gradient(180deg, #f9fafb 0%, #e5e7eb 100%);
|
|
padding: 0.3rem 0.85rem;
|
|
font-size: 0.8125rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
}
|
|
.num-lookup-btn-primary {
|
|
border-color: #1c4e80;
|
|
background: linear-gradient(180deg, #2b6cb0 0%, #1c4e80 100%);
|
|
color: #fff;
|
|
}
|
|
.num-lookup-meta {
|
|
margin-top: 0.65rem;
|
|
font-size: 0.75rem;
|
|
color: #4b5563;
|
|
line-height: 1.5;
|
|
}
|
|
.num-lookup-error {
|
|
margin-top: 0.5rem;
|
|
font-size: 0.75rem;
|
|
color: #b91c1c;
|
|
}
|
|
.num-lookup-hint {
|
|
margin: 0.75rem auto 0;
|
|
max-width: 28rem;
|
|
font-size: 0.75rem;
|
|
color: #6b7280;
|
|
line-height: 1.5;
|
|
}
|
|
</style>
|
|
|
|
<div class="num-lookup-wrap" role="dialog" aria-labelledby="numLookupTitle">
|
|
<div class="num-lookup-title" id="numLookupTitle">
|
|
<i class="fa-solid fa-trash-can" aria-hidden="true"></i>
|
|
봉투번호확인
|
|
</div>
|
|
<form class="num-lookup-body" method="get" action="<?= site_url('bag/number-lookup') ?>" id="numLookupForm">
|
|
<label class="num-lookup-label" for="codeInput">코드 입력</label>
|
|
<input
|
|
type="text"
|
|
id="codeInput"
|
|
name="code"
|
|
class="num-lookup-input"
|
|
value="<?= esc($code) ?>"
|
|
autocomplete="off"
|
|
spellcheck="false"
|
|
placeholder="예: OQXCKH-000008-P299-S00125"
|
|
/>
|
|
|
|
<hr class="num-lookup-sep"/>
|
|
|
|
<div class="num-lookup-row">
|
|
<label for="barcodeOut">바코드</label>
|
|
<div id="barcodeOut" class="num-lookup-out" aria-live="polite"><?= esc($barcodeText) ?></div>
|
|
</div>
|
|
<div class="num-lookup-row">
|
|
<label for="printOut">인쇄숫자</label>
|
|
<div id="printOut" class="num-lookup-out" aria-live="polite"><?= esc($printText) ?></div>
|
|
</div>
|
|
<div class="num-lookup-row">
|
|
<label for="recognitionOut">인식번호</label>
|
|
<div id="recognitionOut" class="num-lookup-out" aria-live="polite"><?= esc($recognition) ?></div>
|
|
</div>
|
|
|
|
<?php if ($error !== ''): ?>
|
|
<p class="num-lookup-error" id="numLookupError"><?= esc($error) ?></p>
|
|
<?php else: ?>
|
|
<p class="num-lookup-error" id="numLookupError" hidden></p>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($hasResult && ($unit !== '' || $bagName !== '')): ?>
|
|
<p class="num-lookup-meta" id="numLookupMeta">
|
|
<?php if ($unit !== ''): ?>단위: <?= esc($unit) ?><?php endif; ?>
|
|
<?php if ($bagName !== ''): ?> · <?= esc($bagName) ?><?= $bagCode !== '' ? ' (' . esc($bagCode) . ')' : '' ?><?php endif; ?>
|
|
</p>
|
|
<?php else: ?>
|
|
<p class="num-lookup-meta" id="numLookupMeta" hidden></p>
|
|
<?php endif; ?>
|
|
|
|
<div class="num-lookup-actions">
|
|
<button type="button" class="num-lookup-btn" id="numLookupReset">초기화</button>
|
|
<button type="submit" class="num-lookup-btn num-lookup-btn-primary">확인</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<p class="num-lookup-hint">
|
|
봉투 바코드·LOT·팩·낱장 코드를 입력하면 <strong>바코드</strong>(4칸), <strong>인쇄숫자</strong>(3칸), <strong>인식번호</strong>(2칸)로 나누어 표시합니다.
|
|
등록된 입고 바코드는 DB에서 품목명을 함께 조회합니다.
|
|
</p>
|
|
|
|
<div class="mx-auto mt-4 max-w-xl text-xs text-gray-700">
|
|
<table class="w-full border border-gray-300 border-collapse">
|
|
<thead>
|
|
<tr class="bg-gray-100">
|
|
<th class="border border-gray-300 px-2 py-1 text-left">단위</th>
|
|
<th class="border border-gray-300 px-2 py-1 text-left">입력 예시</th>
|
|
<th class="border border-gray-300 px-2 py-1 text-left">설명</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td class="border border-gray-300 px-2 py-1 align-top">LOT</td>
|
|
<td class="border border-gray-300 px-2 py-1 align-top"><code>OQXCKH</code></td>
|
|
<td class="border border-gray-300 px-2 py-1 align-top">
|
|
발주 LOT 번호만 입력합니다.<br>
|
|
바코드: <code>OQXCKH - - -</code><br>
|
|
인쇄숫자/인식번호는 LOT 기준으로만 표시됩니다.
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="border border-gray-300 px-2 py-1 align-top">박스</td>
|
|
<td class="border border-gray-300 px-2 py-1 align-top"><code>OQXCKH-000008-B001</code></td>
|
|
<td class="border border-gray-300 px-2 py-1 align-top">
|
|
박스 바코드(LOT-입고번호-B박스번호)를 그대로 입력합니다.<br>
|
|
바코드: <code>LOT 입고번호 B박스 -</code><br>
|
|
인쇄숫자: <code>입고번호 박스번호 -</code><br>
|
|
인식번호: <code>입고번호 B박스</code>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="border border-gray-300 px-2 py-1 align-top">팩</td>
|
|
<td class="border border-gray-300 px-2 py-1 align-top"><code>OQXCKH-000008-P299</code></td>
|
|
<td class="border border-gray-300 px-2 py-1 align-top">
|
|
팩 바코드(LOT-입고번호-P팩번호)를 그대로 입력합니다.<br>
|
|
바코드: <code>LOT 입고번호 P팩 -</code><br>
|
|
인쇄숫자: <code>입고번호 팩번호 -</code><br>
|
|
인식번호: <code>입고번호 P팩</code>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="border border-gray-300 px-2 py-1 align-top">낱장</td>
|
|
<td class="border border-gray-300 px-2 py-1 align-top"><code>OQXCKH-000008-P299-S00125</code></td>
|
|
<td class="border border-gray-300 px-2 py-1 align-top">
|
|
낱장 바코드(LOT-입고번호-P팩-S장번호)를 그대로 입력합니다.<br>
|
|
바코드: <code>LOT 입고번호 P팩 S장번호</code><br>
|
|
인쇄숫자: <code>입고번호 팩번호 장번호</code><br>
|
|
인식번호: <code>입고번호 P팩</code>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
var form = document.getElementById('numLookupForm');
|
|
var input = document.getElementById('codeInput');
|
|
var resetBtn = document.getElementById('numLookupReset');
|
|
if (!form || !input) return;
|
|
|
|
var emptyBarcode = '- - - -';
|
|
var emptyPrint = '- - -';
|
|
var emptyRec = '- -';
|
|
|
|
function setOutputs(data) {
|
|
document.getElementById('barcodeOut').textContent = data.barcode_text || emptyBarcode;
|
|
document.getElementById('printOut').textContent = data.print_text || emptyPrint;
|
|
document.getElementById('recognitionOut').textContent = data.recognition_text || emptyRec;
|
|
var err = document.getElementById('numLookupError');
|
|
var meta = document.getElementById('numLookupMeta');
|
|
if (err) {
|
|
if (data.message) {
|
|
err.textContent = data.message;
|
|
err.hidden = false;
|
|
} else {
|
|
err.textContent = '';
|
|
err.hidden = true;
|
|
}
|
|
}
|
|
if (meta) {
|
|
var parts = [];
|
|
if (data.unit) parts.push('단위: ' + data.unit);
|
|
if (data.bag_name) {
|
|
var line = data.bag_name;
|
|
if (data.bag_code) line += ' (' + data.bag_code + ')';
|
|
parts.push(line);
|
|
}
|
|
if (parts.length) {
|
|
meta.textContent = parts.join(' · ');
|
|
meta.hidden = false;
|
|
} else {
|
|
meta.hidden = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
function lookupAjax() {
|
|
var code = (input.value || '').trim();
|
|
if (!code) {
|
|
setOutputs({ message: '코드를 입력해 주세요.', barcode_text: emptyBarcode, print_text: emptyPrint, recognition_text: emptyRec });
|
|
return;
|
|
}
|
|
fetch('<?= site_url('bag/number-lookup/resolve') ?>', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' },
|
|
body: 'code=' + encodeURIComponent(code) + '&<?= csrf_token() ?>=' + encodeURIComponent('<?= csrf_hash() ?>')
|
|
})
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (data) { setOutputs(data || {}); })
|
|
.catch(function () { setOutputs({ message: '조회 중 오류가 발생했습니다.' }); });
|
|
}
|
|
|
|
input.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
lookupAjax();
|
|
}
|
|
});
|
|
|
|
if (resetBtn) {
|
|
resetBtn.addEventListener('click', function () {
|
|
input.value = '';
|
|
setOutputs({ barcode_text: emptyBarcode, print_text: emptyPrint, recognition_text: emptyRec });
|
|
input.focus();
|
|
});
|
|
}
|
|
})();
|
|
</script>
|