feat: [ai-quotation] 제조 견적서 자동 생성 기능 추가
- AI 2단계 분석: 고객 인터뷰 → 요구사항 추출 → 견적 산출 - 모델 확장: AiQuotation(모드/견적번호), AiQuotationItem(규격/단가/금액) - AiQuotePriceTable 모델 신규 생성 - Create 페이지: 모듈/제조 모드 탭, 제품 카테고리, 고객 정보 입력 - Show 페이지: 제조 모드 분기 렌더링 (품목/금액/고객정보) - Edit 페이지: 품목 인라인 편집, 할인/부가세/조건 입력 - Document: 한국 표준 제조업 견적서 양식 템플릿 - Controller/Route: update 엔드포인트, edit 라우트 추가
This commit is contained in:
@@ -18,10 +18,68 @@
|
||||
<div class="bg-white rounded-lg shadow-sm">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<h2 class="text-lg font-semibold text-gray-800">인터뷰 내용 입력</h2>
|
||||
<p class="text-sm text-gray-500 mt-1">고객사 인터뷰 내용을 입력하면 AI가 업무를 분석하고 맞춤형 견적서를 자동 생성합니다.</p>
|
||||
<p class="text-sm text-gray-500 mt-1">고객사 인터뷰 내용을 입력하면 AI가 분석하고 맞춤형 견적서를 자동 생성합니다.</p>
|
||||
</div>
|
||||
|
||||
<form id="quotationForm" class="p-6 space-y-6">
|
||||
<!-- 견적 모드 선택 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">견적 모드</label>
|
||||
<div class="flex gap-3">
|
||||
<label id="modeModule" class="flex items-center gap-2 px-4 py-2.5 border border-purple-500 bg-purple-50 text-purple-700 rounded-lg cursor-pointer transition">
|
||||
<input type="radio" name="quote_mode" value="module" checked class="text-purple-600" onchange="updateModeUI()">
|
||||
<i class="ri-apps-line"></i> 모듈 추천
|
||||
</label>
|
||||
<label id="modeManufacture" class="flex items-center gap-2 px-4 py-2.5 border border-gray-300 text-gray-600 rounded-lg cursor-pointer transition">
|
||||
<input type="radio" name="quote_mode" value="manufacture" class="text-blue-600" onchange="updateModeUI()">
|
||||
<i class="ri-building-4-line"></i> 제조 견적
|
||||
<span class="text-xs bg-blue-100 text-blue-600 px-1.5 py-0.5 rounded">NEW</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 제조 견적 전용: 제품 카테고리 -->
|
||||
<div id="manufactureSections" class="hidden space-y-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">제품 카테고리 <span class="text-red-500">*</span></label>
|
||||
<div class="flex gap-3">
|
||||
<label class="flex items-center gap-2 px-4 py-2.5 border rounded-lg cursor-pointer transition hover:bg-blue-50" id="catScreen">
|
||||
<input type="radio" name="product_category" value="SCREEN" checked class="text-blue-600" onchange="updateCategoryUI()">
|
||||
<span class="font-medium">방화스크린</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-4 py-2.5 border rounded-lg cursor-pointer transition hover:bg-orange-50" id="catSteel">
|
||||
<input type="radio" name="product_category" value="STEEL" class="text-orange-600" onchange="updateCategoryUI()">
|
||||
<span class="font-medium">철재</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 고객 정보 (선택) -->
|
||||
<div class="bg-blue-50 rounded-lg p-4">
|
||||
<h3 class="text-sm font-semibold text-blue-700 mb-3 flex items-center gap-1">
|
||||
<i class="ri-user-line"></i> 고객 정보 (선택 — AI가 인터뷰에서 자동 추출합니다)
|
||||
</h3>
|
||||
<div class="grid gap-3" style="grid-template-columns: repeat(2, 1fr);">
|
||||
<div>
|
||||
<label class="block text-xs text-gray-600 mb-1">회사명</label>
|
||||
<input type="text" name="client_company" placeholder="(주)한빛건설" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-600 mb-1">담당자</label>
|
||||
<input type="text" name="client_contact" placeholder="김철수 과장" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-600 mb-1">연락처</label>
|
||||
<input type="text" name="client_phone" placeholder="010-1234-5678" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-600 mb-1">이메일</label>
|
||||
<input type="email" name="client_email" placeholder="cs.kim@hanbit.co.kr" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 제목 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">견적 제목 <span class="text-red-500">*</span></label>
|
||||
@@ -101,6 +159,45 @@ class="px-6 py-2.5 bg-purple-600 text-white rounded-lg hover:bg-purple-700 trans
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
function getQuoteMode() {
|
||||
return document.querySelector('input[name="quote_mode"]:checked')?.value || 'module';
|
||||
}
|
||||
|
||||
function updateModeUI() {
|
||||
const mode = getQuoteMode();
|
||||
const modeModule = document.getElementById('modeModule');
|
||||
const modeManufacture = document.getElementById('modeManufacture');
|
||||
const mfgSections = document.getElementById('manufactureSections');
|
||||
const titleInput = document.getElementById('inputTitle');
|
||||
|
||||
if (mode === 'manufacture') {
|
||||
modeModule.className = 'flex items-center gap-2 px-4 py-2.5 border border-gray-300 text-gray-600 rounded-lg cursor-pointer transition';
|
||||
modeManufacture.className = 'flex items-center gap-2 px-4 py-2.5 border border-blue-500 bg-blue-50 text-blue-700 rounded-lg cursor-pointer transition';
|
||||
mfgSections.classList.remove('hidden');
|
||||
titleInput.placeholder = '예: (주)한빛건설 오피스빌딩 방화스크린';
|
||||
} else {
|
||||
modeModule.className = 'flex items-center gap-2 px-4 py-2.5 border border-purple-500 bg-purple-50 text-purple-700 rounded-lg cursor-pointer transition';
|
||||
modeManufacture.className = 'flex items-center gap-2 px-4 py-2.5 border border-gray-300 text-gray-600 rounded-lg cursor-pointer transition';
|
||||
mfgSections.classList.add('hidden');
|
||||
titleInput.placeholder = '예: (주)대한기계 ERP 도입 견적';
|
||||
}
|
||||
|
||||
updateCategoryUI();
|
||||
}
|
||||
|
||||
function updateCategoryUI() {
|
||||
const cat = document.querySelector('input[name="product_category"]:checked')?.value || 'SCREEN';
|
||||
const catScreen = document.getElementById('catScreen');
|
||||
const catSteel = document.getElementById('catSteel');
|
||||
|
||||
catScreen.classList.toggle('border-blue-500', cat === 'SCREEN');
|
||||
catScreen.classList.toggle('bg-blue-50', cat === 'SCREEN');
|
||||
catScreen.classList.toggle('border-gray-300', cat !== 'SCREEN');
|
||||
catSteel.classList.toggle('border-orange-500', cat === 'STEEL');
|
||||
catSteel.classList.toggle('bg-orange-50', cat === 'STEEL');
|
||||
catSteel.classList.toggle('border-gray-300', cat !== 'STEEL');
|
||||
}
|
||||
|
||||
function updateProviderUI() {
|
||||
const gemini = document.getElementById('providerGemini');
|
||||
const claude = document.getElementById('providerClaude');
|
||||
@@ -155,10 +252,8 @@ function updateProviderUI() {
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.data?.id) {
|
||||
// 성공 — 상세 페이지로 이동
|
||||
window.location.href = `{{ url('/rd/ai-quotation') }}/${result.data.id}`;
|
||||
} else {
|
||||
// 실패
|
||||
alert(result.message || 'AI 분석에 실패했습니다.');
|
||||
if (result.data?.id) {
|
||||
window.location.href = `{{ url('/rd/ai-quotation') }}/${result.data.id}`;
|
||||
@@ -175,8 +270,44 @@ function updateProviderUI() {
|
||||
});
|
||||
|
||||
function fillSampleInterview() {
|
||||
document.getElementById('inputTitle').value = '(주)대한기계 ERP 도입 견적';
|
||||
document.getElementById('inputText').value = `Q: 현재 회사 규모와 업종을 알려주세요.
|
||||
const mode = getQuoteMode();
|
||||
|
||||
if (mode === 'manufacture') {
|
||||
document.getElementById('inputTitle').value = '(주)한빛건설 오피스빌딩 방화스크린';
|
||||
document.getElementById('inputText').value = `Q: 어떤 건물인가요?
|
||||
A: 지하1층 지상5층 오피스빌딩입니다. 신축 건물이에요. 인천 남동구에 있습니다.
|
||||
|
||||
Q: 어떤 제품이 필요하세요?
|
||||
A: 지하1층 주차장 진입부에 방화스크린 2개소, 1층 로비에 3개소 필요합니다.
|
||||
|
||||
Q: 규격을 알려주세요.
|
||||
A: 지하1층은 A구역 3000×2500, B구역 4000×3000이고,
|
||||
1층은 C구역 2500×2000 2개소, D구역 5000×3500 1개소입니다.
|
||||
|
||||
Q: 가이드레일과 모터는?
|
||||
A: 전부 벽부형이고 단상 전원입니다. 가이드레일은 일반형으로 해주세요.
|
||||
|
||||
Q: 설치 조건은?
|
||||
A: 접근성 양호하고요. 층고는 지하 3m, 1층 2.8m입니다.
|
||||
|
||||
Q: 특별히 요청사항이 있으신가요?
|
||||
A: 1층 D구역은 개구부가 넓어서 분할 설치가 필요한지 확인 부탁드립니다.
|
||||
|
||||
Q: 회사 정보를 알려주세요.
|
||||
A: (주)한빛건설, 김철수 과장, 010-1234-5678, cs.kim@hanbit.co.kr`;
|
||||
|
||||
// 고객 정보 자동 입력
|
||||
const clientCompany = document.querySelector('input[name="client_company"]');
|
||||
const clientContact = document.querySelector('input[name="client_contact"]');
|
||||
const clientPhone = document.querySelector('input[name="client_phone"]');
|
||||
const clientEmail = document.querySelector('input[name="client_email"]');
|
||||
if (clientCompany) clientCompany.value = '(주)한빛건설';
|
||||
if (clientContact) clientContact.value = '김철수 과장';
|
||||
if (clientPhone) clientPhone.value = '010-1234-5678';
|
||||
if (clientEmail) clientEmail.value = 'cs.kim@hanbit.co.kr';
|
||||
} else {
|
||||
document.getElementById('inputTitle').value = '(주)대한기계 ERP 도입 견적';
|
||||
document.getElementById('inputText').value = `Q: 현재 회사 규모와 업종을 알려주세요.
|
||||
A: 저희는 기계부품 제조업체입니다. 직원이 45명 정도 되고, 공장 1곳에서 CNC 가공이랑 프레스 작업을 주로 하고 있어요. 사무직이 12명, 생산직이 33명입니다.
|
||||
|
||||
Q: 현재 업무 관리는 어떻게 하고 계세요?
|
||||
@@ -188,20 +319,13 @@ function fillSampleInterview() {
|
||||
Q: 생산 현장은요?
|
||||
A: 작업일보를 종이에 써서 제출해요. 작업지시서도 종이로 출력해서 현장에 붙여놓고요. 불량이 나면 어디서 발생했는지 추적이 잘 안 돼요. LOT 관리를 하고 싶은데 현재는 수기로 하다 보니 한계가 있어요. 납기 관리도 엑셀인데, 긴급 주문 들어오면 일정 조정하느라 난리예요.
|
||||
|
||||
Q: 자재/재고 관리는요?
|
||||
A: 재고는 한 달에 한 번 실사를 해봐야 알 수 있어요. 원자재가 얼마나 남았는지 실시간으로 파악이 안 되니까, 급하면 긴급 발주를 넣고 비싼 값에 사오는 경우도 많아요. 발주서도 엑셀로 만들어서 팩스로 보내요.
|
||||
|
||||
Q: 품질 관리 쪽은 어떤 상황인가요?
|
||||
A: 수입검사는 하고 있긴 한데 검사 기록을 종이 대장에 적어요. 고객사에서 검사성적서 요청하면 수작업으로 만들어야 해서 시간이 오래 걸려요. ISO 인증 심사 때마다 서류 준비하느라 야근해요.
|
||||
|
||||
Q: 회계/재무 쪽은요?
|
||||
A: 세금계산서는 홈택스에서 발행하고 있어요. 미수금 관리가 안 돼서 어떤 거래처가 얼마 미지급인지 파악하려면 한참 뒤져야 해요. 매출 현황도 월말에 정리하는 식이라 실시간 파악이 불가능해요.
|
||||
|
||||
Q: 가장 시급하게 개선하고 싶은 부분은?
|
||||
A: 일단 생산 현황을 실시간으로 볼 수 있었으면 좋겠고, 재고 관리가 급해요. 그리고 직원 관리랑 급여 계산을 자동화하고 싶어요. 영업 쪽도 견적서 관리가 너무 안 되니까 개선이 필요해요.`;
|
||||
}
|
||||
}
|
||||
|
||||
// 초기 Provider UI
|
||||
// 초기 UI
|
||||
updateProviderUI();
|
||||
updateModeUI();
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
{{-- 제조 견적서 클래식 템플릿 — 한국 표준 제조업 견적서 양식 --}}
|
||||
<style>
|
||||
.mfg-classic .doc-table { width: 100%; border-collapse: collapse; }
|
||||
.mfg-classic .doc-table th, .mfg-classic .doc-table td { border: 1px solid #333; padding: 6px 10px; font-size: 13px; }
|
||||
.mfg-classic .doc-table th { background-color: #f3f4f6; font-weight: 600; }
|
||||
.mfg-classic .seal-box {
|
||||
display: inline-block; width: 60px; height: 60px; border: 2px solid #dc2626;
|
||||
border-radius: 50%; text-align: center; line-height: 56px; color: #dc2626;
|
||||
font-weight: 700; font-size: 16px;
|
||||
}
|
||||
@media print {
|
||||
.mfg-classic { padding: 10mm 15mm !important; box-shadow: none !important; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@php
|
||||
$materialItems = $quotation->items->where('item_category', 'material');
|
||||
$laborItems = $quotation->items->where('item_category', 'labor');
|
||||
$installItems = $quotation->items->where('item_category', 'install');
|
||||
$otherItems = $quotation->items->whereNotIn('item_category', ['material', 'labor', 'install']);
|
||||
$allItems = $materialItems->merge($laborItems)->merge($installItems)->merge($otherItems);
|
||||
|
||||
$materialTotal = $materialItems->sum('total_price');
|
||||
$laborTotal = $laborItems->sum('total_price');
|
||||
$installTotal = $installItems->sum('total_price');
|
||||
@endphp
|
||||
|
||||
<div class="mfg-classic document-page max-w-[210mm] mx-auto my-8 bg-white shadow-lg" style="padding: 15mm 20mm;">
|
||||
|
||||
{{-- 제목 --}}
|
||||
<h1 class="text-center text-3xl font-bold tracking-[0.5em] mb-8 pb-4 border-b-2 border-gray-800">
|
||||
견 적 서
|
||||
</h1>
|
||||
|
||||
{{-- 견적 정보 --}}
|
||||
<div class="flex justify-between mb-6 text-sm">
|
||||
<div>
|
||||
<p><span class="font-semibold">견적번호:</span> {{ $quotationNo }}</p>
|
||||
<p><span class="font-semibold">유효기간:</span> {{ $validUntil }}</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p><span class="font-semibold">견적일자:</span> {{ $quotation->created_at->format('Y년 m월 d일') }}</p>
|
||||
<p><span class="font-semibold">제품구분:</span> {{ $quotation->product_category === 'STEEL' ? '철재' : '방화스크린' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 수신 / 공급자 --}}
|
||||
<table class="doc-table mb-6">
|
||||
<colgroup>
|
||||
<col style="width: 8%;"><col style="width: 42%;"><col style="width: 8%;"><col style="width: 42%;">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2" class="text-center" style="background-color: #eff6ff;">수 신</th>
|
||||
<th colspan="2" class="text-center" style="background-color: #f0fdf4;">공 급 자</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>상 호</th><td>{{ $client['company'] ?? $quotation->title }}</td>
|
||||
<th>상 호</th><td>(주)코드브릿지엑스</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>담당자</th><td>{{ $client['contact'] ?? '-' }}</td>
|
||||
<th>대 표</th><td>이의찬</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>연락처</th><td>{{ $client['phone'] ?? '-' }}</td>
|
||||
<th>주 소</th><td>인천 남동구 남동대로 215번길 30</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>이메일</th><td>{{ $client['email'] ?? '-' }}</td>
|
||||
<th>연락처</th><td>032-123-4567</td>
|
||||
</tr>
|
||||
@if(!empty($project['name']))
|
||||
<tr>
|
||||
<th>현장명</th><td colspan="3">{{ $project['name'] }}{{ !empty($project['location']) ? ' ('.$project['location'].')' : '' }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{-- 합계 --}}
|
||||
<div class="mb-6 p-4 border-2 border-gray-800 text-center">
|
||||
<p class="text-sm mb-2">아래와 같이 견적합니다.</p>
|
||||
<p class="text-xl font-bold">
|
||||
합계금액: 금 {{ $subtotalKorean }}원정
|
||||
<span class="text-base font-normal">(₩{{ number_format($subtotal) }})</span>
|
||||
</p>
|
||||
<p class="text-xs text-gray-600 mt-1">※ 부가가치세 별도</p>
|
||||
</div>
|
||||
|
||||
{{-- 품목 테이블 --}}
|
||||
<table class="doc-table mb-6">
|
||||
<colgroup>
|
||||
<col style="width: 5%;"><col style="width: 10%;"><col style="width: 20%;">
|
||||
<col style="width: 15%;"><col style="width: 7%;"><col style="width: 7%;">
|
||||
<col style="width: 18%;"><col style="width: 18%;">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">No</th>
|
||||
<th class="text-center">위치</th>
|
||||
<th class="text-center">품 목</th>
|
||||
<th class="text-center">규 격</th>
|
||||
<th class="text-center">단위</th>
|
||||
<th class="text-center">수량</th>
|
||||
<th class="text-center">단가 (원)</th>
|
||||
<th class="text-center">금액 (원)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@php $no = 1; @endphp
|
||||
|
||||
{{-- 재료비 --}}
|
||||
@if($materialItems->isNotEmpty())
|
||||
<tr style="background-color: #eff6ff;">
|
||||
<td colspan="8" class="text-sm font-semibold" style="padding: 4px 10px;">[ 재료비 ]</td>
|
||||
</tr>
|
||||
@foreach($materialItems as $item)
|
||||
<tr>
|
||||
<td class="text-center">{{ $no++ }}</td>
|
||||
<td class="text-center text-xs">{{ $item->floor_code }}</td>
|
||||
<td>{{ $item->module_name }}</td>
|
||||
<td class="text-center text-xs">{{ $item->specification }}</td>
|
||||
<td class="text-center">{{ $item->unit }}</td>
|
||||
<td class="text-center">{{ number_format((float)$item->quantity, 0) }}</td>
|
||||
<td class="text-right">{{ number_format((float)$item->unit_price) }}</td>
|
||||
<td class="text-right">{{ number_format((float)$item->total_price) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
{{-- 노무비 --}}
|
||||
@if($laborItems->isNotEmpty())
|
||||
<tr style="background-color: #f0fdf4;">
|
||||
<td colspan="8" class="text-sm font-semibold" style="padding: 4px 10px;">[ 노무비 ]</td>
|
||||
</tr>
|
||||
@foreach($laborItems as $item)
|
||||
<tr>
|
||||
<td class="text-center">{{ $no++ }}</td>
|
||||
<td class="text-center text-xs">{{ $item->floor_code }}</td>
|
||||
<td>{{ $item->module_name }}</td>
|
||||
<td class="text-center text-xs">{{ $item->specification }}</td>
|
||||
<td class="text-center">{{ $item->unit }}</td>
|
||||
<td class="text-center">{{ number_format((float)$item->quantity, 0) }}</td>
|
||||
<td class="text-right">{{ number_format((float)$item->unit_price) }}</td>
|
||||
<td class="text-right">{{ number_format((float)$item->total_price) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
{{-- 설치비 --}}
|
||||
@if($installItems->isNotEmpty())
|
||||
<tr style="background-color: #fff7ed;">
|
||||
<td colspan="8" class="text-sm font-semibold" style="padding: 4px 10px;">[ 설치비 ]</td>
|
||||
</tr>
|
||||
@foreach($installItems as $item)
|
||||
<tr>
|
||||
<td class="text-center">{{ $no++ }}</td>
|
||||
<td class="text-center text-xs">{{ $item->floor_code }}</td>
|
||||
<td>{{ $item->module_name }}</td>
|
||||
<td class="text-center text-xs">{{ $item->specification }}</td>
|
||||
<td class="text-center">{{ $item->unit }}</td>
|
||||
<td class="text-center">{{ number_format((float)$item->quantity, 0) }}</td>
|
||||
<td class="text-right">{{ number_format((float)$item->unit_price) }}</td>
|
||||
<td class="text-right">{{ number_format((float)$item->total_price) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
{{-- 기타 --}}
|
||||
@foreach($otherItems as $item)
|
||||
<tr>
|
||||
<td class="text-center">{{ $no++ }}</td>
|
||||
<td class="text-center text-xs">{{ $item->floor_code }}</td>
|
||||
<td>{{ $item->module_name }}</td>
|
||||
<td class="text-center text-xs">{{ $item->specification }}</td>
|
||||
<td class="text-center">{{ $item->unit }}</td>
|
||||
<td class="text-center">{{ number_format((float)$item->quantity, 0) }}</td>
|
||||
<td class="text-right">{{ number_format((float)$item->unit_price) }}</td>
|
||||
<td class="text-right">{{ number_format((float)$item->total_price) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
<tfoot>
|
||||
{{-- 카테고리별 소계 --}}
|
||||
@if($materialItems->isNotEmpty())
|
||||
<tr>
|
||||
<th colspan="7" class="text-right text-xs">재료비 소계</th>
|
||||
<td class="text-right font-semibold">{{ number_format((int)$materialTotal) }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
@if($laborItems->isNotEmpty())
|
||||
<tr>
|
||||
<th colspan="7" class="text-right text-xs">노무비 소계</th>
|
||||
<td class="text-right font-semibold">{{ number_format((int)$laborTotal) }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
@if($installItems->isNotEmpty())
|
||||
<tr>
|
||||
<th colspan="7" class="text-right text-xs">설치비 소계</th>
|
||||
<td class="text-right font-semibold">{{ number_format((int)$installTotal) }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
<tr>
|
||||
<th colspan="7" class="text-right">소 계</th>
|
||||
<td class="text-right font-bold">{{ number_format($subtotal) }}</td>
|
||||
</tr>
|
||||
@if($discountAmount > 0)
|
||||
<tr>
|
||||
<th colspan="7" class="text-right text-red-600">할인 ({{ $discountRate }}%)</th>
|
||||
<td class="text-right text-red-600">-{{ number_format($discountAmount) }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
<tr>
|
||||
<th colspan="7" class="text-right">부가세 (10%)</th>
|
||||
<td class="text-right">{{ number_format($vatAmount) }}</td>
|
||||
</tr>
|
||||
<tr style="background-color: #f3f4f6;">
|
||||
<th colspan="7" class="text-right text-base">합 계</th>
|
||||
<td class="text-right font-bold text-base">{{ number_format($finalAmount) }}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
{{-- 비고 / 조건 --}}
|
||||
<div class="mb-8">
|
||||
<table class="doc-table">
|
||||
<thead><tr><th class="text-left">비 고</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="text-sm leading-relaxed" style="padding: 12px 16px;">
|
||||
<ol class="list-decimal list-inside space-y-1">
|
||||
<li>상기 금액은 부가가치세 별도입니다.</li>
|
||||
<li>결제 조건: {{ $terms['payment'] ?? '계약 시 50%, 설치 완료 후 50%' }}</li>
|
||||
<li>납기 조건: {{ $terms['delivery'] ?? '계약 후 4주 이내' }}</li>
|
||||
<li>본 견적서의 유효기간: {{ $validUntil }}까지</li>
|
||||
<li>설치 현장 여건에 따라 규격 및 금액이 변경될 수 있습니다.</li>
|
||||
<li>세부 사항은 별도 협의를 통해 조정될 수 있습니다.</li>
|
||||
</ol>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- 서명 --}}
|
||||
<div class="flex justify-end items-center gap-6 mt-12">
|
||||
<div class="text-right">
|
||||
<p class="text-lg font-bold mb-1">(주)코드브릿지엑스</p>
|
||||
<p class="text-sm text-gray-600">대표이사 이 의 찬</p>
|
||||
</div>
|
||||
<div class="seal-box">(인)</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -30,72 +30,63 @@
|
||||
|
||||
@section('content')
|
||||
@php
|
||||
// 견적번호
|
||||
$quotationNo = 'AQ-' . $quotation->created_at->format('Y') . '-' . str_pad($quotation->id, 3, '0', STR_PAD_LEFT);
|
||||
|
||||
// 회사 분석 정보
|
||||
$company = $quotation->analysis_result['company_analysis'] ?? [];
|
||||
|
||||
// 구현 계획
|
||||
$plan = $quotation->quotation_result['implementation_plan'] ?? [];
|
||||
$estimatedMonths = $plan['estimated_months'] ?? null;
|
||||
|
||||
// 금액 계산
|
||||
$devSubtotal = (int) $quotation->total_dev_cost;
|
||||
$monthlySubtotal = (int) $quotation->total_monthly_fee;
|
||||
$devVat = (int) round($devSubtotal * 0.1);
|
||||
$monthlyVat = (int) round($monthlySubtotal * 0.1);
|
||||
$devTotal = $devSubtotal + $devVat;
|
||||
$monthlyTotal = $monthlySubtotal + $monthlyVat;
|
||||
|
||||
// 한글 금액 변환
|
||||
// 한글 금액 변환 함수
|
||||
if (!function_exists('numberToKorean')) {
|
||||
function numberToKorean(int $number): string {
|
||||
if ($number === 0) return '영';
|
||||
$units = ['', '만', '억', '조'];
|
||||
$digits = ['', '일', '이', '삼', '사', '오', '육', '칠', '팔', '구'];
|
||||
$subUnits = ['', '십', '백', '천'];
|
||||
|
||||
$result = '';
|
||||
$unitIndex = 0;
|
||||
while ($number > 0) {
|
||||
$chunk = $number % 10000;
|
||||
if ($chunk > 0) {
|
||||
$chunkStr = '';
|
||||
$subIndex = 0;
|
||||
$temp = $chunk;
|
||||
while ($temp > 0) {
|
||||
$digit = $temp % 10;
|
||||
if ($digit > 0) {
|
||||
$prefix = ($digit === 1 && $subIndex > 0) ? '' : $digits[$digit];
|
||||
$chunkStr = $prefix . $subUnits[$subIndex] . $chunkStr;
|
||||
}
|
||||
$temp = (int)($temp / 10);
|
||||
$subIndex++;
|
||||
}
|
||||
$result = $chunkStr . $units[$unitIndex] . $result;
|
||||
}
|
||||
$number = (int)($number / 10000);
|
||||
$unitIndex++;
|
||||
}
|
||||
return $result;
|
||||
return \App\Services\Rd\AiQuotationService::numberToKorean($number);
|
||||
}
|
||||
}
|
||||
|
||||
$devTotalKorean = numberToKorean($devSubtotal);
|
||||
|
||||
// 필수 → 선택 순으로 정렬된 품목
|
||||
$sortedItems = $quotation->items->sortByDesc('is_required')->values();
|
||||
// 견적 모드 판별
|
||||
$isManufacture = $quotation->isManufacture();
|
||||
|
||||
// 템플릿 (기본값: classic)
|
||||
$template = $template ?? 'classic';
|
||||
$templates = [
|
||||
'classic' => '클래식',
|
||||
'modern' => '모던',
|
||||
'blue' => '블루',
|
||||
'dark' => '다크',
|
||||
'colorful' => '컬러풀',
|
||||
];
|
||||
|
||||
if ($isManufacture) {
|
||||
// 제조 견적서 데이터
|
||||
$options = $quotation->options ?? [];
|
||||
$client = $options['client'] ?? [];
|
||||
$project = $options['project'] ?? [];
|
||||
$pricing = $options['pricing'] ?? [];
|
||||
$terms = $options['terms'] ?? [];
|
||||
|
||||
$quotationNo = $quotation->quote_number ?? 'AQ-'.$quotation->created_at->format('Y').'-'.str_pad($quotation->id, 3, '0', STR_PAD_LEFT);
|
||||
$subtotal = (int) ($pricing['subtotal'] ?? 0);
|
||||
$discountRate = (float) ($pricing['discount_rate'] ?? 0);
|
||||
$discountAmount = (int) ($pricing['discount_amount'] ?? 0);
|
||||
$vatAmount = (int) ($pricing['vat_amount'] ?? 0);
|
||||
$finalAmount = (int) ($pricing['final_amount'] ?? 0);
|
||||
$subtotalKorean = numberToKorean($subtotal);
|
||||
$validUntil = $terms['valid_until'] ?? now()->addDays(30)->format('Y-m-d');
|
||||
|
||||
$templates = [
|
||||
'classic' => '클래식',
|
||||
];
|
||||
} else {
|
||||
// 모듈 추천 견적서 데이터 (기존)
|
||||
$quotationNo = 'AQ-' . $quotation->created_at->format('Y') . '-' . str_pad($quotation->id, 3, '0', STR_PAD_LEFT);
|
||||
$company = $quotation->analysis_result['company_analysis'] ?? [];
|
||||
$plan = $quotation->quotation_result['implementation_plan'] ?? [];
|
||||
$estimatedMonths = $plan['estimated_months'] ?? null;
|
||||
|
||||
$devSubtotal = (int) $quotation->total_dev_cost;
|
||||
$monthlySubtotal = (int) $quotation->total_monthly_fee;
|
||||
$devVat = (int) round($devSubtotal * 0.1);
|
||||
$monthlyVat = (int) round($monthlySubtotal * 0.1);
|
||||
$devTotal = $devSubtotal + $devVat;
|
||||
$monthlyTotal = $monthlySubtotal + $monthlyVat;
|
||||
$devTotalKorean = numberToKorean($devSubtotal);
|
||||
$sortedItems = $quotation->items->sortByDesc('is_required')->values();
|
||||
|
||||
$templates = [
|
||||
'classic' => '클래식',
|
||||
'modern' => '모던',
|
||||
'blue' => '블루',
|
||||
'dark' => '다크',
|
||||
'colorful' => '컬러풀',
|
||||
];
|
||||
}
|
||||
@endphp
|
||||
|
||||
{{-- 템플릿 선택 UI (인쇄 시 숨김) --}}
|
||||
@@ -109,21 +100,45 @@ class="template-card {{ $template === $key ? 'active' : '' }}">
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
{{-- PDF 다운로드 버튼 --}}
|
||||
<div class="text-center mt-3">
|
||||
<button onclick="window.print()" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition text-sm">
|
||||
<i class="ri-printer-line"></i> 인쇄 / PDF 다운로드
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 선택된 템플릿 렌더링 --}}
|
||||
@include('rd.ai-quotation.document-templates.' . $template, [
|
||||
'quotation' => $quotation,
|
||||
'quotationNo' => $quotationNo,
|
||||
'company' => $company,
|
||||
'estimatedMonths' => $estimatedMonths,
|
||||
'devSubtotal' => $devSubtotal,
|
||||
'monthlySubtotal' => $monthlySubtotal,
|
||||
'devVat' => $devVat,
|
||||
'monthlyVat' => $monthlyVat,
|
||||
'devTotal' => $devTotal,
|
||||
'monthlyTotal' => $monthlyTotal,
|
||||
'devTotalKorean' => $devTotalKorean,
|
||||
'sortedItems' => $sortedItems,
|
||||
])
|
||||
@if($isManufacture)
|
||||
@include('rd.ai-quotation.document-templates.manufacture-classic', [
|
||||
'quotation' => $quotation,
|
||||
'quotationNo' => $quotationNo,
|
||||
'client' => $client,
|
||||
'project' => $project,
|
||||
'pricing' => $pricing,
|
||||
'terms' => $terms,
|
||||
'subtotal' => $subtotal,
|
||||
'discountRate' => $discountRate,
|
||||
'discountAmount' => $discountAmount,
|
||||
'vatAmount' => $vatAmount,
|
||||
'finalAmount' => $finalAmount,
|
||||
'subtotalKorean' => $subtotalKorean,
|
||||
'validUntil' => $validUntil,
|
||||
])
|
||||
@else
|
||||
@include('rd.ai-quotation.document-templates.' . $template, [
|
||||
'quotation' => $quotation,
|
||||
'quotationNo' => $quotationNo,
|
||||
'company' => $company,
|
||||
'estimatedMonths' => $estimatedMonths,
|
||||
'devSubtotal' => $devSubtotal,
|
||||
'monthlySubtotal' => $monthlySubtotal,
|
||||
'devVat' => $devVat,
|
||||
'monthlyVat' => $monthlyVat,
|
||||
'devTotal' => $devTotal,
|
||||
'monthlyTotal' => $monthlyTotal,
|
||||
'devTotalKorean' => $devTotalKorean,
|
||||
'sortedItems' => $sortedItems,
|
||||
])
|
||||
@endif
|
||||
@endsection
|
||||
|
||||
403
resources/views/rd/ai-quotation/edit.blade.php
Normal file
403
resources/views/rd/ai-quotation/edit.blade.php
Normal file
@@ -0,0 +1,403 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', '견적서 편집')
|
||||
|
||||
@section('content')
|
||||
@php
|
||||
$options = $quotation->options ?? [];
|
||||
$client = $options['client'] ?? [];
|
||||
$project = $options['project'] ?? [];
|
||||
$pricing = $options['pricing'] ?? [];
|
||||
$terms = $options['terms'] ?? [];
|
||||
@endphp
|
||||
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-edit-line text-blue-600"></i>
|
||||
견적서 편집
|
||||
<span class="text-base font-normal text-gray-400">{{ $quotation->quote_number }}</span>
|
||||
</h1>
|
||||
<div class="flex gap-2">
|
||||
<a href="{{ route('rd.ai-quotation.show', $quotation->id) }}" class="bg-white hover:bg-gray-100 text-gray-700 px-4 py-2 rounded-lg border transition">
|
||||
<i class="ri-arrow-left-line"></i> 상세보기
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="editForm">
|
||||
<!-- 고객 정보 -->
|
||||
<div class="bg-white rounded-lg shadow-sm mb-6">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-user-line text-blue-600"></i> 고객 정보
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="grid gap-4" style="grid-template-columns: repeat(2, 1fr);">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">회사명</label>
|
||||
<input type="text" name="client_company" value="{{ $client['company'] ?? '' }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">담당자</label>
|
||||
<input type="text" name="client_contact" value="{{ $client['contact'] ?? '' }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">연락처</label>
|
||||
<input type="text" name="client_phone" value="{{ $client['phone'] ?? '' }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">이메일</label>
|
||||
<input type="email" name="client_email" value="{{ $client['email'] ?? '' }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
<div style="grid-column: span 2;">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">주소</label>
|
||||
<input type="text" name="client_address" value="{{ $client['address'] ?? '' }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 프로젝트 정보 -->
|
||||
<div class="bg-white rounded-lg shadow-sm mb-6">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-building-line text-green-600"></i> 프로젝트 정보
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="grid gap-4" style="grid-template-columns: repeat(2, 1fr);">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">현장명</label>
|
||||
<input type="text" name="project_name" value="{{ $project['name'] ?? '' }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">위치</label>
|
||||
<input type="text" name="project_location" value="{{ $project['location'] ?? '' }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 품목 편집 -->
|
||||
<div class="bg-white rounded-lg shadow-sm mb-6">
|
||||
<div class="px-6 py-4 border-b border-gray-100 flex justify-between items-center">
|
||||
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-list-check-2 text-purple-600"></i> 품목 편집
|
||||
</h2>
|
||||
<button type="button" onclick="addItemRow()" class="px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition">
|
||||
<i class="ri-add-line"></i> 행 추가
|
||||
</button>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm" id="itemsTable">
|
||||
<thead class="bg-gray-50 border-b">
|
||||
<tr>
|
||||
<th class="px-3 py-3 text-center text-xs font-medium text-gray-500" style="width: 40px;">No</th>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500">분류</th>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500">위치</th>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500">품목명</th>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500">규격</th>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500" style="width: 60px;">단위</th>
|
||||
<th class="px-3 py-3 text-center text-xs font-medium text-gray-500" style="width: 70px;">수량</th>
|
||||
<th class="px-3 py-3 text-right text-xs font-medium text-gray-500" style="width: 120px;">단가</th>
|
||||
<th class="px-3 py-3 text-right text-xs font-medium text-gray-500" style="width: 120px;">금액</th>
|
||||
<th class="px-3 py-3 text-center text-xs font-medium text-gray-500" style="width: 40px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="itemsBody">
|
||||
@foreach($quotation->items as $index => $item)
|
||||
<tr class="item-row border-b border-gray-100" data-index="{{ $index }}">
|
||||
<td class="px-3 py-2 text-center text-gray-500 row-number">{{ $index + 1 }}</td>
|
||||
<td class="px-3 py-2">
|
||||
<select name="items[{{ $index }}][item_category]" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs">
|
||||
<option value="material" {{ $item->item_category === 'material' ? 'selected' : '' }}>재료비</option>
|
||||
<option value="labor" {{ $item->item_category === 'labor' ? 'selected' : '' }}>노무비</option>
|
||||
<option value="install" {{ $item->item_category === 'install' ? 'selected' : '' }}>설치비</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<input type="text" name="items[{{ $index }}][floor_code]" value="{{ $item->floor_code }}"
|
||||
class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs" placeholder="B1-A01">
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<input type="text" name="items[{{ $index }}][item_name]" value="{{ $item->module_name }}"
|
||||
class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs" required>
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<input type="text" name="items[{{ $index }}][specification]" value="{{ $item->specification }}"
|
||||
class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs" placeholder="3000×2500">
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<input type="text" name="items[{{ $index }}][unit]" value="{{ $item->unit ?? 'SET' }}"
|
||||
class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs">
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<input type="number" name="items[{{ $index }}][quantity]" value="{{ (float)$item->quantity }}"
|
||||
class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs text-center item-qty"
|
||||
min="0" step="1" onchange="calcRow(this)">
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<input type="text" name="items[{{ $index }}][unit_price]" value="{{ number_format((float)$item->unit_price) }}"
|
||||
class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs text-right money-input item-price"
|
||||
inputmode="numeric" onfocus="moneyFocus(this)" onblur="moneyBlur(this)" onchange="calcRow(this)">
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<span class="item-total font-medium text-gray-800">{{ number_format((float)$item->total_price) }}</span>
|
||||
</td>
|
||||
<td class="px-3 py-2 text-center">
|
||||
<button type="button" onclick="removeRow(this)" class="text-red-400 hover:text-red-600 transition">
|
||||
<i class="ri-delete-bin-line"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
<tfoot class="bg-gray-50 border-t-2">
|
||||
<tr>
|
||||
<td colspan="8" class="px-3 py-3 text-right font-bold text-gray-700">소계</td>
|
||||
<td class="px-3 py-3 text-right font-bold text-blue-700" id="subtotalDisplay">{{ number_format((int)($pricing['subtotal'] ?? 0)) }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 가격 조정 + 조건 -->
|
||||
<div class="grid gap-6 mb-6" style="grid-template-columns: 1fr 1fr;">
|
||||
<!-- 가격 조정 -->
|
||||
<div class="bg-white rounded-lg shadow-sm">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-calculator-line text-orange-600"></i> 가격 조정
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-6 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">할인율 (%)</label>
|
||||
<input type="number" name="discount_rate" id="discountRate" value="{{ $pricing['discount_rate'] ?? 0 }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
|
||||
min="0" max="100" step="0.1" onchange="recalcTotal()">
|
||||
</div>
|
||||
<div class="space-y-2 text-sm border-t pt-4">
|
||||
<div class="flex justify-between"><span class="text-gray-600">소계</span><span id="pricingSubtotal">{{ number_format((int)($pricing['subtotal'] ?? 0)) }}원</span></div>
|
||||
<div class="flex justify-between text-red-600"><span>할인</span><span id="pricingDiscount">-{{ number_format((int)($pricing['discount_amount'] ?? 0)) }}원</span></div>
|
||||
<div class="flex justify-between"><span class="text-gray-600">부가세 (10%)</span><span id="pricingVat">{{ number_format((int)($pricing['vat_amount'] ?? 0)) }}원</span></div>
|
||||
<div class="flex justify-between text-lg border-t pt-2">
|
||||
<span class="font-bold text-blue-700">최종 금액</span>
|
||||
<span class="font-bold text-blue-700" id="pricingFinal">{{ number_format((int)($pricing['final_amount'] ?? 0)) }}원</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 조건 입력 -->
|
||||
<div class="bg-white rounded-lg shadow-sm">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-file-list-3-line text-teal-600"></i> 견적 조건
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-6 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">유효기간</label>
|
||||
<input type="date" name="terms_valid_until" value="{{ $terms['valid_until'] ?? now()->addDays(30)->format('Y-m-d') }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">결제조건</label>
|
||||
<input type="text" name="terms_payment" value="{{ $terms['payment'] ?? '계약 시 50%, 설치 완료 후 50%' }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">납기조건</label>
|
||||
<input type="text" name="terms_delivery" value="{{ $terms['delivery'] ?? '계약 후 4주 이내' }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 저장 버튼 -->
|
||||
<div class="flex justify-end gap-3">
|
||||
<a href="{{ route('rd.ai-quotation.show', $quotation->id) }}" class="px-6 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition">
|
||||
취소
|
||||
</a>
|
||||
<button type="submit" id="saveBtn" class="px-6 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition flex items-center gap-2">
|
||||
<i class="ri-save-line"></i> 저장
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
let rowCounter = {{ $quotation->items->count() }};
|
||||
|
||||
function moneyFocus(el) { el.value = el.value.replace(/,/g, ''); }
|
||||
function moneyBlur(el) {
|
||||
const val = parseInt(el.value) || 0;
|
||||
el.value = val.toLocaleString();
|
||||
}
|
||||
function parseMoneyValue(el) { return parseInt(String(el.value).replace(/,/g, '')) || 0; }
|
||||
|
||||
function calcRow(el) {
|
||||
const row = el.closest('tr');
|
||||
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
|
||||
const priceEl = row.querySelector('.item-price');
|
||||
const price = parseMoneyValue(priceEl);
|
||||
const total = Math.round(qty * price);
|
||||
row.querySelector('.item-total').textContent = total.toLocaleString();
|
||||
recalcTotal();
|
||||
}
|
||||
|
||||
function recalcTotal() {
|
||||
let subtotal = 0;
|
||||
document.querySelectorAll('.item-row').forEach(row => {
|
||||
const text = row.querySelector('.item-total')?.textContent || '0';
|
||||
subtotal += parseInt(text.replace(/,/g, '')) || 0;
|
||||
});
|
||||
|
||||
const discountRate = parseFloat(document.getElementById('discountRate').value) || 0;
|
||||
const discountAmount = Math.round(subtotal * discountRate / 100);
|
||||
const afterDiscount = subtotal - discountAmount;
|
||||
const vat = Math.round(afterDiscount * 0.1);
|
||||
const final_amount = afterDiscount + vat;
|
||||
|
||||
document.getElementById('subtotalDisplay').textContent = subtotal.toLocaleString();
|
||||
document.getElementById('pricingSubtotal').textContent = subtotal.toLocaleString() + '원';
|
||||
document.getElementById('pricingDiscount').textContent = '-' + discountAmount.toLocaleString() + '원';
|
||||
document.getElementById('pricingVat').textContent = vat.toLocaleString() + '원';
|
||||
document.getElementById('pricingFinal').textContent = final_amount.toLocaleString() + '원';
|
||||
}
|
||||
|
||||
function addItemRow() {
|
||||
const idx = rowCounter++;
|
||||
const tbody = document.getElementById('itemsBody');
|
||||
const tr = document.createElement('tr');
|
||||
tr.className = 'item-row border-b border-gray-100';
|
||||
tr.dataset.index = idx;
|
||||
tr.innerHTML = `
|
||||
<td class="px-3 py-2 text-center text-gray-500 row-number"></td>
|
||||
<td class="px-3 py-2">
|
||||
<select name="items[${idx}][item_category]" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs">
|
||||
<option value="material">재료비</option>
|
||||
<option value="labor">노무비</option>
|
||||
<option value="install">설치비</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="px-3 py-2"><input type="text" name="items[${idx}][floor_code]" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs" placeholder="B1-A01"></td>
|
||||
<td class="px-3 py-2"><input type="text" name="items[${idx}][item_name]" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs" required></td>
|
||||
<td class="px-3 py-2"><input type="text" name="items[${idx}][specification]" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs" placeholder="3000×2500"></td>
|
||||
<td class="px-3 py-2"><input type="text" name="items[${idx}][unit]" value="SET" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs"></td>
|
||||
<td class="px-3 py-2"><input type="number" name="items[${idx}][quantity]" value="1" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs text-center item-qty" min="0" step="1" onchange="calcRow(this)"></td>
|
||||
<td class="px-3 py-2"><input type="text" name="items[${idx}][unit_price]" value="0" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs text-right money-input item-price" inputmode="numeric" onfocus="moneyFocus(this)" onblur="moneyBlur(this)" onchange="calcRow(this)"></td>
|
||||
<td class="px-3 py-2"><span class="item-total font-medium text-gray-800">0</span></td>
|
||||
<td class="px-3 py-2 text-center"><button type="button" onclick="removeRow(this)" class="text-red-400 hover:text-red-600 transition"><i class="ri-delete-bin-line"></i></button></td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
renumberRows();
|
||||
}
|
||||
|
||||
function removeRow(btn) {
|
||||
if (document.querySelectorAll('.item-row').length <= 1) {
|
||||
alert('최소 1개의 품목이 필요합니다.');
|
||||
return;
|
||||
}
|
||||
btn.closest('tr').remove();
|
||||
renumberRows();
|
||||
recalcTotal();
|
||||
}
|
||||
|
||||
function renumberRows() {
|
||||
document.querySelectorAll('.item-row').forEach((row, idx) => {
|
||||
row.querySelector('.row-number').textContent = idx + 1;
|
||||
});
|
||||
}
|
||||
|
||||
// 폼 제출
|
||||
document.getElementById('editForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const btn = document.getElementById('saveBtn');
|
||||
const original = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="ri-loader-4-line animate-spin"></i> 저장중...';
|
||||
|
||||
// 데이터 수집
|
||||
const items = [];
|
||||
document.querySelectorAll('.item-row').forEach(row => {
|
||||
const inputs = row.querySelectorAll('input, select');
|
||||
const item = {};
|
||||
inputs.forEach(input => {
|
||||
const name = input.name;
|
||||
if (!name) return;
|
||||
const key = name.replace(/items\[\d+\]\[/, '').replace(']', '');
|
||||
let val = input.value;
|
||||
if (key === 'unit_price') val = String(val).replace(/,/g, '');
|
||||
item[key] = val;
|
||||
});
|
||||
items.push(item);
|
||||
});
|
||||
|
||||
const payload = {
|
||||
client: {
|
||||
company: document.querySelector('[name="client_company"]').value,
|
||||
contact: document.querySelector('[name="client_contact"]').value,
|
||||
phone: document.querySelector('[name="client_phone"]').value,
|
||||
email: document.querySelector('[name="client_email"]').value,
|
||||
address: document.querySelector('[name="client_address"]').value,
|
||||
},
|
||||
project: {
|
||||
name: document.querySelector('[name="project_name"]').value,
|
||||
location: document.querySelector('[name="project_location"]').value,
|
||||
},
|
||||
terms: {
|
||||
valid_until: document.querySelector('[name="terms_valid_until"]').value,
|
||||
payment: document.querySelector('[name="terms_payment"]').value,
|
||||
delivery: document.querySelector('[name="terms_delivery"]').value,
|
||||
},
|
||||
discount_rate: parseFloat(document.getElementById('discountRate').value) || 0,
|
||||
items: items,
|
||||
};
|
||||
|
||||
try {
|
||||
const token = document.querySelector('meta[name="api-token"]')?.content
|
||||
|| sessionStorage.getItem('api_token') || '';
|
||||
|
||||
const response = await fetch('{{ url("/api/admin/rd/ai-quotation/{$quotation->id}") }}', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || '',
|
||||
'Authorization': token ? `Bearer ${token}` : '',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
window.location.href = '{{ route("rd.ai-quotation.show", $quotation->id) }}';
|
||||
} else {
|
||||
alert(result.message || '저장에 실패했습니다.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('저장 실패:', err);
|
||||
alert('서버 통신 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = original;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@@ -11,12 +11,21 @@
|
||||
{{ $quotation->title }}
|
||||
</h1>
|
||||
<span class="badge {{ $quotation->status_color }}">{{ $quotation->status_label }}</span>
|
||||
@if($quotation->isManufacture())
|
||||
<span class="px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-full font-medium">제조 견적</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a href="{{ route('rd.ai-quotation.index') }}" class="bg-white hover:bg-gray-100 text-gray-700 px-4 py-2 rounded-lg border transition">
|
||||
<i class="ri-arrow-left-line"></i> 목록
|
||||
</a>
|
||||
@if($quotation->isCompleted())
|
||||
@if($quotation->isManufacture())
|
||||
<a href="{{ route('rd.ai-quotation.edit', $quotation->id) }}"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition">
|
||||
<i class="ri-edit-line"></i> 견적서 편집
|
||||
</a>
|
||||
@endif
|
||||
<a href="{{ route('rd.ai-quotation.document', $quotation->id) }}" target="_blank"
|
||||
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition">
|
||||
<i class="ri-file-text-line"></i> 견적서 보기
|
||||
@@ -38,10 +47,21 @@ class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg transit
|
||||
<p class="text-xs text-gray-500 mb-1">AI Provider</p>
|
||||
<p class="font-medium text-gray-800">{{ strtoupper($quotation->ai_provider) }}{{ $quotation->ai_model ? ' ('.$quotation->ai_model.')' : '' }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 mb-1">입력 유형</p>
|
||||
<p class="font-medium text-gray-800">{{ ['text' => '텍스트', 'voice' => '음성', 'document' => '문서'][$quotation->input_type] ?? $quotation->input_type }}</p>
|
||||
</div>
|
||||
@if($quotation->isManufacture())
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 mb-1">견적번호</p>
|
||||
<p class="font-medium text-gray-800 font-mono">{{ $quotation->quote_number ?? '-' }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 mb-1">제품 카테고리</p>
|
||||
<p class="font-medium text-gray-800">{{ $quotation->product_category === 'STEEL' ? '철재' : '방화스크린' }}</p>
|
||||
</div>
|
||||
@else
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 mb-1">입력 유형</p>
|
||||
<p class="font-medium text-gray-800">{{ ['text' => '텍스트', 'voice' => '음성', 'document' => '문서'][$quotation->input_type] ?? $quotation->input_type }}</p>
|
||||
</div>
|
||||
@endif
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 mb-1">요청자</p>
|
||||
<p class="font-medium text-gray-800">{{ $quotation->creator?->name ?? '-' }}</p>
|
||||
@@ -54,46 +74,297 @@ class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg transit
|
||||
</div>
|
||||
|
||||
@if($quotation->isCompleted())
|
||||
<!-- 업무 분석 결과 -->
|
||||
@if($quotation->analysis_result)
|
||||
@php $analysis = $quotation->analysis_result; @endphp
|
||||
@if($quotation->isManufacture())
|
||||
{{-- ============================================ --}}
|
||||
{{-- 제조 견적 모드 --}}
|
||||
{{-- ============================================ --}}
|
||||
|
||||
@php
|
||||
$options = $quotation->options ?? [];
|
||||
$client = $options['client'] ?? [];
|
||||
$project = $options['project'] ?? [];
|
||||
$pricing = $options['pricing'] ?? [];
|
||||
$terms = $options['terms'] ?? [];
|
||||
$analysis = $quotation->analysis_result ?? [];
|
||||
$productSpecs = $analysis['product_specs'] ?? [];
|
||||
@endphp
|
||||
|
||||
<!-- 고객 정보 -->
|
||||
@if(!empty($client))
|
||||
<div class="bg-white rounded-lg shadow-sm mb-6">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-search-eye-line text-blue-600"></i> AI 업무 분석 결과
|
||||
<i class="ri-user-line text-blue-600"></i> 고객 정보
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<!-- 기업 분석 -->
|
||||
@if(isset($analysis['company_analysis']))
|
||||
@php $company = $analysis['company_analysis']; @endphp
|
||||
<div class="flex flex-wrap gap-4 mb-6 pb-6 border-b border-gray-100">
|
||||
<div class="px-4 py-2 bg-blue-50 rounded-lg">
|
||||
<span class="text-xs text-blue-500 block">업종</span>
|
||||
<span class="font-semibold text-blue-800">{{ $company['industry'] ?? '-' }}</span>
|
||||
</div>
|
||||
<div class="px-4 py-2 bg-green-50 rounded-lg">
|
||||
<span class="text-xs text-green-500 block">규모</span>
|
||||
<span class="font-semibold text-green-800">{{ $company['scale'] ?? '-' }}</span>
|
||||
</div>
|
||||
<div class="px-4 py-2 bg-purple-50 rounded-lg">
|
||||
<span class="text-xs text-purple-500 block">디지털화 수준</span>
|
||||
<span class="font-semibold text-purple-800">{{ $company['digitalization_level'] ?? '-' }}</span>
|
||||
</div>
|
||||
@if(!empty($company['current_systems']))
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 mb-1">회사명</p>
|
||||
<p class="font-medium text-gray-800">{{ $client['company'] ?? '-' }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 mb-1">담당자</p>
|
||||
<p class="font-medium text-gray-800">{{ $client['contact'] ?? '-' }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 mb-1">연락처</p>
|
||||
<p class="font-medium text-gray-800">{{ $client['phone'] ?? '-' }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 mb-1">이메일</p>
|
||||
<p class="font-medium text-gray-800">{{ $client['email'] ?? '-' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- 프로젝트 분석 -->
|
||||
@if(!empty($project))
|
||||
<div class="bg-white rounded-lg shadow-sm mb-6">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-building-line text-green-600"></i> 프로젝트 분석
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<div class="px-4 py-2 bg-green-50 rounded-lg">
|
||||
<span class="text-xs text-green-500 block">현장명</span>
|
||||
<span class="font-semibold text-green-800">{{ $project['name'] ?? '-' }}</span>
|
||||
</div>
|
||||
<div class="px-4 py-2 bg-blue-50 rounded-lg">
|
||||
<span class="text-xs text-blue-500 block">건물유형</span>
|
||||
<span class="font-semibold text-blue-800">{{ $project['building_type'] ?? '-' }}</span>
|
||||
</div>
|
||||
@if(!empty($project['location']))
|
||||
<div class="px-4 py-2 bg-gray-50 rounded-lg">
|
||||
<span class="text-xs text-gray-500 block">위치</span>
|
||||
<span class="font-semibold text-gray-800">{{ $project['location'] }}</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- 위치별 사양 테이블 -->
|
||||
@if(!empty($productSpecs))
|
||||
<div class="bg-white rounded-lg shadow-sm mb-6">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-ruler-line text-indigo-600"></i> 위치별 제품 사양 (AI 추출)
|
||||
</h2>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 border-b">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">위치</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">유형</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">규격 (W×H)</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">수량</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">가이드레일</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">모터</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">비고</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-50">
|
||||
@foreach($productSpecs as $spec)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3">
|
||||
<div class="font-medium text-gray-800">{{ $spec['floor_name'] ?? '-' }}</div>
|
||||
<div class="text-xs text-gray-400 font-mono">{{ $spec['floor_code'] ?? '' }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-full">{{ $spec['product_type'] ?? '-' }}</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center font-mono">{{ ($spec['width_mm'] ?? 0) }}×{{ ($spec['height_mm'] ?? 0) }}</td>
|
||||
<td class="px-4 py-3 text-center font-medium">{{ $spec['quantity'] ?? 1 }}</td>
|
||||
<td class="px-4 py-3 text-gray-600">{{ $spec['guide_rail'] ?? '-' }}</td>
|
||||
<td class="px-4 py-3 text-gray-600">{{ $spec['motor'] ?? '-' }}</td>
|
||||
<td class="px-4 py-3 text-xs text-gray-500">{{ $spec['note'] ?? '' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- 견적 품목 테이블 -->
|
||||
<div class="bg-white rounded-lg shadow-sm mb-6">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-price-tag-3-line text-green-600"></i> 견적 품목
|
||||
</h2>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
@php
|
||||
$materialItems = $quotation->items->where('item_category', 'material');
|
||||
$laborItems = $quotation->items->where('item_category', 'labor');
|
||||
$installItems = $quotation->items->where('item_category', 'install');
|
||||
$otherItems = $quotation->items->whereNotIn('item_category', ['material', 'labor', 'install']);
|
||||
@endphp
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 border-b">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase" style="width: 40px;">No</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">위치</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">품목</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">규격</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">수량</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">단가</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">금액</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-50">
|
||||
@php $no = 1; @endphp
|
||||
|
||||
{{-- 재료비 --}}
|
||||
@if($materialItems->isNotEmpty())
|
||||
<tr class="bg-blue-50">
|
||||
<td colspan="7" class="px-4 py-2 text-xs font-semibold text-blue-700">재료비</td>
|
||||
</tr>
|
||||
@foreach($materialItems as $item)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 text-center text-gray-500">{{ $no++ }}</td>
|
||||
<td class="px-4 py-3 text-xs text-gray-500 font-mono">{{ $item->floor_code }}</td>
|
||||
<td class="px-4 py-3 font-medium text-gray-800">{{ $item->module_name }}</td>
|
||||
<td class="px-4 py-3 text-center text-gray-600 font-mono text-xs">{{ $item->specification }}</td>
|
||||
<td class="px-4 py-3 text-center text-gray-600">{{ number_format((float)$item->quantity, 0) }} {{ $item->unit }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-600">{{ number_format((float)$item->unit_price) }}</td>
|
||||
<td class="px-4 py-3 text-right font-medium text-gray-800">{{ number_format((float)$item->total_price) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
{{-- 노무비 --}}
|
||||
@if($laborItems->isNotEmpty())
|
||||
<tr class="bg-green-50">
|
||||
<td colspan="7" class="px-4 py-2 text-xs font-semibold text-green-700">노무비</td>
|
||||
</tr>
|
||||
@foreach($laborItems as $item)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 text-center text-gray-500">{{ $no++ }}</td>
|
||||
<td class="px-4 py-3 text-xs text-gray-500 font-mono">{{ $item->floor_code }}</td>
|
||||
<td class="px-4 py-3 font-medium text-gray-800">{{ $item->module_name }}</td>
|
||||
<td class="px-4 py-3 text-center text-gray-600 font-mono text-xs">{{ $item->specification }}</td>
|
||||
<td class="px-4 py-3 text-center text-gray-600">{{ number_format((float)$item->quantity, 0) }} {{ $item->unit }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-600">{{ number_format((float)$item->unit_price) }}</td>
|
||||
<td class="px-4 py-3 text-right font-medium text-gray-800">{{ number_format((float)$item->total_price) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
{{-- 설치비 --}}
|
||||
@if($installItems->isNotEmpty())
|
||||
<tr class="bg-orange-50">
|
||||
<td colspan="7" class="px-4 py-2 text-xs font-semibold text-orange-700">설치비</td>
|
||||
</tr>
|
||||
@foreach($installItems as $item)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 text-center text-gray-500">{{ $no++ }}</td>
|
||||
<td class="px-4 py-3 text-xs text-gray-500 font-mono">{{ $item->floor_code }}</td>
|
||||
<td class="px-4 py-3 font-medium text-gray-800">{{ $item->module_name }}</td>
|
||||
<td class="px-4 py-3 text-center text-gray-600 font-mono text-xs">{{ $item->specification }}</td>
|
||||
<td class="px-4 py-3 text-center text-gray-600">{{ number_format((float)$item->quantity, 0) }} {{ $item->unit }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-600">{{ number_format((float)$item->unit_price) }}</td>
|
||||
<td class="px-4 py-3 text-right font-medium text-gray-800">{{ number_format((float)$item->total_price) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
{{-- 기타 --}}
|
||||
@foreach($otherItems as $item)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 text-center text-gray-500">{{ $no++ }}</td>
|
||||
<td class="px-4 py-3 text-xs text-gray-500 font-mono">{{ $item->floor_code }}</td>
|
||||
<td class="px-4 py-3 font-medium text-gray-800">{{ $item->module_name }}</td>
|
||||
<td class="px-4 py-3 text-center text-gray-600 font-mono text-xs">{{ $item->specification }}</td>
|
||||
<td class="px-4 py-3 text-center text-gray-600">{{ number_format((float)$item->quantity, 0) }} {{ $item->unit }}</td>
|
||||
<td class="px-4 py-3 text-right text-gray-600">{{ number_format((float)$item->unit_price) }}</td>
|
||||
<td class="px-4 py-3 text-right font-medium text-gray-800">{{ number_format((float)$item->total_price) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 금액 요약 -->
|
||||
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-lg p-6 mb-6">
|
||||
<h3 class="font-semibold text-gray-800 mb-4 flex items-center gap-2">
|
||||
<i class="ri-money-cny-circle-line text-blue-600"></i> 금액 요약
|
||||
</h3>
|
||||
<div class="grid gap-3" style="grid-template-columns: 1fr 1fr;">
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex justify-between"><span class="text-gray-600">재료비</span><span class="font-medium">{{ number_format((int)($pricing['material_cost'] ?? 0)) }}원</span></div>
|
||||
<div class="flex justify-between"><span class="text-gray-600">노무비</span><span class="font-medium">{{ number_format((int)($pricing['labor_cost'] ?? 0)) }}원</span></div>
|
||||
<div class="flex justify-between"><span class="text-gray-600">설치비</span><span class="font-medium">{{ number_format((int)($pricing['install_cost'] ?? 0)) }}원</span></div>
|
||||
<div class="flex justify-between border-t pt-2"><span class="font-semibold">소계</span><span class="font-bold">{{ number_format((int)($pricing['subtotal'] ?? 0)) }}원</span></div>
|
||||
</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
@if(($pricing['discount_rate'] ?? 0) > 0)
|
||||
<div class="flex justify-between"><span class="text-red-600">할인 ({{ $pricing['discount_rate'] }}%)</span><span class="font-medium text-red-600">-{{ number_format((int)($pricing['discount_amount'] ?? 0)) }}원</span></div>
|
||||
@endif
|
||||
<div class="flex justify-between"><span class="text-gray-600">부가세 (10%)</span><span class="font-medium">{{ number_format((int)($pricing['vat_amount'] ?? 0)) }}원</span></div>
|
||||
<div class="flex justify-between border-t pt-2 text-lg">
|
||||
<span class="font-bold text-blue-700">최종 금액</span>
|
||||
<span class="font-bold text-blue-700">{{ number_format((int)($pricing['final_amount'] ?? 0)) }}원</span>
|
||||
</div>
|
||||
@php
|
||||
$koreanAmount = \App\Services\Rd\AiQuotationService::numberToKorean((int)($pricing['subtotal'] ?? 0));
|
||||
@endphp
|
||||
<div class="text-xs text-gray-500 text-right">금 {{ $koreanAmount }}원정 (VAT 별도)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@else
|
||||
{{-- ============================================ --}}
|
||||
{{-- 모듈 추천 모드 (기존) --}}
|
||||
{{-- ============================================ --}}
|
||||
|
||||
<!-- 업무 분석 결과 -->
|
||||
@if($quotation->analysis_result)
|
||||
@php $analysis = $quotation->analysis_result; @endphp
|
||||
<div class="bg-white rounded-lg shadow-sm mb-6">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-search-eye-line text-blue-600"></i> AI 업무 분석 결과
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
@if(isset($analysis['company_analysis']))
|
||||
@php $company = $analysis['company_analysis']; @endphp
|
||||
<div class="flex flex-wrap gap-4 mb-6 pb-6 border-b border-gray-100">
|
||||
<div class="px-4 py-2 bg-blue-50 rounded-lg">
|
||||
<span class="text-xs text-blue-500 block">업종</span>
|
||||
<span class="font-semibold text-blue-800">{{ $company['industry'] ?? '-' }}</span>
|
||||
</div>
|
||||
<div class="px-4 py-2 bg-green-50 rounded-lg">
|
||||
<span class="text-xs text-green-500 block">규모</span>
|
||||
<span class="font-semibold text-green-800">{{ $company['scale'] ?? '-' }}</span>
|
||||
</div>
|
||||
<div class="px-4 py-2 bg-purple-50 rounded-lg">
|
||||
<span class="text-xs text-purple-500 block">디지털화 수준</span>
|
||||
<span class="font-semibold text-purple-800">{{ $company['digitalization_level'] ?? '-' }}</span>
|
||||
</div>
|
||||
@if(!empty($company['current_systems']))
|
||||
<div class="px-4 py-2 bg-gray-50 rounded-lg">
|
||||
<span class="text-xs text-gray-500 block">현재 시스템</span>
|
||||
<span class="font-semibold text-gray-800">{{ implode(', ', $company['current_systems']) }}</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- 업무 영역별 분석 -->
|
||||
@if(!empty($analysis['business_domains']))
|
||||
<h3 class="text-sm font-semibold text-gray-600 uppercase mb-3">업무 영역 분석</h3>
|
||||
<div class="space-y-4">
|
||||
@foreach($analysis['business_domains'] as $domain)
|
||||
@if(!empty($analysis['business_domains']))
|
||||
<h3 class="text-sm font-semibold text-gray-600 uppercase mb-3">업무 영역 분석</h3>
|
||||
<div class="space-y-4">
|
||||
@foreach($analysis['business_domains'] as $domain)
|
||||
<div class="border border-gray-200 rounded-lg p-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h4 class="font-semibold text-gray-800">{{ $domain['domain'] ?? '' }}</h4>
|
||||
@@ -109,47 +380,47 @@ class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg transit
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mb-2">{{ $domain['current_process'] ?? '' }}</p>
|
||||
@if(!empty($domain['pain_points']))
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
@foreach($domain['pain_points'] as $point)
|
||||
<span class="px-2 py-0.5 bg-red-50 text-red-600 text-xs rounded">{{ $point }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
@foreach($domain['pain_points'] as $point)
|
||||
<span class="px-2 py-0.5 bg-red-50 text-red-600 text-xs rounded">{{ $point }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@if(!empty($domain['matched_modules']))
|
||||
<div class="flex flex-wrap gap-1.5 mt-2">
|
||||
@foreach($domain['matched_modules'] as $mod)
|
||||
<span class="px-2 py-0.5 bg-purple-50 text-purple-600 text-xs rounded font-mono">{{ $mod }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1.5 mt-2">
|
||||
@foreach($domain['matched_modules'] as $mod)
|
||||
<span class="px-2 py-0.5 bg-purple-50 text-purple-600 text-xs rounded font-mono">{{ $mod }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
<!-- 추천 모듈 + 견적 -->
|
||||
<div class="bg-white rounded-lg shadow-sm mb-6">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-price-tag-3-line text-green-600"></i> 추천 모듈 및 견적
|
||||
</h2>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 border-b">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">구분</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">모듈</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">추천 근거</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">개발비</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">월 구독료</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-50">
|
||||
@foreach($quotation->items as $item)
|
||||
<!-- 추천 모듈 + 견적 -->
|
||||
<div class="bg-white rounded-lg shadow-sm mb-6">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-price-tag-3-line text-green-600"></i> 추천 모듈 및 견적
|
||||
</h2>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 border-b">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">구분</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">모듈</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">추천 근거</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">개발비</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">월 구독료</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-50">
|
||||
@foreach($quotation->items as $item)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3">
|
||||
@if($item->is_required)
|
||||
@@ -168,69 +439,69 @@ class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg transit
|
||||
<td class="px-4 py-3 text-right font-medium text-gray-800">{{ number_format((int)$item->dev_cost) }}원</td>
|
||||
<td class="px-4 py-3 text-right font-medium text-gray-800">{{ number_format((int)$item->monthly_fee) }}원</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
<tfoot class="bg-purple-50 border-t-2 border-purple-200">
|
||||
<tr>
|
||||
<td colspan="3" class="px-4 py-3 text-right font-bold text-gray-800">합계</td>
|
||||
<td class="px-4 py-3 text-right font-bold text-purple-700 text-base">{{ number_format((int)$quotation->total_dev_cost) }}원</td>
|
||||
<td class="px-4 py-3 text-right font-bold text-purple-700 text-base">{{ number_format((int)$quotation->total_monthly_fee) }}원/월</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 구현 계획 (AI 생성) -->
|
||||
@if(!empty($quotation->quotation_result['implementation_plan']))
|
||||
@php $plan = $quotation->quotation_result['implementation_plan']; @endphp
|
||||
<div class="bg-white rounded-lg shadow-sm mb-6">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-calendar-schedule-line text-indigo-600"></i> 구현 계획 (AI 추천)
|
||||
</h2>
|
||||
@endforeach
|
||||
</tbody>
|
||||
<tfoot class="bg-purple-50 border-t-2 border-purple-200">
|
||||
<tr>
|
||||
<td colspan="3" class="px-4 py-3 text-right font-bold text-gray-800">합계</td>
|
||||
<td class="px-4 py-3 text-right font-bold text-purple-700 text-base">{{ number_format((int)$quotation->total_dev_cost) }}원</td>
|
||||
<td class="px-4 py-3 text-right font-bold text-purple-700 text-base">{{ number_format((int)$quotation->total_monthly_fee) }}원/월</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<p class="text-sm text-gray-600 mb-4">예상 기간: <span class="font-semibold">{{ $plan['estimated_months'] ?? '?' }}개월</span></p>
|
||||
@if(!empty($plan['phases']))
|
||||
</div>
|
||||
|
||||
<!-- 구현 계획 -->
|
||||
@if(!empty($quotation->quotation_result['implementation_plan']))
|
||||
@php $plan = $quotation->quotation_result['implementation_plan']; @endphp
|
||||
<div class="bg-white rounded-lg shadow-sm mb-6">
|
||||
<div class="px-6 py-4 border-b border-gray-100">
|
||||
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
||||
<i class="ri-calendar-schedule-line text-indigo-600"></i> 구현 계획 (AI 추천)
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<p class="text-sm text-gray-600 mb-4">예상 기간: <span class="font-semibold">{{ $plan['estimated_months'] ?? '?' }}개월</span></p>
|
||||
@if(!empty($plan['phases']))
|
||||
<div class="space-y-3">
|
||||
@foreach($plan['phases'] as $phase)
|
||||
<div class="flex items-center gap-4 p-3 bg-gray-50 rounded-lg">
|
||||
<div class="w-10 h-10 bg-indigo-100 text-indigo-700 rounded-full flex items-center justify-center font-bold shrink-0">
|
||||
{{ $phase['phase'] ?? '' }}
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-gray-800">{{ $phase['name'] ?? '' }}</p>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<span class="text-xs text-gray-500">{{ $phase['duration_weeks'] ?? '?' }}주</span>
|
||||
@if(!empty($phase['modules']))
|
||||
<span class="text-xs text-gray-400">|</span>
|
||||
@foreach($phase['modules'] as $mod)
|
||||
<span class="px-1.5 py-0.5 bg-indigo-50 text-indigo-600 text-xs rounded font-mono">{{ $mod }}</span>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex items-center gap-4 p-3 bg-gray-50 rounded-lg">
|
||||
<div class="w-10 h-10 bg-indigo-100 text-indigo-700 rounded-full flex items-center justify-center font-bold shrink-0">
|
||||
{{ $phase['phase'] ?? '' }}
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-gray-800">{{ $phase['name'] ?? '' }}</p>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<span class="text-xs text-gray-500">{{ $phase['duration_weeks'] ?? '?' }}주</span>
|
||||
@if(!empty($phase['modules']))
|
||||
<span class="text-xs text-gray-400">|</span>
|
||||
@foreach($phase['modules'] as $mod)
|
||||
<span class="px-1.5 py-0.5 bg-indigo-50 text-indigo-600 text-xs rounded font-mono">{{ $mod }}</span>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
<!-- 분석 요약 -->
|
||||
@if(!empty($quotation->quotation_result['analysis_summary']))
|
||||
<!-- 분석 요약 -->
|
||||
@if(!empty($quotation->quotation_result['analysis_summary']))
|
||||
<div class="bg-gradient-to-r from-purple-50 to-indigo-50 rounded-lg p-6 mb-6">
|
||||
<h3 class="font-semibold text-gray-800 mb-2 flex items-center gap-2">
|
||||
<i class="ri-lightbulb-line text-yellow-500"></i> AI 분석 요약
|
||||
</h3>
|
||||
<p class="text-gray-700">{{ $quotation->quotation_result['analysis_summary'] }}</p>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@elseif($quotation->status === 'failed')
|
||||
<!-- 실패 상태 -->
|
||||
<div class="bg-red-50 border border-red-200 rounded-lg p-6 mb-6">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<i class="ri-error-warning-line text-2xl text-red-500"></i>
|
||||
@@ -240,7 +511,6 @@ class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg transit
|
||||
</div>
|
||||
|
||||
@elseif($quotation->isProcessing())
|
||||
<!-- 분석중 상태 -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-6 mb-6 text-center">
|
||||
<i class="ri-loader-4-line text-4xl text-blue-500 animate-spin mb-2 block"></i>
|
||||
<h2 class="text-lg font-semibold text-blue-800">AI 분석 진행중...</h2>
|
||||
|
||||
Reference in New Issue
Block a user