feat: [quote] 견적 API Phase 2-3 완료 (Service + Controller Layer)
Phase 2 - Service Layer:
- QuoteService: 견적 CRUD + 상태관리 (확정/전환)
- QuoteNumberService: 견적번호 채번 (KD-{PREFIX}-YYMMDD-SEQ)
- FormulaEvaluatorService: 수식 평가 엔진 (SUM, IF, ROUND 등)
- QuoteCalculationService: 자동산출 (스크린/철재 제품)
- QuoteDocumentService: PDF 생성 및 이메일/카카오 발송
Phase 3 - Controller Layer:
- QuoteController: 16개 엔드포인트
- FormRequest 7개: Index, Store, Update, BulkDelete, Calculate, SendEmail, SendKakao
- QuoteApi.php: Swagger 문서 (12개 스키마, 16개 엔드포인트)
- routes/api.php: 16개 라우트 등록
i18n 키 추가:
- error.php: quote_not_found, formula_* 등
- message.php: quote.* 성공 메시지
2025-12-04 22:03:40 +09:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Api\V1;
|
|
|
|
|
|
|
|
|
|
use App\Helpers\ApiResponse;
|
|
|
|
|
use App\Http\Controllers\Controller;
|
2026-01-02 13:13:50 +09:00
|
|
|
use App\Http\Requests\Quote\QuoteBomBulkCalculateRequest;
|
2026-01-02 11:24:22 +09:00
|
|
|
use App\Http\Requests\Quote\QuoteBomCalculateRequest;
|
feat: [quote] 견적 API Phase 2-3 완료 (Service + Controller Layer)
Phase 2 - Service Layer:
- QuoteService: 견적 CRUD + 상태관리 (확정/전환)
- QuoteNumberService: 견적번호 채번 (KD-{PREFIX}-YYMMDD-SEQ)
- FormulaEvaluatorService: 수식 평가 엔진 (SUM, IF, ROUND 등)
- QuoteCalculationService: 자동산출 (스크린/철재 제품)
- QuoteDocumentService: PDF 생성 및 이메일/카카오 발송
Phase 3 - Controller Layer:
- QuoteController: 16개 엔드포인트
- FormRequest 7개: Index, Store, Update, BulkDelete, Calculate, SendEmail, SendKakao
- QuoteApi.php: Swagger 문서 (12개 스키마, 16개 엔드포인트)
- routes/api.php: 16개 라우트 등록
i18n 키 추가:
- error.php: quote_not_found, formula_* 등
- message.php: quote.* 성공 메시지
2025-12-04 22:03:40 +09:00
|
|
|
use App\Http\Requests\Quote\QuoteBulkDeleteRequest;
|
|
|
|
|
use App\Http\Requests\Quote\QuoteCalculateRequest;
|
|
|
|
|
use App\Http\Requests\Quote\QuoteIndexRequest;
|
|
|
|
|
use App\Http\Requests\Quote\QuoteSendEmailRequest;
|
|
|
|
|
use App\Http\Requests\Quote\QuoteSendKakaoRequest;
|
|
|
|
|
use App\Http\Requests\Quote\QuoteStoreRequest;
|
|
|
|
|
use App\Http\Requests\Quote\QuoteUpdateRequest;
|
|
|
|
|
use App\Services\Quote\QuoteCalculationService;
|
|
|
|
|
use App\Services\Quote\QuoteDocumentService;
|
|
|
|
|
use App\Services\Quote\QuoteNumberService;
|
|
|
|
|
use App\Services\Quote\QuoteService;
|
2026-01-06 20:57:51 +09:00
|
|
|
use Illuminate\Support\Facades\Log;
|
feat: [quote] 견적 API Phase 2-3 완료 (Service + Controller Layer)
Phase 2 - Service Layer:
- QuoteService: 견적 CRUD + 상태관리 (확정/전환)
- QuoteNumberService: 견적번호 채번 (KD-{PREFIX}-YYMMDD-SEQ)
- FormulaEvaluatorService: 수식 평가 엔진 (SUM, IF, ROUND 등)
- QuoteCalculationService: 자동산출 (스크린/철재 제품)
- QuoteDocumentService: PDF 생성 및 이메일/카카오 발송
Phase 3 - Controller Layer:
- QuoteController: 16개 엔드포인트
- FormRequest 7개: Index, Store, Update, BulkDelete, Calculate, SendEmail, SendKakao
- QuoteApi.php: Swagger 문서 (12개 스키마, 16개 엔드포인트)
- routes/api.php: 16개 라우트 등록
i18n 키 추가:
- error.php: quote_not_found, formula_* 등
- message.php: quote.* 성공 메시지
2025-12-04 22:03:40 +09:00
|
|
|
|
|
|
|
|
class QuoteController extends Controller
|
|
|
|
|
{
|
|
|
|
|
public function __construct(
|
|
|
|
|
private QuoteService $quoteService,
|
|
|
|
|
private QuoteNumberService $numberService,
|
|
|
|
|
private QuoteCalculationService $calculationService,
|
|
|
|
|
private QuoteDocumentService $documentService
|
|
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 견적 목록 조회
|
|
|
|
|
*/
|
|
|
|
|
public function index(QuoteIndexRequest $request)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($request) {
|
|
|
|
|
return $this->quoteService->index($request->validated());
|
|
|
|
|
}, __('message.quote.fetched'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 견적 단건 조회
|
|
|
|
|
*/
|
|
|
|
|
public function show(int $id)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($id) {
|
|
|
|
|
return $this->quoteService->show($id);
|
|
|
|
|
}, __('message.quote.fetched'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 견적 생성
|
|
|
|
|
*/
|
|
|
|
|
public function store(QuoteStoreRequest $request)
|
|
|
|
|
{
|
2026-01-06 20:57:51 +09:00
|
|
|
// DEBUG: 요청 데이터 로깅
|
|
|
|
|
Log::info('[QuoteController::store] 원본 요청:', [
|
|
|
|
|
'author' => $request->input('author'),
|
|
|
|
|
'manager' => $request->input('manager'),
|
|
|
|
|
'contact' => $request->input('contact'),
|
|
|
|
|
'remarks' => $request->input('remarks'),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$validated = $request->validated();
|
|
|
|
|
|
|
|
|
|
// DEBUG: validated 데이터 로깅
|
|
|
|
|
Log::info('[QuoteController::store] validated 데이터:', [
|
|
|
|
|
'author' => $validated['author'] ?? 'NOT_SET',
|
|
|
|
|
'manager' => $validated['manager'] ?? 'NOT_SET',
|
|
|
|
|
'contact' => $validated['contact'] ?? 'NOT_SET',
|
|
|
|
|
'remarks' => $validated['remarks'] ?? 'NOT_SET',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return ApiResponse::handle(function () use ($validated) {
|
|
|
|
|
return $this->quoteService->store($validated);
|
feat: [quote] 견적 API Phase 2-3 완료 (Service + Controller Layer)
Phase 2 - Service Layer:
- QuoteService: 견적 CRUD + 상태관리 (확정/전환)
- QuoteNumberService: 견적번호 채번 (KD-{PREFIX}-YYMMDD-SEQ)
- FormulaEvaluatorService: 수식 평가 엔진 (SUM, IF, ROUND 등)
- QuoteCalculationService: 자동산출 (스크린/철재 제품)
- QuoteDocumentService: PDF 생성 및 이메일/카카오 발송
Phase 3 - Controller Layer:
- QuoteController: 16개 엔드포인트
- FormRequest 7개: Index, Store, Update, BulkDelete, Calculate, SendEmail, SendKakao
- QuoteApi.php: Swagger 문서 (12개 스키마, 16개 엔드포인트)
- routes/api.php: 16개 라우트 등록
i18n 키 추가:
- error.php: quote_not_found, formula_* 등
- message.php: quote.* 성공 메시지
2025-12-04 22:03:40 +09:00
|
|
|
}, __('message.quote.created'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 견적 수정
|
|
|
|
|
*/
|
|
|
|
|
public function update(QuoteUpdateRequest $request, int $id)
|
|
|
|
|
{
|
2026-01-16 21:58:57 +09:00
|
|
|
$validated = $request->validated();
|
|
|
|
|
|
|
|
|
|
// 🔍 디버깅: 요청 데이터 확인
|
|
|
|
|
\Log::info('🔍 [QuoteController::update] 요청 수신', [
|
|
|
|
|
'id' => $id,
|
|
|
|
|
'raw_options_keys' => $request->input('options') ? array_keys($request->input('options')) : null,
|
|
|
|
|
'raw_options_detail_items_count' => $request->input('options.detail_items') ? count($request->input('options.detail_items')) : 0,
|
|
|
|
|
'validated_options_keys' => isset($validated['options']) ? array_keys($validated['options']) : null,
|
|
|
|
|
'validated_options_detail_items_count' => isset($validated['options']['detail_items']) ? count($validated['options']['detail_items']) : 0,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return ApiResponse::handle(function () use ($validated, $id) {
|
|
|
|
|
return $this->quoteService->update($id, $validated);
|
feat: [quote] 견적 API Phase 2-3 완료 (Service + Controller Layer)
Phase 2 - Service Layer:
- QuoteService: 견적 CRUD + 상태관리 (확정/전환)
- QuoteNumberService: 견적번호 채번 (KD-{PREFIX}-YYMMDD-SEQ)
- FormulaEvaluatorService: 수식 평가 엔진 (SUM, IF, ROUND 등)
- QuoteCalculationService: 자동산출 (스크린/철재 제품)
- QuoteDocumentService: PDF 생성 및 이메일/카카오 발송
Phase 3 - Controller Layer:
- QuoteController: 16개 엔드포인트
- FormRequest 7개: Index, Store, Update, BulkDelete, Calculate, SendEmail, SendKakao
- QuoteApi.php: Swagger 문서 (12개 스키마, 16개 엔드포인트)
- routes/api.php: 16개 라우트 등록
i18n 키 추가:
- error.php: quote_not_found, formula_* 등
- message.php: quote.* 성공 메시지
2025-12-04 22:03:40 +09:00
|
|
|
}, __('message.quote.updated'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 견적 삭제
|
|
|
|
|
*/
|
|
|
|
|
public function destroy(int $id)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($id) {
|
|
|
|
|
$this->quoteService->destroy($id);
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}, __('message.quote.deleted'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 견적 일괄 삭제
|
|
|
|
|
*/
|
|
|
|
|
public function bulkDestroy(QuoteBulkDeleteRequest $request)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($request) {
|
|
|
|
|
$count = $this->quoteService->bulkDestroy($request->validated()['ids']);
|
|
|
|
|
|
|
|
|
|
return ['deleted_count' => $count];
|
|
|
|
|
}, __('message.quote.bulk_deleted'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 견적 확정
|
|
|
|
|
*/
|
|
|
|
|
public function finalize(int $id)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($id) {
|
|
|
|
|
return $this->quoteService->finalize($id);
|
|
|
|
|
}, __('message.quote.finalized'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 견적 확정 취소
|
|
|
|
|
*/
|
|
|
|
|
public function cancelFinalize(int $id)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($id) {
|
|
|
|
|
return $this->quoteService->cancelFinalize($id);
|
|
|
|
|
}, __('message.quote.finalize_cancelled'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 수주 전환
|
|
|
|
|
*/
|
|
|
|
|
public function convertToOrder(int $id)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($id) {
|
|
|
|
|
return $this->quoteService->convertToOrder($id);
|
|
|
|
|
}, __('message.quote.converted'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 견적번호 미리보기
|
|
|
|
|
*/
|
|
|
|
|
public function previewNumber(?string $productCategory = null)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($productCategory) {
|
|
|
|
|
return $this->numberService->preview($productCategory);
|
|
|
|
|
}, __('message.fetched'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 자동산출 미리보기
|
|
|
|
|
*/
|
|
|
|
|
public function calculate(QuoteCalculateRequest $request)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($request) {
|
|
|
|
|
$validated = $request->validated();
|
|
|
|
|
|
|
|
|
|
return $this->calculationService->calculate(
|
|
|
|
|
$validated['inputs'] ?? $validated,
|
|
|
|
|
$validated['product_category'] ?? null
|
|
|
|
|
);
|
|
|
|
|
}, __('message.quote.calculated'));
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-02 11:24:22 +09:00
|
|
|
/**
|
|
|
|
|
* BOM 기반 자동산출 (10단계 디버깅 포함)
|
|
|
|
|
*
|
|
|
|
|
* React 견적등록 화면에서 완제품 코드와 입력 변수를 받아
|
|
|
|
|
* BOM 기반으로 품목/단가/금액을 계산합니다.
|
|
|
|
|
*/
|
|
|
|
|
public function calculateBom(QuoteBomCalculateRequest $request)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($request) {
|
|
|
|
|
return $this->calculationService->calculateBom(
|
|
|
|
|
$request->finished_goods_code,
|
|
|
|
|
$request->getInputVariables(),
|
|
|
|
|
$request->boolean('debug', false)
|
|
|
|
|
);
|
|
|
|
|
}, __('message.quote.calculated'));
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-02 13:13:50 +09:00
|
|
|
/**
|
|
|
|
|
* 다건 BOM 기반 자동산출
|
|
|
|
|
*
|
|
|
|
|
* React 견적등록 화면에서 여러 품목의 완제품 코드와 입력 변수를 받아
|
|
|
|
|
* BOM 기반으로 일괄 계산합니다.
|
|
|
|
|
* React QuoteFormItem 필드명(camelCase)과 API 변수명(약어) 모두 지원합니다.
|
|
|
|
|
*/
|
|
|
|
|
public function calculateBomBulk(QuoteBomBulkCalculateRequest $request)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($request) {
|
|
|
|
|
return $this->calculationService->calculateBomBulk(
|
|
|
|
|
$request->getInputItems(),
|
|
|
|
|
$request->boolean('debug', false)
|
|
|
|
|
);
|
|
|
|
|
}, __('message.quote.bulk_calculated'));
|
|
|
|
|
}
|
|
|
|
|
|
feat: [quote] 견적 API Phase 2-3 완료 (Service + Controller Layer)
Phase 2 - Service Layer:
- QuoteService: 견적 CRUD + 상태관리 (확정/전환)
- QuoteNumberService: 견적번호 채번 (KD-{PREFIX}-YYMMDD-SEQ)
- FormulaEvaluatorService: 수식 평가 엔진 (SUM, IF, ROUND 등)
- QuoteCalculationService: 자동산출 (스크린/철재 제품)
- QuoteDocumentService: PDF 생성 및 이메일/카카오 발송
Phase 3 - Controller Layer:
- QuoteController: 16개 엔드포인트
- FormRequest 7개: Index, Store, Update, BulkDelete, Calculate, SendEmail, SendKakao
- QuoteApi.php: Swagger 문서 (12개 스키마, 16개 엔드포인트)
- routes/api.php: 16개 라우트 등록
i18n 키 추가:
- error.php: quote_not_found, formula_* 등
- message.php: quote.* 성공 메시지
2025-12-04 22:03:40 +09:00
|
|
|
/**
|
|
|
|
|
* 자동산출 입력 스키마 조회
|
|
|
|
|
*/
|
|
|
|
|
public function calculationSchema(?string $productCategory = null)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($productCategory) {
|
|
|
|
|
return $this->calculationService->getInputSchema($productCategory);
|
|
|
|
|
}, __('message.fetched'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 견적서 PDF 생성
|
|
|
|
|
*/
|
|
|
|
|
public function generatePdf(int $id)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($id) {
|
|
|
|
|
return $this->documentService->generatePdf($id);
|
|
|
|
|
}, __('message.quote.pdf_generated'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 견적서 이메일 발송
|
|
|
|
|
*/
|
|
|
|
|
public function sendEmail(QuoteSendEmailRequest $request, int $id)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($request, $id) {
|
|
|
|
|
return $this->documentService->sendEmail($id, $request->validated());
|
|
|
|
|
}, __('message.quote_email_sent'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 견적서 카카오톡 발송
|
|
|
|
|
*/
|
|
|
|
|
public function sendKakao(QuoteSendKakaoRequest $request, int $id)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($request, $id) {
|
|
|
|
|
return $this->documentService->sendKakao($id, $request->validated());
|
|
|
|
|
}, __('message.quote_kakao_sent'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 발송 이력 조회
|
|
|
|
|
*/
|
|
|
|
|
public function sendHistory(int $id)
|
|
|
|
|
{
|
|
|
|
|
return ApiResponse::handle(function () use ($id) {
|
|
|
|
|
return $this->documentService->getSendHistory($id);
|
|
|
|
|
}, __('message.fetched'));
|
|
|
|
|
}
|
|
|
|
|
}
|