409 lines
20 KiB
PHP
409 lines
20 KiB
PHP
<?php
|
||
// SAM MODAL BODY (NO header/footer)
|
||
// Params: job_id, cust, order_no (POST/GET)
|
||
$jobId = $_POST['job_id'] ?? ($_GET['job_id'] ?? '');
|
||
$cust = $_POST['cust'] ?? ($_GET['cust'] ?? '');
|
||
$orderNo = $_POST['order_no'] ?? ($_GET['order_no'] ?? '');
|
||
?>
|
||
<div class="p-3">
|
||
|
||
<!-- ROW 1 : 기본정보(좌 2/3) + 우선 작업순위(우 1/3) -->
|
||
<div class="row g-3 mb-3">
|
||
<!-- 작업 기본정보 -->
|
||
<div class="col-lg-8">
|
||
<div class="border rounded p-3 h-100">
|
||
<div class="fw-bold mb-2">① 작업 기본정보</div>
|
||
<div class="row g-2">
|
||
<div class="col-6 col-md-4">
|
||
<label class="form-label mb-0 small text-muted">수주계약일</label>
|
||
<input class="form-control form-control-sm" value="자동입력" readonly>
|
||
</div>
|
||
<div class="col-6 col-md-4">
|
||
<label class="form-label mb-0 small text-muted">발주처</label>
|
||
<input class="form-control form-control-sm" value="<?= htmlspecialchars($cust) ?: '자동입력' ?>" readonly>
|
||
</div>
|
||
<div class="col-12 col-md-4">
|
||
<label class="form-label mb-0 small text-muted">현장명</label>
|
||
<input class="form-control form-control-sm" value="자동입력" readonly>
|
||
</div>
|
||
<div class="col-6 col-md-4">
|
||
<label class="form-label mb-0 small text-muted">출고요청일</label>
|
||
<input class="form-control form-control-sm" value="자동입력" readonly>
|
||
</div>
|
||
<div class="col-6 col-md-4">
|
||
<label class="form-label mb-0 small text-muted">배송방식</label>
|
||
<input class="form-control form-control-sm" value="자동입력" readonly>
|
||
</div>
|
||
<div class="col-6 col-md-4">
|
||
<label class="form-label mb-0 small text-muted">제품명</label>
|
||
<input class="form-control form-control-sm" value="자동입력" readonly>
|
||
</div>
|
||
<div class="col-12">
|
||
<label class="form-label mb-0 small text-muted">비고</label>
|
||
<input class="form-control form-control-sm" value="">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 우선 작업순위 -->
|
||
<div class="col-lg-4">
|
||
<div class="border rounded p-3 h-100">
|
||
<div class="fw-bold mb-2">우선 작업순위</div>
|
||
<div class="row g-2 align-items-center">
|
||
<div class="col-5 text-end small text-muted">현재순위</div>
|
||
<div class="col-7"><input type="number" class="form-control form-control-sm" value="0" readonly></div>
|
||
<div class="col-5 text-end small text-muted">설정순위</div>
|
||
<div class="col-7">
|
||
<select class="form-select form-select-sm" id="setPriority">
|
||
<?php for ($i = 1; $i <= 10; $i++) {
|
||
echo "<option>$i</option>";
|
||
} ?>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ROW 2 : 메인 자재(전체폭) + 일괄적용 버튼(이 박스 안) -->
|
||
<div class="border rounded p-3 mb-3">
|
||
<div class="d-flex align-items-center gap-2 mb-2">
|
||
<div class="fw-bold flex-grow-1">② 재고확인 및 자재투입</div>
|
||
<div class="d-flex gap-2">
|
||
<input type="text" class="form-control form-control-sm" id="bulkLot" placeholder="LOT 번호 입력/선택값" style="width:200px;">
|
||
<button type="button" class="btn btn-outline-secondary btn-sm" id="btnOpenLotForBulk">LOT 선택</button>
|
||
<button type="button" class="btn btn-primary btn-sm" id="btnApplyLotMain">로트번호 일괄적용</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-responsive" style="max-height:360px; overflow:auto;">
|
||
<table class="table table-sm align-middle m-0">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th style="width:42px;"><input type="checkbox" id="chkAll"></th>
|
||
<th style="width:70px;">일련</th>
|
||
<th style="width:60px;">층</th>
|
||
<th style="width:80px;">부호</th>
|
||
<th>품목명</th>
|
||
<th style="width:110px;">가로</th>
|
||
<th style="width:110px;">세로</th>
|
||
<th style="width:90px;">매수</th>
|
||
<th style="width:110px;">조인트바</th>
|
||
<th style="width:160px;">입고 LOT NO.</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="wiRows"><!-- JS 렌더 --></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ROW 3 : 좌(내화실/부자재, 다건) / 우(작업자 배치) -->
|
||
<div class="row g-3">
|
||
<!-- ③ 부자재 (고정 목록, 추가/삭제 없음) -->
|
||
<div class="col-lg-6">
|
||
<div class="border rounded p-3 h-100">
|
||
<div class="fw-bold mb-2">③ 부자재</div>
|
||
|
||
<div class="table-responsive" style="max-height:260px; overflow:auto;">
|
||
<table class="table table-sm align-middle m-0">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th>항목명</th>
|
||
<th style="width:160px;">LOT NO.</th>
|
||
<th style="width:90px;">수량</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="subRows"><!-- JS --></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 작업자 배치 -->
|
||
<div class="col-lg-6">
|
||
<div class="border rounded p-3 h-100">
|
||
<div class="fw-bold mb-2">④ 작업자 배치</div>
|
||
|
||
<div class="row g-2 align-items-center mb-2">
|
||
<div class="col-4 col-md-3 text-end small text-muted">부서 선택</div>
|
||
<div class="col-8 col-md-5">
|
||
<select id="deptSelect" class="form-select form-select-sm"></select>
|
||
</div>
|
||
<div class="col-12 col-md-4 mt-2 mt-md-0">
|
||
<input type="text" id="workerSearch" class="form-control form-control-sm" placeholder="작업자 검색">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row g-3">
|
||
<div class="col-md-7">
|
||
<div class="border rounded" style="height:220px; overflow:auto;">
|
||
<table class="table table-sm m-0 align-middle">
|
||
<thead class="table-light">
|
||
<tr><th style="width:42px;"></th><th>이름</th><th class="text-muted" style="width:120px;">직책</th></tr>
|
||
</thead>
|
||
<tbody id="workerList"><!-- JS --></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-5">
|
||
<div class="small text-muted mb-1">선택됨</div>
|
||
<div id="chosenWrap" class="d-flex flex-wrap gap-2"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 하단 버튼 -->
|
||
<div class="d-flex justify-content-end gap-2 mt-3">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">취소</button>
|
||
<button type="button" class="btn btn-primary" id="btnSaveWI">저장</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- LOT 선택 서브 모달 (메인/부자재에서 공용) -->
|
||
<div class="modal fade" id="lotSelectModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">로트번호 선택</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="table-responsive border rounded">
|
||
<table class="table table-hover align-middle m-0">
|
||
<thead class="table-light">
|
||
<tr><th style="width:160px;">로트번호</th><th>자재명</th><th style="width:100px;">재고</th><th style="width:80px;">선택</th></tr>
|
||
</thead>
|
||
<tbody id="lotRows"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<small class="text-muted">선택 즉시 대상 입력란에 반영됩니다.</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
.table th, .table td { vertical-align: middle; }
|
||
#wiRows .lot-input { min-width: 120px; }
|
||
#chosenWrap .badge { font-size:.85rem; }
|
||
</style>
|
||
|
||
<script>
|
||
(function($){
|
||
/* ===== 샘플 데이터 ===== */
|
||
const rows = [
|
||
{no:1, floor:1, sign:'FSS-01', item:'실리카', w:696, h:3050, qty:47, joint:50, lot:'', checked:false},
|
||
{no:2, floor:1, sign:'', item:'실리카', w:700, h:3100, qty:40, joint:45, lot:'', checked:false},
|
||
{no:3, floor:2, sign:'', item:'실리카', w:650, h:3000, qty:38, joint:42, lot:'', checked:false},
|
||
];
|
||
const lots = [
|
||
{lot:'250626-01', mat:'실리카원단-WY-SC780', stock:800},
|
||
{lot:'250617-01', mat:'실리카원단-WY-SC780', stock:5069},
|
||
{lot:'250513-02', mat:'실리카원단-WY-SC780', stock:4625},
|
||
{lot:'250411-05', mat:'실리카원단-WY-SC780', stock:738},
|
||
];
|
||
const DEPTS = {
|
||
'생산1팀': [
|
||
{id:'u01', name:'김동실', role:'반장'},
|
||
{id:'u02', name:'원준서', role:'사원'},
|
||
{id:'u03', name:'유영수', role:'사원'},
|
||
{id:'u04', name:'배천석', role:'사원'},
|
||
],
|
||
'생산2팀': [
|
||
{id:'u11', name:'박서준', role:'주임'},
|
||
{id:'u12', name:'최다연', role:'사원'},
|
||
{id:'u13', name:'한민수', role:'사원'},
|
||
]
|
||
};
|
||
|
||
/* ===== 렌더: 메인 자재 ===== */
|
||
function renderMain(){
|
||
const html = rows.map((r,i)=>`
|
||
<tr data-idx="${i}">
|
||
<td><input type="checkbox" class="row-check" ${r.checked?'checked':''}></td>
|
||
<td>${r.no}</td>
|
||
<td>${r.floor}</td>
|
||
<td>${r.sign||''}</td>
|
||
<td>${r.item}</td>
|
||
<td><input type="number" class="form-control form-control-sm wi-w" value="${r.w}"></td>
|
||
<td><input type="number" class="form-control form-control-sm wi-h" value="${r.h}"></td>
|
||
<td><input type="number" class="form-control form-control-sm wi-qty" value="${r.qty}"></td>
|
||
<td><input type="number" class="form-control form-control-sm wi-joint" value="${r.joint}"></td>
|
||
<td>
|
||
<div class="input-group input-group-sm">
|
||
<input type="text" class="form-control lot-input wi-lot" value="${r.lot}" placeholder="선택">
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
$('#wiRows').html(html);
|
||
}
|
||
|
||
/* ===== 렌더: LOT 목록 ===== */
|
||
function renderLots(){
|
||
$('#lotRows').html(
|
||
lots.map(l=>`
|
||
<tr>
|
||
<td>${l.lot}</td>
|
||
<td>${l.mat}</td>
|
||
<td>${l.stock.toLocaleString()}</td>
|
||
<td><button type="button" class="btn btn-sm btn-primary btn-choose-lot" data-lot="${l.lot}">선택</button></td>
|
||
</tr>`).join('')
|
||
);
|
||
}
|
||
|
||
/* ===== 렌더: 부자재(내화실) ===== */
|
||
// 부자재 고정 목록 (예: 제품별 프리셋)
|
||
let subItems = [
|
||
{ id: 's1', name: '내화실', lot: '', qty: 1 },
|
||
{ id: 's2', name: '실리콘(내열)', lot: '', qty: 2 }
|
||
];
|
||
|
||
function addSubRow(){ subItems.push({id:'s'+(subSeed++), name:'', lot:'', qty:1}); renderSub(); }
|
||
function renderSub(){
|
||
$('#subRows').html(
|
||
subItems.map(s => `
|
||
<tr data-sid="${s.id}">
|
||
<td><input type="text" class="form-control form-control-sm sub-name" value="${s.name}" readonly></td>
|
||
<td>
|
||
<input type="text" class="form-control form-control-sm sub-lot" value="${s.lot}" placeholder="LOT 선택">
|
||
</td>
|
||
<td><input type="number" class="form-control form-control-sm sub-qty" value="${s.qty}" min="1"></td>
|
||
</tr>
|
||
`).join('')
|
||
);
|
||
}
|
||
|
||
/* ===== 체크박스 전체/부분 ===== */
|
||
$(document).on('change', '#chkAll', function(){
|
||
const on = $(this).is(':checked'); rows.forEach(r=>r.checked=on); renderMain(); this.indeterminate=false;
|
||
});
|
||
$(document).on('change', '.row-check', function(){
|
||
const idx = +$(this).closest('tr').data('idx'); rows[idx].checked = $(this).is(':checked');
|
||
const total=rows.length, sel=rows.filter(r=>r.checked).length, all=document.getElementById('chkAll');
|
||
if(sel===0){ all.checked=false; all.indeterminate=false; }
|
||
else if(sel===total){ all.checked=true; all.indeterminate=false; }
|
||
else{ all.checked=false; all.indeterminate=true; }
|
||
});
|
||
|
||
/* ===== 값 반영 ===== */
|
||
$(document).on('input', '.wi-w, .wi-h, .wi-qty, .wi-joint, .wi-lot', function(){
|
||
const i = +$(this).closest('tr').data('idx');
|
||
rows[i].w = +$(this).closest('tr').find('.wi-w').val()||0;
|
||
rows[i].h = +$(this).closest('tr').find('.wi-h').val()||0;
|
||
rows[i].qty = +$(this).closest('tr').find('.wi-qty').val()||0;
|
||
rows[i].joint = +$(this).closest('tr').find('.wi-joint').val()||0;
|
||
rows[i].lot = $(this).closest('tr').find('.wi-lot').val().trim();
|
||
});
|
||
|
||
$(document).on('input', '.sub-name, .sub-lot, .sub-qty', function(){
|
||
const sid = $(this).closest('tr').data('sid');
|
||
const s = subItems.find(x=>x.id===sid);
|
||
s.name = $(this).closest('tr').find('.sub-name').val().trim();
|
||
s.lot = $(this).closest('tr').find('.sub-lot').val().trim();
|
||
s.qty = +($(this).closest('tr').find('.sub-qty').val())||1;
|
||
});
|
||
|
||
/* ===== LOT 모달 호출/선택 (메인/부자재 공용) ===== */
|
||
let lotTarget = {mode:null, rowIdx:null, sid:null}; // mode: 'main' | 'sub' | 'bulk'
|
||
function openLotModal(){ renderLots(); new bootstrap.Modal('#lotSelectModal').show(); }
|
||
// 부자재 LOT 입력 클릭 시 모달 오픈
|
||
$(document).on('focus click', '.sub-lot', function(){
|
||
lotTarget = { mode: 'sub', sid: $(this).closest('tr').data('sid') };
|
||
openLotModal();
|
||
});
|
||
|
||
$(document).on('click', '.btn-open-lot', function(){
|
||
lotTarget.mode = $(this).data('mode'); // main | sub
|
||
if(lotTarget.mode==='main') lotTarget.rowIdx = +$(this).closest('tr').data('idx');
|
||
if(lotTarget.mode==='sub') lotTarget.sid = $(this).closest('tr').data('sid');
|
||
openLotModal();
|
||
});
|
||
|
||
$('#btnOpenLotForBulk').on('click', function(){ lotTarget = {mode:'bulk'}; openLotModal(); });
|
||
|
||
$(document).on('click', '.btn-choose-lot', function(){
|
||
const lot = $(this).data('lot');
|
||
if(lotTarget.mode==='main' && lotTarget.rowIdx!=null){
|
||
rows[lotTarget.rowIdx].lot = lot; renderMain();
|
||
}else if(lotTarget.mode==='sub' && lotTarget.sid){
|
||
const s = subItems.find(x=>x.id===lotTarget.sid); if(s){ s.lot = lot; renderSub(); }
|
||
}else if(lotTarget.mode==='bulk'){
|
||
$('#bulkLot').val(lot);
|
||
}
|
||
const m = bootstrap.Modal.getInstance(document.getElementById('lotSelectModal')); m && m.hide();
|
||
});
|
||
|
||
// 메인 자재 일괄적용(체크된 행들만)
|
||
$('#btnApplyLotMain').on('click', function(){
|
||
const lot = $('#bulkLot').val().trim();
|
||
if(!lot) return alert('일괄 적용할 LOT 번호를 입력/선택하세요.');
|
||
const targets = rows.filter(r=>r.checked);
|
||
if(!targets.length) return alert('적용할 행(체크)을 선택하세요.');
|
||
rows.forEach(r=>{ if(r.checked) r.lot = lot; });
|
||
renderMain();
|
||
});
|
||
|
||
/* ===== 작업자 배치(부서 선택 → 체크) ===== */
|
||
let currentDept=null, chosen=new Map(); // id -> user
|
||
function fillDept(){ const $s=$('#deptSelect').empty(); Object.keys(DEPTS).forEach((k,i)=>$s.append(`<option ${i? '':'selected'}>${k}</option>`)); currentDept=$s.val(); }
|
||
function renderWorkers(){
|
||
const q=($('#workerSearch').val()||'').trim().toLowerCase();
|
||
const list=(DEPTS[currentDept]||[]).filter(u=>u.name.toLowerCase().includes(q)||u.role.toLowerCase().includes(q));
|
||
$('#workerList').html(list.map(u=>`
|
||
<tr>
|
||
<td><input type="checkbox" class="chk-worker" data-id="${u.id}" ${chosen.has(u.id)?'checked':''}></td>
|
||
<td>${u.name}</td><td class="text-muted">${u.role}</td>
|
||
</tr>`).join(''));
|
||
renderChosen();
|
||
}
|
||
function renderChosen(){
|
||
const $w=$('#chosenWrap').empty();
|
||
if(!chosen.size) return $w.append('<span class="text-muted small">선택된 작업자가 없습니다.</span>');
|
||
chosen.forEach(u=>$w.append(
|
||
`<span class="badge bg-secondary-subtle border text-body d-flex align-items-center gap-1">
|
||
${u.name}<span class="text-muted small">(${u.role})</span>
|
||
<button type="button" class="btn btn-sm btn-link p-0 ms-1 btn-pill-remove" data-id="${u.id}">×</button>
|
||
</span>`
|
||
));
|
||
}
|
||
$('#deptSelect').on('change', function(){ currentDept=this.value; renderWorkers(); });
|
||
$('#workerSearch').on('input', renderWorkers);
|
||
$(document).on('change','.chk-worker',function(){
|
||
const id=$(this).data('id'); const u=Object.values(DEPTS).flat().find(x=>x.id===id);
|
||
if($(this).is(':checked')) chosen.set(id,u); else chosen.delete(id); renderChosen();
|
||
});
|
||
$(document).on('click','.btn-pill-remove',function(){ chosen.delete($(this).data('id')); renderWorkers(); });
|
||
|
||
/* ===== 부자재 행 추가/삭제 ===== */
|
||
$('#btnAddSub').on('click', addSubRow);
|
||
$(document).on('click','.btn-del-sub',function(){
|
||
const sid=$(this).closest('tr').data('sid'); subItems=subItems.filter(x=>x.id!==sid); renderSub();
|
||
});
|
||
|
||
/* ===== 저장(모의) ===== */
|
||
$('#btnSaveWI').on('click', function(){
|
||
const payload = {
|
||
job_id: <?= json_encode($jobId) ?>,
|
||
order_no: <?= json_encode($orderNo) ?>,
|
||
priority: $('#setPriority').val(),
|
||
main_rows: rows,
|
||
sub_items: subItems,
|
||
workers: Array.from(chosen.values())
|
||
};
|
||
console.log('[WORK INSTRUCTION SAVE]', payload);
|
||
alert('저장(모의). 콘솔을 확인하세요.');
|
||
// 실제: $.post('/tenant/api/production/save_work_instruction.php', payload)
|
||
});
|
||
|
||
/* 초기 구동 */
|
||
renderMain(); renderSub(); fillDept(); renderWorkers();
|
||
|
||
})(jQuery);
|
||
</script>
|