feat: 견적확정 밸리데이션, 수주등록 개소그룹, 작업지시 개선
- 견적확정 시 업체명/현장명/담당자/연락처 프론트 밸리데이션 추가 - 견적확정 후 수주등록 버튼 동적 전환 - 수주등록 품목 개소별(floor+code) 그룹핑 수정 - 작업지시 상세 quantity 문자열→숫자 변환 (formatQuantity) - 작업지시 탭 카운트 초기 로딩 시 전체 표시 (by_process 활용) - 작업지시 상세 개소별/품목별 합산 테이블 추가 - 작업자 화면 API 연동 및 목업 데이터 분리 - 입고관리 완료건 수정, 재고현황 개선
This commit is contained in:
@@ -70,13 +70,10 @@ export function ItemSearchModal({
|
||||
}
|
||||
}, [itemType]);
|
||||
|
||||
// 검색어 유효성 검사: 영문 1자 이상 또는 한글 1자 이상
|
||||
// 검색어 유효성 검사: 영문, 한글, 숫자 1자 이상
|
||||
const isValidSearchQuery = useCallback((query: string) => {
|
||||
if (!query) return false;
|
||||
// 영문 1자 이상 또는 한글 1자 이상
|
||||
const hasEnglish = /[a-zA-Z]/.test(query);
|
||||
const hasKorean = /[가-힣ㄱ-ㅎㅏ-ㅣ]/.test(query);
|
||||
return hasEnglish || hasKorean;
|
||||
if (!query || !query.trim()) return false;
|
||||
return /[a-zA-Z가-힣ㄱ-ㅎㅏ-ㅣ0-9]/.test(query);
|
||||
}, []);
|
||||
|
||||
// 모달 열릴 때 초기화 (자동 로드 안함)
|
||||
@@ -167,7 +164,7 @@ export function ItemSearchModal({
|
||||
{!searchQuery
|
||||
? "품목코드 또는 품목명을 입력하세요"
|
||||
: !isValidSearchQuery(searchQuery)
|
||||
? "영문 또는 한글 1자 이상 입력하세요"
|
||||
? "영문, 한글 또는 숫자 1자 이상 입력하세요"
|
||||
: "검색 결과가 없습니다"}
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -188,8 +188,8 @@ export function QuoteFooterBar({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* 견적완료 - edit 모드에서만 표시 (final 상태가 아닐 때만) */}
|
||||
{!isViewMode && status !== "final" && (
|
||||
{/* 견적확정 - final 상태가 아닐 때 표시 (view/edit 모두) */}
|
||||
{status !== "final" && (
|
||||
<Button
|
||||
onClick={onFinalize}
|
||||
disabled={isSaving || totalAmount === 0}
|
||||
@@ -201,7 +201,7 @@ export function QuoteFooterBar({
|
||||
) : (
|
||||
<Check className="h-4 w-4 md:mr-2" />
|
||||
)}
|
||||
<span className="hidden md:inline">견적완료</span>
|
||||
<span className="hidden md:inline">{isViewMode ? "견적확정" : "견적완료"}</span>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
|
||||
@@ -56,7 +56,8 @@ import { quoteRegistrationCreateConfig, quoteRegistrationEditConfig } from "./qu
|
||||
import { FormSection } from "@/components/organisms/FormSection";
|
||||
import { FormFieldGrid } from "@/components/organisms/FormFieldGrid";
|
||||
import { FormField } from "../molecules/FormField";
|
||||
import { getFinishedGoods, calculateBomBulk, getSiteNames, type FinishedGoods, type BomCalculationResult } from "./actions";
|
||||
import { getFinishedGoods, calculateBomBulk, getSiteNames, type FinishedGoods } from "./actions";
|
||||
import type { BomCalculationResult } from "./types";
|
||||
import { getClients } from "../accounting/VendorManagement/actions";
|
||||
import { isNextRedirectError } from "@/lib/utils/redirect-error";
|
||||
import type { Vendor } from "../accounting/VendorManagement";
|
||||
|
||||
@@ -693,6 +693,19 @@ export function QuoteRegistrationV2({
|
||||
const handleSave = useCallback(async (saveType: "temporary" | "final") => {
|
||||
if (!onSave) return;
|
||||
|
||||
// 확정 시 필수 필드 밸리데이션
|
||||
if (saveType === "final") {
|
||||
const missing: string[] = [];
|
||||
if (!formData.clientName?.trim()) missing.push("업체명");
|
||||
if (!formData.siteName?.trim()) missing.push("현장명");
|
||||
if (!formData.managerName?.trim()) missing.push("담당자");
|
||||
if (!formData.managerContact?.trim()) missing.push("연락처");
|
||||
if (missing.length > 0) {
|
||||
toast.error(`견적확정을 위해 다음 항목을 입력해주세요: ${missing.join(", ")}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const dataToSave: QuoteFormDataV2 = {
|
||||
@@ -700,7 +713,10 @@ export function QuoteRegistrationV2({
|
||||
status: saveType === "temporary" ? "temporary" : "final",
|
||||
};
|
||||
await onSave(dataToSave, saveType);
|
||||
toast.success(saveType === "temporary" ? "저장되었습니다." : "견적이 확정되었습니다.");
|
||||
// 확정 성공 시 상태 즉시 반영 → 견적확정 버튼 → 수주등록 버튼으로 전환
|
||||
if (saveType === "final") {
|
||||
setFormData(prev => ({ ...prev, status: "final" }));
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
const message = error instanceof Error ? error.message : "저장 중 오류가 발생했습니다.";
|
||||
|
||||
@@ -902,8 +902,8 @@ export interface BomCalculateItem {
|
||||
inspectionFee?: number;
|
||||
}
|
||||
|
||||
// BomCalculationResult는 types.ts에서 import하고 re-export
|
||||
export type { BomCalculationResult } from './types';
|
||||
// BomCalculationResult는 types.ts에서 직접 import하세요
|
||||
// import type { BomCalculationResult } from './types';
|
||||
|
||||
// API 서버 응답 구조 (QuoteCalculationService::calculateBomBulk)
|
||||
export interface BomBulkResponse {
|
||||
|
||||
Reference in New Issue
Block a user