472 lines
22 KiB
PHP
472 lines
22 KiB
PHP
<!-- 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'; ?>
|