Files
jongryangje/app/Views/admin/bag_order/create.php

444 lines
20 KiB
PHP
Raw Normal View History

<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>
<?php
$oldBagCodes = old('item_bag_code');
$oldQtyBoxes = old('item_qty_box');
$oldQtySheets = old('item_qty_sheet');
$oldBagCodes = is_array($oldBagCodes) ? $oldBagCodes : [];
$oldQtyBoxes = is_array($oldQtyBoxes) ? $oldQtyBoxes : [];
$oldQtySheets = is_array($oldQtySheets) ? $oldQtySheets : [];
$defaultOrderDate = old('bo_order_date', date('Y-m-d'));
$defaultOrderMonth = old('bo_order_month_ui', substr($defaultOrderDate, 0, 7));
$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' => '삭제'];
?>
<form action="<?= mgmt_url('bag-orders/store') ?>" method="POST" class="mt-2 space-y-2">
<?= csrf_field() ?>
<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>
<input id="bo_order_month_ui" name="bo_order_month_ui" type="month" value="<?= esc($defaultOrderMonth) ?>" class="border border-gray-300 rounded px-2 py-1" />
</div>
<div class="flex items-center gap-2">
<label for="bo_order_date" class="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" />
</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"><?= 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="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">발주 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_fee_rate" class="w-20 font-bold text-gray-700">수수료</label>
<input id="bo_fee_rate" name="bo_fee_rate" type="number" step="0.01" value="<?= esc(old('bo_fee_rate', '0')) ?>" class="border border-gray-300 rounded px-2 py-1 w-24 text-right" />
<span>%</span>
</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') === (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') === (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') === (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-28">수량(BOX)</th>
<th class="w-24">단가</th>
<th class="w-24">환산수량</th>
<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>
<th class="text-right pr-2" id="sum-amount">0</th>
</tr>
</tfoot>
</table>
</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">발주</button>
<a href="<?= mgmt_url('bag-orders') ?>" 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>
<p class="text-xs text-gray-600 px-2 py-1">아래 목록에서 봉투를 선택하면 발주 품목에 추가됩니다. (개수 제한 없음)</p>
<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="7" class="text-center text-gray-400 py-4">표시할 봉투 기준 데이터가 없습니다.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</section>
</form>
<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 monthInput = document.getElementById('bo_order_month_ui');
const orderDateInput = document.getElementById('bo_order_date');
const orderForm = document.querySelector('form[action*="bag-orders/store"]');
const selectedItems = new Map();
let activeCode = null;
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 syncMonthFromDate = () => {
if (!orderDateInput || !monthInput || !orderDateInput.value) return;
monthInput.value = orderDateInput.value.substring(0, 7);
};
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}`;
};
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 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 unitPrice = Number(meta.orderPrice || 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 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 (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);
};
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="7" class="text-center text-gray-400 py-4">아래 "발주 등록 종류"에서 봉투를 선택해 주세요.</td></tr>';
setActiveRow(null);
updateTotals();
updateReferenceSelectionUi();
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;
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>
<td class="text-right pr-2 item-unit-price">0</td>
<td class="text-right pr-2 item-qty-sheet">0</td>
<td class="text-right pr-2 item-amount">0</td>
</tr>
`;
}).join('');
if (!activeCode || !selectedItems.has(activeCode)) {
activeCode = codes[0];
}
setActiveRow(activeCode);
updateTotals();
updateReferenceSelectionUi();
};
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');
if (!qtyInput) return;
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();
});
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 (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();
renderSelectedRows();
})();
</script>