Files
sam-api/public/tenant/order/estimate_form.php

472 lines
22 KiB
PHP
Raw Normal View History

<!-- SAM RULES: include=../inc/header.php; base=/tenant; width=1280; js=jQuery+BS5 -->
<?php
$CURRENT_SECTION = 'order';
include '../inc/header.php';
?>
<div class="container" style="max-width:1280px; margin-top:18px;">
<!-- ========== 기본정보 입력 ========== -->
<div class="card mb-3">
<div class="card-header"><strong>기본정보 입력</strong></div>
<div class="card-body">
<form id="baseForm" class="row g-2 align-items-center">
<div class="col-sm-2">
<label class="form-label mb-1">접수일</label>
<input type="date" class="form-control" id="recvDate">
</div>
<div class="col-sm-1">
<label class="form-label mb-1">작성자</label>
<input type="text" class="form-control" id="writer" placeholder="작성자">
</div>
<div class="col-sm-2">
<label class="form-label mb-1">발주처</label>
<div class="input-group">
<input type="text" class="form-control" id="customer" placeholder="검색">
<button class="btn btn-outline-secondary" type="button" id="btnFindCust">🔍</button>
</div>
</div>
<div class="col-sm-1">
<label class="form-label mb-1">업체담당자</label>
<input type="text" class="form-control" id="custManager" placeholder="담당자명">
</div>
<div class="col-sm-2">
<label class="form-label mb-1">연락처</label>
<input type="text" class="form-control" id="phone" placeholder="연락처">
</div>
<div class="col-sm-4">
<label class="form-label mb-1">현장</label>
<input type="text" class="form-control" id="site" placeholder="현장명">
</div>
<div class="col-6">
<label class="form-label mb-1">메모</label>
<input type="text" class="form-control" id="memo" placeholder="메모입력">
</div>
<div class="col-6">
<label class="form-label mb-1">첨부파일 <span class="text-muted">(파일당 20MB, 100MB, 여러개 가능)</span></label>
<input type="file" class="form-control" id="files" multiple>
</div>
</form>
</div>
</div>
<!-- ========== 제품구성 설정 ========== -->
<div class="card mb-3" id="productCard">
<div class="card-header d-flex align-items-center justify-content-between">
<strong>제품구성 설정</strong>
<div class="d-flex gap-2">
<select id="modelPreset" class="form-select form-select-sm" style="width:220px;">
<option value="">모델 프리셋 선택(임시)</option>
<option value="KSS01">KSS01 (인정)</option>
<option value="KSS02">KSS02 (인정)</option>
</select>
<button class="btn btn-sm btn-outline-secondary" id="btnAddSet">구성추가</button>
<button class="btn btn-sm btn-primary" id="btnSaveSet">저장</button>
<button class="btn btn-sm btn-outline-primary" id="btnCalc">계산하기</button>
</div>
</div>
<!-- 기본 설정 (항상 위로 붙음 / 세트 생성 복사 원본) -->
<div class="sam-sticky p-3 border-bottom bg-white">
<div class="row g-2">
<div class="col-md-1">
<label class="form-label mb-1">층수</label>
<select id="g_floor" class="form-select">
<option>1</option><option>2</option><option>3</option><option>4</option>
</select>
</div>
<div class="col-md-1">
<label class="form-label mb-1">부호</label>
<input id="g_room" class="form-control" placeholder="예: 1205">
</div>
<div class="col-md-1">
<label class="form-label mb-1">본체종류</label>
<select id="g_body" class="form-select">
<option value="일반">실리카</option>
<option value="내화">내화</option>
</select>
</div>
<div class="col-md-1">
<label class="form-label mb-1">마감유형</label>
<select id="g_finish" class="form-select">
<option value="EGI">EGI</option>
<option value="SUS">SUS</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label mb-1">오픈사이즈</label>
<div class="input-group">
<input id="g_open_w" class="form-control" placeholder="가로(mm)" inputmode="numeric" value="2000">
<span class="input-group-text">×</span>
<input id="g_open_h" class="form-control" placeholder="세로(mm)" inputmode="numeric" value="1000">
</div>
</div>
<div class="col-md-2">
<label class="form-label mb-1">제작사이즈</label>
<div class="input-group">
<input id="g_make_w" class="form-control" placeholder="가로(mm)" inputmode="numeric" value="2160">
<span class="input-group-text">×</span>
<input id="g_make_h" class="form-control" placeholder="세로(mm)" inputmode="numeric" value="1350">
</div>
</div>
<div class="col-md-3">
<label class="form-label mb-1">제작치수</label>
<div class="row g-2">
<div class="col"><input id="g_mk_w" class="form-control" placeholder="가로" inputmode="numeric" value="160"></div>
<div class="col"><input id="g_mk_h" class="form-control" placeholder="높이" inputmode="numeric" value="350"></div>
<div class="col"><input id="g_mk_edge" class="form-control" placeholder="마구리" inputmode="numeric" value="50"></div>
</div>
</div>
</div>
</div>
<!-- 세트 컨테이너 -->
<div class="card-body" id="setsBox">
<!-- JS가 세트 카드를 여기 추가 -->
</div>
</div>
<!-- ========== 세부 항목 (계산 결과) ========== -->
<div class="card">
<div class="card-header"><strong>세부 항목</strong></div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sam table-hover align-middle m-0" id="detailTable">
<thead><tr>
<th style="width:60px;">#</th>
<th style="width:120px;">품목코드</th>
<th>품목명</th>
<th style="width:180px;">규격</th>
<th style="width:80px;">수량</th>
<th style="width:100px;">단위</th>
<th style="width:140px;">단가</th>
<th style="width:140px;">공급가액</th>
<th style="width:100px;">부가세</th>
</tr></thead>
<tbody></tbody>
<tfoot><tr>
<th colspan="7" class="text-end">합계</th>
<th id="sumAmt" class="text-end">0</th>
<th id="sumTax" class="text-end">0</th>
</tr></tfoot>
</table>
</div>
</div>
</div>
</div>
<style>
/* sticky bar */
.sam-sticky{ position: sticky; top: 70px; z-index: 30; }
/* 좌측/우측 리스트 */
.pick-col{border:1px solid #e5e9f2; border-radius:10px; overflow:hidden;}
.pick-head{background:#f7f9ff; padding:.6rem .9rem; font-weight:600;}
.pick-body{max-height:260px; overflow:auto;}
.pick-row{display:flex; align-items:center; gap:8px; padding:.5rem .75rem; border-top:1px solid #f1f3f8;}
.pick-row:hover{background:#fafcff;}
.chip{display:inline-block; padding:.1rem .45rem; border:1px solid #cbd6ee; border-radius:10rem; font-size:.8rem; color:#2c4a85; background:#edf3ff;}
/* 공용 테이블 톤 */
.table-sam.table > :not(caption) > * > * { vertical-align: middle; text-align: center; padding:.55rem .6rem; }
.table-sam thead th{ white-space:nowrap; }
.table-sam tbody tr:hover{ background:#f7f9ff; }
.table-sam thead{ background:#f0f4fb; }
</style>
<script>
(function($){
// ===== 샘플 마스터 데이터 =====
const MASTER = {
screen: [
{code:'SC-001', name:'스크린 베이스', unit:'EA', price:30000},
{code:'SC-002', name:'스크린 레일', unit:'M', price:15000},
{code:'SC-003', name:'실리카원단', unit:'㎡', price:12000},
],
steel: [
{code:'ST-101', name:'가이드레일(앞판재)', unit:'EA', price:26892},
{code:'ST-102', name:'보강판철', unit:'EA', price:21547},
]
};
// ===== 세트 카드 템플릿 =====
function setCardTpl(idx, presetLabel, base){
const id = `set${idx}`;
const v = base || {};
return `
<div class="border rounded-3 p-3 mb-3 set-card" id="${id}" data-index="${idx}">
<div class="d-flex align-items-center justify-content-between mb-3">
<div class="d-flex align-items-center gap-2">
<span class="badge bg-secondary-subtle text-dark border">세트 #${idx}</span>
<span class="badge ${presetLabel? 'bg-success' : 'bg-dark-subtle text-dark'}">${presetLabel||'비인정'}</span>
</div>
<button type="button" class="btn btn-sm btn-outline-danger btnDelSet">삭제</button>
</div>
<!-- 세트별 설정 (기본설정 복사본) -->
<div class="row g-2 mb-2">
<div class="col-md-1">
<label class="form-label mb-1">층수</label>
<select class="form-select s_floor">
${[1,2,3,4].map(n=>`<option ${v.floor==n?'selected':''}>${n}</option>`).join('')}
</select>
</div>
<div class="col-md-1">
<label class="form-label mb-1">부호(호수)</label>
<input class="form-control s_room" placeholder="예: 1205" value="${v.room||''}">
</div>
<div class="col-md-1">
<label class="form-label mb-1">본체종류</label>
<select class="form-select s_body">
<option ${v.body==='일반'?'selected':''}>일반</option>
<option ${v.body==='내화'?'selected':''}>내화</option>
</select>
</div>
<div class="col-md-1">
<label class="form-label mb-1">마감유형</label>
<select class="form-select s_finish">
<option ${v.finish==='EGI'?'selected':''} value="EGI">EGI</option>
<option ${v.finish==='SUS'?'selected':''} value="SUS">SUS</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label mb-1">오픈사이즈</label>
<div class="input-group">
<input class="form-control s_open_w" placeholder="가로(mm)" value="${v.open_w||''}">
<span class="input-group-text">×</span>
<input class="form-control s_open_h" placeholder="세로(mm)" value="${v.open_h||''}">
</div>
</div>
<div class="col-md-2">
<label class="form-label mb-1">제작사이즈</label>
<div class="input-group">
<input class="form-control s_make_w" placeholder="가로(mm)" value="${v.make_w||''}">
<span class="input-group-text">×</span>
<input class="form-control s_make_h" placeholder="세로(mm)" value="${v.make_h||''}">
</div>
</div>
<div class="col-md-3">
<label class="form-label mb-1">제작치수</label>
<div class="row g-2">
<div class="col"><input class="form-control s_mk_w" placeholder="가로" value="${v.mk_w||''}"></div>
<div class="col"><input class="form-control s_mk_h" placeholder="높이" value="${v.mk_h||''}"></div>
<div class="col"><input class="form-control s_mk_edge" placeholder="마구리" value="${v.mk_edge||''}"></div>
</div>
</div>
</div>
<!-- -->
<ul class="nav nav-tabs small mb-2">
<li class="nav-item"><a class="nav-link active" data-bs-toggle="tab" href="#${id}_scr">스크린</a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#${id}_steel">철재</a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#${id}_slat">슬랫</a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#${id}_motor">전동개폐기</a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#${id}_safe">연동 폐쇄기구</a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#${id}_bend">절곡품</a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#${id}_sub">부자재</a></li>
</ul>
<div class="tab-content">
${pickPane(id,'scr','선택 가능한 스크린 제품', MASTER.screen)}
${pickPane(id,'steel','선택 가능한 철재 제품', MASTER.steel)}
${emptyPane(id,'slat')}
${emptyPane(id,'motor')}
${emptyPane(id,'safe')}
${emptyPane(id,'bend')}
${emptyPane(id,'sub')}
</div>
</div>
`;
}
function pickPane(id, key, title, items){
return `
<div class="tab-pane fade ${key==='scr'?'show active':''}" id="${id}_${key}">
<div class="row g-3">
<div class="col-lg-6">
<div class="pick-col">
<div class="pick-head">${title}</div>
<div class="pick-body">
${items.map(it=>`
<div class="pick-row">
<div class="flex-grow-1">
<div><strong>${it.code}</strong> <span class="text-muted">· ${it.name}</span></div>
<div class="small text-muted">단위 ${it.unit} / 기준단가 ${num(it.price)}</div>
</div>
<button type="button" class="btn btn-sm btn-outline-primary btnPick"
data-code="${it.code}" data-name="${it.name}" data-unit="${it.unit}" data-price="${it.price}">선택</button>
</div>`).join('')}
</div>
</div>
</div>
<div class="col-lg-6">
<div class="pick-col">
<div class="pick-head">계산식(선택 결과)</div>
<div class="pick-body selectedBox"></div>
</div>
</div>
</div>
</div>
`;
}
function emptyPane(id,key){
return `<div class="tab-pane fade" id="${id}_${key}">
<div class="text-muted small p-3"> 탭은 나중에 마스터 연동 채워집니다.</div>
</div>`;
}
function selectedRowTpl(code,name,unit){
return `<div class="pick-row">
<span class="chip">${code}</span>
<div class="flex-grow-1">${name}</div>
<div style="width:70px;"><span class="badge bg-light text-dark w-100">${unit}</span></div>
<input type="number" class="form-control form-control-sm selQty" value="1" style="width:80px;">
<button class="btn btn-sm btn-outline-danger btnSelDel" type="button">삭제</button>
</div>`;
}
// ===== 상태 =====
let setSeq = 0;
// ===== 유틸 =====
function num(v){ return (+v).toLocaleString(); }
function readGlobal(){
return {
floor: $('#g_floor').val(),
room: $('#g_room').val().trim(),
body: $('#g_body').val(),
finish:$('#g_finish').val(),
open_w:$('#g_open_w').val(), open_h:$('#g_open_h').val(),
make_w:$('#g_make_w').val(), make_h:$('#g_make_h').val(),
mk_w: $('#g_mk_w').val(), mk_h: $('#g_mk_h').val(), mk_edge: $('#g_mk_edge').val()
};
}
function validSize(v){
const ok = (x)=> x!=='' && !isNaN(+x);
return ok(v.open_w)&&ok(v.open_h)&&ok(v.make_w)&&ok(v.make_h);
}
// ===== 세트 추가 =====
$('#btnAddSet').on('click', function(){
const g = readGlobal();
const preset = $('#modelPreset').val();
const label = preset ? preset : '비인정';
setSeq++;
$('#setsBox').append( setCardTpl(setSeq, label, g) );
});
// 세트 삭제
$(document).on('click', '.btnDelSet', function(){
$(this).closest('.set-card').remove();
});
// 품목 선택 → 오른쪽 박스 추가(중복 시 수량 +1)
$(document).on('click', '.btnPick', function(){
const $set = $(this).closest('.set-card');
const code = $(this).data('code');
const name = $(this).data('name');
const unit = $(this).data('unit');
const $box = $set.find('.tab-pane.active .selectedBox');
// 중복 찾아 수량 +1
const $exists = $box.find('.pick-row').filter(function(){
return $(this).find('.chip').text()===code;
});
if($exists.length){
const $qty = $exists.find('.selQty');
$qty.val( (+$qty.val()||0) + 1 );
}else{
$box.append( selectedRowTpl(code,name,unit) );
}
});
// 선택 삭제
$(document).on('click', '.btnSelDel', function(){
$(this).closest('.pick-row').remove();
});
// ===== 저장 (임시) =====
$('#btnSaveSet').on('click', function(){
alert('세트 저장(임시). 실제로는 /tenant/api/estimate/save_set.php 로 전송');
});
// ===== 계산하기 =====
$('#btnCalc').on('click', function(){
try{
const result = []; // [{code,name,unit,qty,price}]
let lineNo = 0;
// 모든 세트 순회
$('.set-card').each(function(){
const gset = {
floor: $('.s_floor',this).val(),
room: $('.s_room',this).val(),
body: $('.s_body',this).val(),
finish:$('.s_finish',this).val(),
open_w:$('.s_open_w',this).val(), open_h:$('.s_open_h',this).val(),
make_w:$('.s_make_w',this).val(), make_h:$('.s_make_h',this).val()
};
if(!validSize(gset)){
throw new Error('세트의 치수(오픈/제작)가 비었습니다.');
}
// 활성 탭들에서 선택 결과 취합
$(this).find('.selectedBox').each(function(){
$(this).find('.pick-row').each(function(){
const code = $(this).find('.chip').text();
const name = $(this).find('.flex-grow-1').text().trim();
const unit = $(this).find('.badge').text().trim();
const qty = +($(this).find('.selQty').val()||0);
if(!qty) return;
// 샘플 단가 소스: MASTER 에서 조회
const price = (MASTER.screen.concat(MASTER.steel).find(x=>x.code===code)?.price) || 0;
// 간단 산식(샘플): 제작 면적(m²) = (make_w * make_h)/1e6
// 단위가 ㎡이면 면적×단가, 그 외는 수량×단가
let amount = 0;
if(unit==='㎡'){
const area = (+gset.make_w||0) * (+gset.make_h||0) / 1_000_000;
amount = Math.round(area * price * qty);
}else{
amount = Math.round(price * qty);
}
result.push({line: ++lineNo, code, name, unit, qty, price, amount});
});
});
});
// 테이블 렌더
const $tb = $('#detailTable tbody').empty();
let sumAmt = 0, sumTax = 0;
result.forEach(r=>{
sumAmt += r.amount;
const tax = Math.round(r.amount * 0.1); // 부가세
sumTax += tax;
$tb.append(`<tr>
<td>${r.line}</td>
<td>${r.code}</td>
<td class="text-start">${r.name}</td>
<td>-</td>
<td>${r.qty}</td>
<td>${r.unit}</td>
<td class="text-end">${num(r.price)}</td>
<td class="text-end">${num(r.amount)}</td>
<td class="text-end">${num(tax)}</td>
</tr>`);
});
$('#sumAmt').text(num(sumAmt));
$('#sumTax').text(num(sumTax));
if(result.length===0) alert('선택된 품목이 없습니다.');
}catch(err){
console.error(err);
alert('계산 중 오류: '+ err.message);
}
});
})(jQuery);
</script>
<?php include '../inc/footer.php'; ?>