- 메뉴/화면/매뉴얼 명칭을 '발주파일 생성'으로 변경(메뉴 rename 마이그레이션 SQL 포함) - 발주파일(seed) 생성 전 '내용 확인' 미리보기 추가(암호화 전 발주 메타·품목 표시) · GET bag/order/lot-seed/preview 신설, 생성/미리보기 데이터 구성 공용화 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
262 lines
13 KiB
PHP
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">×</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 ({ '&': '&', '<': '<', '>': '>' })[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>
|