Files
sam-api/public/tenant/order/estimate_form.php
hskwon cc206fdbed style: Laravel Pint 코드 포맷팅 적용
- PSR-12 스타일 가이드 준수
- 302개 파일 스타일 이슈 자동 수정
- 코드 로직 변경 없음 (포맷팅만)
2025-11-06 17:45:49 +09:00

472 lines
22 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- 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'; ?>