Files
jongryangje/app/Views/bag/order_lot_seed.php
taekyoungc de15cc9e47 feat: '발주파일 생성'(구 LOT-No 디스켓 불출) 이름변경 + 생성 전 내용 확인
- 메뉴/화면/매뉴얼 명칭을 '발주파일 생성'으로 변경(메뉴 rename 마이그레이션 SQL 포함)
- 발주파일(seed) 생성 전 '내용 확인' 미리보기 추가(암호화 전 발주 메타·품목 표시)
  · GET bag/order/lot-seed/preview 신설, 생성/미리보기 데이터 구성 공용화

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 18:26:58 +09:00

262 lines
13 KiB
PHP

<?php
$orders = is_array($orders ?? null) ? $orders : [];
$companyMap = is_array($companyMap ?? null) ? $companyMap : [];
$itemSummary = is_array($itemSummary ?? null) ? $itemSummary : [];
$companies = is_array($companies ?? null) ? $companies : [];
$startMonthValue = (string) ($startMonth ?? date('Y-m'));
$endMonthValue = (string) ($endMonth ?? date('Y-m'));
$baseYear = (int) date('Y');
if (preg_match('/^(\d{4})-\d{2}$/', $startMonthValue, $sm)) {
$baseYear = (int) $sm[1];
} elseif (preg_match('/^(\d{4})-\d{2}$/', $endMonthValue, $em)) {
$baseYear = (int) $em[1];
}
$monthOptionValues = [];
for ($year = $baseYear - 2; $year <= $baseYear + 2; $year++) {
for ($m = 1; $m <= 12; $m++) {
$monthValue = sprintf('%04d-%02d', $year, $m);
$monthOptionValues[] = ['value' => $monthValue, 'label' => $year . '년 ' . $m . '월'];
}
}
?>
<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>
<section class="border border-gray-300 rounded-lg p-2 mt-2 bg-white">
<form method="get" 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>
<select id="start_month" name="start_month" class="border border-gray-300 rounded px-2 py-1 w-full min-w-0">
<?php foreach ($monthOptionValues as $opt): ?>
<option value="<?= esc($opt['value']) ?>" <?= $opt['value'] === $startMonthValue ? 'selected' : '' ?>><?= esc($opt['label']) ?></option>
<?php endforeach; ?>
</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 id="end_month" name="end_month" class="border border-gray-300 rounded px-2 py-1 w-full min-w-0">
<?php foreach ($monthOptionValues as $opt): ?>
<option value="<?= esc($opt['value']) ?>" <?= $opt['value'] === $endMonthValue ? 'selected' : '' ?>><?= esc($opt['label']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="flex items-center gap-2 min-w-0 xl:col-span-2">
<label class="font-bold text-gray-700 whitespace-nowrap shrink-0">LOT-No</label>
<input type="text" name="lot_no" value="<?= esc((string) ($lotNo ?? '')) ?>" placeholder="예: ZLCH2M" class="border border-gray-300 rounded px-2 py-1 w-full min-w-0" />
</div>
<div class="flex items-center gap-2 min-w-0">
<label class="font-bold text-gray-700 whitespace-nowrap shrink-0">제작업체</label>
<select name="company_idx" class="border border-gray-300 rounded px-2 py-1 w-full min-w-0">
<option value="0">전체</option>
<?php foreach ($companies as $company): ?>
<?php $cpIdx = (int) ($company->cp_idx ?? 0); ?>
<option value="<?= $cpIdx ?>" <?= (int) ($companyIdx ?? 0) === $cpIdx ? 'selected' : '' ?>>
<?= esc((string) ($company->cp_name ?? '')) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="flex items-center gap-2">
<button type="submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">조회</button>
<a href="<?= base_url('bag/order/lot-seed') ?>" class="text-sm text-gray-500 hover:text-gray-700">초기화</a>
</div>
</form>
</section>
<section class="border border-gray-300 rounded-lg overflow-hidden mt-2 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">
<thead>
<tr>
<th class="w-20 text-center">작업</th>
<th class="w-24 text-center">발주일</th>
<th class="w-28 text-center">LOT-No</th>
<th class="text-left">제작업체</th>
<th class="w-24 text-right">품목수</th>
<th class="w-24 text-right">박스합계</th>
<th class="w-28 text-right">낱장합계</th>
<th class="w-20 text-center">상태</th>
</tr>
</thead>
<tbody>
<?php if ($orders !== []): ?>
<?php foreach ($orders as $order): ?>
<?php
$boIdx = (int) ($order->bo_idx ?? 0);
$sum = $itemSummary[$boIdx] ?? ['line_count' => 0, 'qty_box' => 0, 'qty_sheet' => 0];
$companyName = (string) ($companyMap[(int) ($order->bo_company_idx ?? 0)] ?? '-');
$status = (string) ($order->bo_status ?? 'normal');
?>
<tr>
<td class="text-center">
<button type="button"
class="lot-preview-btn text-blue-600 hover:underline text-xs"
data-bo-idx="<?= $boIdx ?>"
data-lot="<?= esc((string) ($order->bo_lot_no ?? ''), 'attr') ?>">
내용 확인
</button>
</td>
<td class="text-center"><?= esc((string) ($order->bo_order_date ?? '')) ?></td>
<td class="text-center font-mono"><?= esc((string) ($order->bo_lot_no ?? '')) ?></td>
<td class="pl-2"><?= esc($companyName) ?></td>
<td class="text-right pr-2"><?= number_format((int) ($sum['line_count'] ?? 0)) ?></td>
<td class="text-right pr-2"><?= number_format((int) ($sum['qty_box'] ?? 0)) ?></td>
<td class="text-right pr-2"><?= number_format((int) ($sum['qty_sheet'] ?? 0)) ?></td>
<td class="text-center">
<?php if ($status === 'cancelled'): ?>
<span class="text-orange-600">취소</span>
<?php elseif ($status === 'deleted'): ?>
<span class="text-red-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>
</div>
</section>
<?php if (isset($pager)): ?>
<div class="mt-2 mb-2 no-print"><?= $pager->links() ?></div>
<?php endif; ?>
<!-- 발주파일 생성 전 '내용 확인' 모달 -->
<div id="lot-preview-overlay" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/40 p-4">
<div class="bg-white rounded-lg shadow-xl w-full max-w-3xl max-h-[85vh] flex flex-col">
<div class="flex items-center justify-between border-b border-gray-200 px-4 py-2">
<span class="text-sm font-bold text-gray-800">발주파일 내용 확인</span>
<button type="button" id="lot-preview-close" class="text-gray-400 hover:text-gray-700 text-lg leading-none">&times;</button>
</div>
<div class="p-4 overflow-auto text-sm">
<div id="lot-preview-loading" class="text-center text-gray-400 py-8">불러오는 중…</div>
<div id="lot-preview-error" class="hidden text-center text-red-600 py-8"></div>
<div id="lot-preview-body" class="hidden">
<p class="text-xs text-gray-500 mb-2">아래 내용으로 <b>암호화된 발주파일(.seed.json)</b>이 생성·다운로드됩니다. 생성 전 내용을 확인하세요.</p>
<dl class="grid grid-cols-[6rem_1fr] gap-y-1 text-[13px] mb-3 border border-gray-200 rounded p-2 bg-gray-50">
<dt class="font-semibold text-gray-600">LOT-No</dt><dd class="font-mono" id="lp-lot"></dd>
<dt class="font-semibold text-gray-600">발주일</dt><dd id="lp-date"></dd>
<dt class="font-semibold text-gray-600">제작업체</dt><dd id="lp-company"></dd>
<dt class="font-semibold text-gray-600">버전</dt><dd id="lp-version"></dd>
<dt class="font-semibold text-gray-600">무결성해시</dt><dd class="font-mono text-[11px] text-gray-500 break-all" id="lp-hash"></dd>
</dl>
<div class="overflow-auto border border-gray-200 rounded">
<table class="w-full data-table text-[13px]">
<thead>
<tr>
<th class="text-left pl-2">봉투코드</th>
<th class="text-left">봉투명</th>
<th class="w-20 text-right">단가</th>
<th class="w-16 text-right">박스</th>
<th class="w-16 text-right">팩</th>
<th class="w-20 text-right">낱장</th>
<th class="w-24 text-right pr-2">금액</th>
</tr>
</thead>
<tbody id="lp-items"></tbody>
<tfoot>
<tr class="font-semibold bg-gray-50">
<td class="pl-2" colspan="3">합계 (<span id="lp-line-count">0</span>품목)</td>
<td class="text-right" id="lp-sum-box">0</td>
<td class="text-right">—</td>
<td class="text-right" id="lp-sum-sheet">0</td>
<td class="text-right pr-2" id="lp-sum-amount">0</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="border-t border-gray-200 px-4 py-2 flex items-center justify-end gap-2">
<button type="button" id="lot-preview-cancel" class="border border-gray-300 text-gray-600 px-3 py-1.5 rounded-sm text-sm hover:bg-gray-50">닫기</button>
<form method="post" action="<?= base_url('bag/order/lot-seed/generate') ?>" class="inline" id="lot-generate-form">
<?= csrf_field() ?>
<input type="hidden" name="bo_idx" id="lot-generate-bo-idx" value="" />
<button type="submit" id="lot-generate-submit" class="bg-btn-search text-white px-4 py-1.5 rounded-sm text-sm shadow hover:opacity-90 disabled:opacity-50" disabled>발주파일 생성(다운로드)</button>
</form>
</div>
</div>
</div>
<script>
(function () {
var previewUrl = '<?= base_url('bag/order/lot-seed/preview') ?>';
var overlay = document.getElementById('lot-preview-overlay');
var elLoading = document.getElementById('lot-preview-loading');
var elError = document.getElementById('lot-preview-error');
var elBody = document.getElementById('lot-preview-body');
var elSubmit = document.getElementById('lot-generate-submit');
var elBoIdx = document.getElementById('lot-generate-bo-idx');
var nf = function (n) { return new Intl.NumberFormat('ko-KR').format(n || 0); };
var esc = function (s) { return String(s == null ? '' : s).replace(/[&<>]/g, function (c) { return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' })[c]; }); };
function openModal() { overlay.classList.remove('hidden'); overlay.classList.add('flex'); }
function closeModal() { overlay.classList.add('hidden'); overlay.classList.remove('flex'); }
function showState(state) {
elLoading.classList.toggle('hidden', state !== 'loading');
elError.classList.toggle('hidden', state !== 'error');
elBody.classList.toggle('hidden', state !== 'body');
elSubmit.disabled = state !== 'body';
}
document.querySelectorAll('.lot-preview-btn').forEach(function (btn) {
btn.addEventListener('click', function () {
var boIdx = btn.getAttribute('data-bo-idx');
elBoIdx.value = boIdx;
openModal();
showState('loading');
fetch(previewUrl + '?bo_idx=' + encodeURIComponent(boIdx), { credentials: 'same-origin' })
.then(function (r) { return r.json(); })
.then(function (d) {
if (!d || !d.ok) {
elError.textContent = (d && d.error) || '내용을 불러오지 못했습니다.';
showState('error');
return;
}
document.getElementById('lp-lot').textContent = d.lot_no || '';
document.getElementById('lp-date').textContent = d.order_date || '';
document.getElementById('lp-company').textContent = d.company || '-';
document.getElementById('lp-version').textContent = 'v' + (d.version || 1);
document.getElementById('lp-hash').textContent = d.order_hash || '';
var rows = (d.items || []).map(function (it) {
return '<tr>'
+ '<td class="pl-2 font-mono">' + esc(it.bag_code) + '</td>'
+ '<td>' + esc(it.bag_name) + '</td>'
+ '<td class="text-right">' + nf(it.unit_price) + '</td>'
+ '<td class="text-right">' + nf(it.qty_box) + '</td>'
+ '<td class="text-right">' + nf(it.qty_pack) + '</td>'
+ '<td class="text-right">' + nf(it.qty_sheet) + '</td>'
+ '<td class="text-right pr-2">' + nf(it.amount) + '</td>'
+ '</tr>';
}).join('');
document.getElementById('lp-items').innerHTML = rows || '<tr><td colspan="7" class="text-center text-gray-400 py-4">품목이 없습니다.</td></tr>';
var t = d.totals || {};
document.getElementById('lp-line-count').textContent = nf(t.line_count);
document.getElementById('lp-sum-box').textContent = nf(t.qty_box);
document.getElementById('lp-sum-sheet').textContent = nf(t.qty_sheet);
document.getElementById('lp-sum-amount').textContent = nf(t.amount);
showState('body');
})
.catch(function () {
elError.textContent = '통신 오류가 발생했습니다.';
showState('error');
});
});
});
document.getElementById('lot-preview-close').addEventListener('click', closeModal);
document.getElementById('lot-preview-cancel').addEventListener('click', closeModal);
overlay.addEventListener('click', function (e) { if (e.target === overlay) closeModal(); });
})();
</script>