feat: [client] 거래처 API 2차 필드 추가 및 견적 계획 업데이트

- 거래처 유형(client_type), 연락처(mobile, fax), 담당자 정보 필드 추가
- 발주처 설정(account_id/password, payment_day) 필드 추가
- 약정 세금(tax_agreement, tax_amount, tax_start/end_date) 필드 추가
- 악성채권(bad_debt 관련 5개 필드) 정보 필드 추가
- Model, Service, FormRequest, Swagger 문서 업데이트
- 견적 API 계획에 문서 발송 API(email/fax/kakao) 요구사항 추가
This commit is contained in:
2025-12-04 21:13:58 +09:00
parent 96e9a0ba18
commit d164bb4c4a
7 changed files with 318 additions and 11 deletions

View File

@@ -17,13 +17,40 @@ public function rules(): array
'client_group_id' => 'nullable|integer', 'client_group_id' => 'nullable|integer',
'client_code' => 'required|string|max:50', 'client_code' => 'required|string|max:50',
'name' => 'required|string|max:100', 'name' => 'required|string|max:100',
'client_type' => 'nullable|in:매입,매출,매입매출',
// 연락처 정보
'contact_person' => 'nullable|string|max:100', 'contact_person' => 'nullable|string|max:100',
'phone' => 'nullable|string|max:20', 'phone' => 'nullable|string|max:20',
'mobile' => 'nullable|string|max:20',
'fax' => 'nullable|string|max:20',
'email' => 'nullable|email|max:100', 'email' => 'nullable|email|max:100',
'address' => 'nullable|string|max:255', 'address' => 'nullable|string|max:255',
// 담당자 정보
'manager_name' => 'nullable|string|max:50',
'manager_tel' => 'nullable|string|max:20',
'system_manager' => 'nullable|string|max:50',
// 발주처 설정
'account_id' => 'nullable|string|max:50',
'account_password' => 'nullable|string|max:255',
'purchase_payment_day' => 'nullable|string|max:20',
'sales_payment_day' => 'nullable|string|max:20',
// 사업자 정보
'business_no' => 'nullable|string|max:20', 'business_no' => 'nullable|string|max:20',
'business_type' => 'nullable|string|max:50', 'business_type' => 'nullable|string|max:50',
'business_item' => 'nullable|string|max:100', 'business_item' => 'nullable|string|max:100',
// 약정 세금
'tax_agreement' => 'nullable|boolean',
'tax_amount' => 'nullable|numeric|min:0',
'tax_start_date' => 'nullable|date',
'tax_end_date' => 'nullable|date|after_or_equal:tax_start_date',
// 악성채권 정보
'bad_debt' => 'nullable|boolean',
'bad_debt_amount' => 'nullable|numeric|min:0',
'bad_debt_receive_date' => 'nullable|date',
'bad_debt_end_date' => 'nullable|date|after_or_equal:bad_debt_receive_date',
'bad_debt_progress' => 'nullable|in:협의중,소송중,회수완료,대손처리',
// 기타
'memo' => 'nullable|string',
'is_active' => 'nullable|in:Y,N', 'is_active' => 'nullable|in:Y,N',
]; ];
} }

View File

@@ -17,12 +17,40 @@ public function rules(): array
'client_group_id' => 'nullable|integer', 'client_group_id' => 'nullable|integer',
'client_code' => 'sometimes|string|max:50', 'client_code' => 'sometimes|string|max:50',
'name' => 'sometimes|string|max:100', 'name' => 'sometimes|string|max:100',
'client_type' => 'nullable|in:매입,매출,매입매출',
// 연락처 정보
'contact_person' => 'nullable|string|max:100', 'contact_person' => 'nullable|string|max:100',
'phone' => 'nullable|string|max:20', 'phone' => 'nullable|string|max:20',
'mobile' => 'nullable|string|max:20',
'fax' => 'nullable|string|max:20',
'email' => 'nullable|email|max:100', 'email' => 'nullable|email|max:100',
'address' => 'nullable|string|max:255', 'business_no' => 'nullable|string|max:20', 'address' => 'nullable|string|max:255',
// 담당자 정보
'manager_name' => 'nullable|string|max:50',
'manager_tel' => 'nullable|string|max:20',
'system_manager' => 'nullable|string|max:50',
// 발주처 설정
'account_id' => 'nullable|string|max:50',
'account_password' => 'nullable|string|max:255',
'purchase_payment_day' => 'nullable|string|max:20',
'sales_payment_day' => 'nullable|string|max:20',
// 사업자 정보
'business_no' => 'nullable|string|max:20',
'business_type' => 'nullable|string|max:50', 'business_type' => 'nullable|string|max:50',
'business_item' => 'nullable|string|max:100', 'business_item' => 'nullable|string|max:100',
// 약정 세금
'tax_agreement' => 'nullable|boolean',
'tax_amount' => 'nullable|numeric|min:0',
'tax_start_date' => 'nullable|date',
'tax_end_date' => 'nullable|date|after_or_equal:tax_start_date',
// 악성채권 정보
'bad_debt' => 'nullable|boolean',
'bad_debt_amount' => 'nullable|numeric|min:0',
'bad_debt_receive_date' => 'nullable|date',
'bad_debt_end_date' => 'nullable|date|after_or_equal:bad_debt_receive_date',
'bad_debt_progress' => 'nullable|in:협의중,소송중,회수완료,대손처리',
// 기타
'memo' => 'nullable|string',
'is_active' => 'nullable|in:Y,N', 'is_active' => 'nullable|in:Y,N',
]; ];
} }

View File

@@ -17,16 +17,48 @@ class Client extends Model
'name', 'name',
'contact_person', 'contact_person',
'phone', 'phone',
'mobile',
'fax',
'email', 'email',
'address', 'address',
'account_id',
'account_password',
'purchase_payment_day',
'sales_payment_day',
'business_no', 'business_no',
'business_type', 'business_type',
'business_item', 'business_item',
'tax_agreement',
'tax_amount',
'tax_start_date',
'tax_end_date',
'bad_debt',
'bad_debt_amount',
'bad_debt_receive_date',
'bad_debt_end_date',
'bad_debt_progress',
'memo',
'is_active', 'is_active',
'client_type',
'manager_name',
'manager_tel',
'system_manager',
]; ];
protected $casts = [ protected $casts = [
'is_active' => 'boolean', 'is_active' => 'boolean',
'tax_agreement' => 'boolean',
'tax_amount' => 'decimal:2',
'tax_start_date' => 'date',
'tax_end_date' => 'date',
'bad_debt' => 'boolean',
'bad_debt_amount' => 'decimal:2',
'bad_debt_receive_date' => 'date',
'bad_debt_end_date' => 'date',
];
protected $hidden = [
'account_password',
]; ];
// ClientGroup 관계 // ClientGroup 관계

View File

@@ -59,13 +59,33 @@ public function store(array $params)
$v = Validator::make($params, [ $v = Validator::make($params, [
'client_code' => 'required|string|max:50', 'client_code' => 'required|string|max:50',
'name' => 'required|string|max:100', 'name' => 'required|string|max:100',
'client_type' => 'nullable|in:매입,매출,매입매출',
'contact_person' => 'nullable|string|max:50', 'contact_person' => 'nullable|string|max:50',
'phone' => 'nullable|string|max:30', 'phone' => 'nullable|string|max:30',
'mobile' => 'nullable|string|max:20',
'fax' => 'nullable|string|max:20',
'email' => 'nullable|email|max:80', 'email' => 'nullable|email|max:80',
'address' => 'nullable|string|max:255', 'address' => 'nullable|string|max:255',
'manager_name' => 'nullable|string|max:50',
'manager_tel' => 'nullable|string|max:20',
'system_manager' => 'nullable|string|max:50',
'account_id' => 'nullable|string|max:50',
'account_password' => 'nullable|string|max:255',
'purchase_payment_day' => 'nullable|string|max:20',
'sales_payment_day' => 'nullable|string|max:20',
'business_no' => 'nullable|string|max:20', 'business_no' => 'nullable|string|max:20',
'business_type' => 'nullable|string|max:50', 'business_type' => 'nullable|string|max:50',
'business_item' => 'nullable|string|max:100', 'business_item' => 'nullable|string|max:100',
'tax_agreement' => 'nullable|boolean',
'tax_amount' => 'nullable|numeric|min:0',
'tax_start_date' => 'nullable|date',
'tax_end_date' => 'nullable|date',
'bad_debt' => 'nullable|boolean',
'bad_debt_amount' => 'nullable|numeric|min:0',
'bad_debt_receive_date' => 'nullable|date',
'bad_debt_end_date' => 'nullable|date',
'bad_debt_progress' => 'nullable|in:협의중,소송중,회수완료,대손처리',
'memo' => 'nullable|string',
'is_active' => 'nullable|in:Y,N', 'is_active' => 'nullable|in:Y,N',
]); ]);
@@ -103,13 +123,33 @@ public function update(int $id, array $params)
$v = Validator::make($params, [ $v = Validator::make($params, [
'client_code' => 'sometimes|required|string|max:50', 'client_code' => 'sometimes|required|string|max:50',
'name' => 'sometimes|required|string|max:100', 'name' => 'sometimes|required|string|max:100',
'client_type' => 'nullable|in:매입,매출,매입매출',
'contact_person' => 'nullable|string|max:50', 'contact_person' => 'nullable|string|max:50',
'phone' => 'nullable|string|max:30', 'phone' => 'nullable|string|max:30',
'mobile' => 'nullable|string|max:20',
'fax' => 'nullable|string|max:20',
'email' => 'nullable|email|max:80', 'email' => 'nullable|email|max:80',
'address' => 'nullable|string|max:255', 'address' => 'nullable|string|max:255',
'manager_name' => 'nullable|string|max:50',
'manager_tel' => 'nullable|string|max:20',
'system_manager' => 'nullable|string|max:50',
'account_id' => 'nullable|string|max:50',
'account_password' => 'nullable|string|max:255',
'purchase_payment_day' => 'nullable|string|max:20',
'sales_payment_day' => 'nullable|string|max:20',
'business_no' => 'nullable|string|max:20', 'business_no' => 'nullable|string|max:20',
'business_type' => 'nullable|string|max:50', 'business_type' => 'nullable|string|max:50',
'business_item' => 'nullable|string|max:100', 'business_item' => 'nullable|string|max:100',
'tax_agreement' => 'nullable|boolean',
'tax_amount' => 'nullable|numeric|min:0',
'tax_start_date' => 'nullable|date',
'tax_end_date' => 'nullable|date',
'bad_debt' => 'nullable|boolean',
'bad_debt_amount' => 'nullable|numeric|min:0',
'bad_debt_receive_date' => 'nullable|date',
'bad_debt_end_date' => 'nullable|date',
'bad_debt_progress' => 'nullable|in:협의중,소송중,회수완료,대손처리',
'memo' => 'nullable|string',
'is_active' => 'nullable|in:Y,N', 'is_active' => 'nullable|in:Y,N',
]); ]);

View File

@@ -15,13 +15,32 @@
* @OA\Property(property="client_group_id", type="integer", nullable=true, example=1, description="고객 그룹 ID"), * @OA\Property(property="client_group_id", type="integer", nullable=true, example=1, description="고객 그룹 ID"),
* @OA\Property(property="client_code", type="string", example="CLIENT_001"), * @OA\Property(property="client_code", type="string", example="CLIENT_001"),
* @OA\Property(property="name", type="string", example="거래처명"), * @OA\Property(property="name", type="string", example="거래처명"),
* @OA\Property(property="client_type", type="string", enum={"매입", "매출", "매입매출"}, example="매입", description="거래처 유형"),
* @OA\Property(property="contact_person", type="string", nullable=true, example="홍길동"), * @OA\Property(property="contact_person", type="string", nullable=true, example="홍길동"),
* @OA\Property(property="phone", type="string", nullable=true, example="010-1234-5678"), * @OA\Property(property="phone", type="string", nullable=true, example="02-1234-5678"),
* @OA\Property(property="mobile", type="string", nullable=true, example="010-1234-5678", description="모바일 번호"),
* @OA\Property(property="fax", type="string", nullable=true, example="02-1234-5679", description="팩스 번호"),
* @OA\Property(property="email", type="string", nullable=true, example="client@example.com"), * @OA\Property(property="email", type="string", nullable=true, example="client@example.com"),
* @OA\Property(property="address", type="string", nullable=true, example="서울시 강남구"), * @OA\Property(property="address", type="string", nullable=true, example="서울시 강남구"),
* @OA\Property(property="manager_name", type="string", nullable=true, example="김담당", description="담당자명"),
* @OA\Property(property="manager_tel", type="string", nullable=true, example="010-9876-5432", description="담당자 전화"),
* @OA\Property(property="system_manager", type="string", nullable=true, example="박시스템", description="시스템 관리자"),
* @OA\Property(property="account_id", type="string", nullable=true, example="user123", description="계정 ID"),
* @OA\Property(property="purchase_payment_day", type="string", nullable=true, example="매월 25일", description="매입 결제일"),
* @OA\Property(property="sales_payment_day", type="string", nullable=true, example="매월 말일", description="매출 결제일"),
* @OA\Property(property="business_no", type="string", nullable=true, maxLength=20, example="123-45-67890", description="사업자등록번호"), * @OA\Property(property="business_no", type="string", nullable=true, maxLength=20, example="123-45-67890", description="사업자등록번호"),
* @OA\Property(property="business_type", type="string", nullable=true, maxLength=50, example="제조업", description="업태"), * @OA\Property(property="business_type", type="string", nullable=true, maxLength=50, example="제조업", description="업태"),
* @OA\Property(property="business_item", type="string", nullable=true, maxLength=100, example="전자부품", description="업종"), * @OA\Property(property="business_item", type="string", nullable=true, maxLength=100, example="전자부품", description="업종"),
* @OA\Property(property="tax_agreement", type="boolean", example=false, description="세금 약정 여부"),
* @OA\Property(property="tax_amount", type="number", format="float", nullable=true, example=1000000, description="약정 금액"),
* @OA\Property(property="tax_start_date", type="string", format="date", nullable=true, example="2025-01-01", description="약정 시작일"),
* @OA\Property(property="tax_end_date", type="string", format="date", nullable=true, example="2025-12-31", description="약정 종료일"),
* @OA\Property(property="bad_debt", type="boolean", example=false, description="악성채권 여부"),
* @OA\Property(property="bad_debt_amount", type="number", format="float", nullable=true, example=500000, description="악성채권 금액"),
* @OA\Property(property="bad_debt_receive_date", type="string", format="date", nullable=true, example="2024-06-01", description="채권 발생일"),
* @OA\Property(property="bad_debt_end_date", type="string", format="date", nullable=true, example="2025-06-01", description="채권 만료일"),
* @OA\Property(property="bad_debt_progress", type="string", enum={"협의중", "소송중", "회수완료", "대손처리"}, nullable=true, description="진행 상태"),
* @OA\Property(property="memo", type="string", nullable=true, example="특이사항 메모", description="메모"),
* @OA\Property(property="is_active", type="string", enum={"Y", "N"}, example="Y"), * @OA\Property(property="is_active", type="string", enum={"Y", "N"}, example="Y"),
* @OA\Property(property="created_at", type="string", example="2025-10-01 12:00:00"), * @OA\Property(property="created_at", type="string", example="2025-10-01 12:00:00"),
* @OA\Property(property="updated_at", type="string", example="2025-10-01 12:00:00") * @OA\Property(property="updated_at", type="string", example="2025-10-01 12:00:00")
@@ -70,13 +89,33 @@
* @OA\Property(property="client_group_id", type="integer", nullable=true, example=1, description="고객 그룹 ID"), * @OA\Property(property="client_group_id", type="integer", nullable=true, example=1, description="고객 그룹 ID"),
* @OA\Property(property="client_code", type="string", maxLength=50, example="CLIENT_001"), * @OA\Property(property="client_code", type="string", maxLength=50, example="CLIENT_001"),
* @OA\Property(property="name", type="string", maxLength=100, example="거래처명"), * @OA\Property(property="name", type="string", maxLength=100, example="거래처명"),
* @OA\Property(property="client_type", type="string", enum={"매입", "매출", "매입매출"}, nullable=true, example="매입", description="거래처 유형"),
* @OA\Property(property="contact_person", type="string", nullable=true, maxLength=100, example="홍길동"), * @OA\Property(property="contact_person", type="string", nullable=true, maxLength=100, example="홍길동"),
* @OA\Property(property="phone", type="string", nullable=true, maxLength=20, example="010-1234-5678"), * @OA\Property(property="phone", type="string", nullable=true, maxLength=20, example="02-1234-5678"),
* @OA\Property(property="mobile", type="string", nullable=true, maxLength=20, example="010-1234-5678", description="모바일 번호"),
* @OA\Property(property="fax", type="string", nullable=true, maxLength=20, example="02-1234-5679", description="팩스 번호"),
* @OA\Property(property="email", type="string", nullable=true, maxLength=100, example="client@example.com"), * @OA\Property(property="email", type="string", nullable=true, maxLength=100, example="client@example.com"),
* @OA\Property(property="address", type="string", nullable=true, maxLength=255, example="서울시 강남구"), * @OA\Property(property="address", type="string", nullable=true, maxLength=255, example="서울시 강남구"),
* @OA\Property(property="manager_name", type="string", nullable=true, maxLength=50, example="김담당", description="담당자명"),
* @OA\Property(property="manager_tel", type="string", nullable=true, maxLength=20, example="010-9876-5432", description="담당자 전화"),
* @OA\Property(property="system_manager", type="string", nullable=true, maxLength=50, example="박시스템", description="시스템 관리자"),
* @OA\Property(property="account_id", type="string", nullable=true, maxLength=50, example="user123", description="계정 ID"),
* @OA\Property(property="account_password", type="string", nullable=true, maxLength=255, example="password123", description="계정 비밀번호"),
* @OA\Property(property="purchase_payment_day", type="string", nullable=true, maxLength=20, example="매월 25일", description="매입 결제일"),
* @OA\Property(property="sales_payment_day", type="string", nullable=true, maxLength=20, example="매월 말일", description="매출 결제일"),
* @OA\Property(property="business_no", type="string", nullable=true, maxLength=20, example="123-45-67890", description="사업자등록번호"), * @OA\Property(property="business_no", type="string", nullable=true, maxLength=20, example="123-45-67890", description="사업자등록번호"),
* @OA\Property(property="business_type", type="string", nullable=true, maxLength=50, example="제조업", description="업태"), * @OA\Property(property="business_type", type="string", nullable=true, maxLength=50, example="제조업", description="업태"),
* @OA\Property(property="business_item", type="string", nullable=true, maxLength=100, example="전자부품", description="업종"), * @OA\Property(property="business_item", type="string", nullable=true, maxLength=100, example="전자부품", description="업종"),
* @OA\Property(property="tax_agreement", type="boolean", nullable=true, example=false, description="세금 약정 여부"),
* @OA\Property(property="tax_amount", type="number", format="float", nullable=true, example=1000000, description="약정 금액"),
* @OA\Property(property="tax_start_date", type="string", format="date", nullable=true, example="2025-01-01", description="약정 시작일"),
* @OA\Property(property="tax_end_date", type="string", format="date", nullable=true, example="2025-12-31", description="약정 종료일"),
* @OA\Property(property="bad_debt", type="boolean", nullable=true, example=false, description="악성채권 여부"),
* @OA\Property(property="bad_debt_amount", type="number", format="float", nullable=true, example=500000, description="악성채권 금액"),
* @OA\Property(property="bad_debt_receive_date", type="string", format="date", nullable=true, example="2024-06-01", description="채권 발생일"),
* @OA\Property(property="bad_debt_end_date", type="string", format="date", nullable=true, example="2025-06-01", description="채권 만료일"),
* @OA\Property(property="bad_debt_progress", type="string", enum={"협의중", "소송중", "회수완료", "대손처리"}, nullable=true, description="진행 상태"),
* @OA\Property(property="memo", type="string", nullable=true, example="특이사항 메모", description="메모"),
* @OA\Property(property="is_active", type="string", enum={"Y", "N"}, example="Y") * @OA\Property(property="is_active", type="string", enum={"Y", "N"}, example="Y")
* ) * )
* *
@@ -87,13 +126,33 @@
* @OA\Property(property="client_group_id", type="integer", nullable=true, example=1, description="고객 그룹 ID"), * @OA\Property(property="client_group_id", type="integer", nullable=true, example=1, description="고객 그룹 ID"),
* @OA\Property(property="client_code", type="string", maxLength=50), * @OA\Property(property="client_code", type="string", maxLength=50),
* @OA\Property(property="name", type="string", maxLength=100), * @OA\Property(property="name", type="string", maxLength=100),
* @OA\Property(property="client_type", type="string", enum={"매입", "매출", "매입매출"}, nullable=true, description="거래처 유형"),
* @OA\Property(property="contact_person", type="string", nullable=true, maxLength=100), * @OA\Property(property="contact_person", type="string", nullable=true, maxLength=100),
* @OA\Property(property="phone", type="string", nullable=true, maxLength=20), * @OA\Property(property="phone", type="string", nullable=true, maxLength=20),
* @OA\Property(property="mobile", type="string", nullable=true, maxLength=20, description="모바일 번호"),
* @OA\Property(property="fax", type="string", nullable=true, maxLength=20, description="팩스 번호"),
* @OA\Property(property="email", type="string", nullable=true, maxLength=100), * @OA\Property(property="email", type="string", nullable=true, maxLength=100),
* @OA\Property(property="address", type="string", nullable=true, maxLength=255), * @OA\Property(property="address", type="string", nullable=true, maxLength=255),
* @OA\Property(property="manager_name", type="string", nullable=true, maxLength=50, description="담당자명"),
* @OA\Property(property="manager_tel", type="string", nullable=true, maxLength=20, description="담당자 전화"),
* @OA\Property(property="system_manager", type="string", nullable=true, maxLength=50, description="시스템 관리자"),
* @OA\Property(property="account_id", type="string", nullable=true, maxLength=50, description="계정 ID"),
* @OA\Property(property="account_password", type="string", nullable=true, maxLength=255, description="계정 비밀번호"),
* @OA\Property(property="purchase_payment_day", type="string", nullable=true, maxLength=20, description="매입 결제일"),
* @OA\Property(property="sales_payment_day", type="string", nullable=true, maxLength=20, description="매출 결제일"),
* @OA\Property(property="business_no", type="string", nullable=true, maxLength=20, description="사업자등록번호"), * @OA\Property(property="business_no", type="string", nullable=true, maxLength=20, description="사업자등록번호"),
* @OA\Property(property="business_type", type="string", nullable=true, maxLength=50, description="업태"), * @OA\Property(property="business_type", type="string", nullable=true, maxLength=50, description="업태"),
* @OA\Property(property="business_item", type="string", nullable=true, maxLength=100, description="업종"), * @OA\Property(property="business_item", type="string", nullable=true, maxLength=100, description="업종"),
* @OA\Property(property="tax_agreement", type="boolean", nullable=true, description="세금 약정 여부"),
* @OA\Property(property="tax_amount", type="number", format="float", nullable=true, description="약정 금액"),
* @OA\Property(property="tax_start_date", type="string", format="date", nullable=true, description="약정 시작일"),
* @OA\Property(property="tax_end_date", type="string", format="date", nullable=true, description="약정 종료일"),
* @OA\Property(property="bad_debt", type="boolean", nullable=true, description="악성채권 여부"),
* @OA\Property(property="bad_debt_amount", type="number", format="float", nullable=true, description="악성채권 금액"),
* @OA\Property(property="bad_debt_receive_date", type="string", format="date", nullable=true, description="채권 발생일"),
* @OA\Property(property="bad_debt_end_date", type="string", format="date", nullable=true, description="채권 만료일"),
* @OA\Property(property="bad_debt_progress", type="string", enum={"협의중", "소송중", "회수완료", "대손처리"}, nullable=true, description="진행 상태"),
* @OA\Property(property="memo", type="string", nullable=true, description="메모"),
* @OA\Property(property="is_active", type="string", enum={"Y", "N"}) * @OA\Property(property="is_active", type="string", enum={"Y", "N"})
* ) * )
*/ */

View File

@@ -2,7 +2,7 @@ # 견적관리 API 개발 계획서
> **작성일**: 2025-12-04 > **작성일**: 2025-12-04
> **요청서**: `docs/front/[API-2025-12-04] quote-api-request.md` > **요청서**: `docs/front/[API-2025-12-04] quote-api-request.md`
> **상태**: 🔄 Phase 1 진행 중 → 요청 변경으로 재계획 필요 > **상태**: Phase 1 완료 → Phase 2 진행 중
--- ---
@@ -30,9 +30,28 @@ #### 2. Model 생성 완료
- 스코프 메서드 (draft, finalized, search, dateRange 등) - 스코프 메서드 (draft, finalized, search, dateRange 등)
- 상태 검증 메서드 (isEditable, isDeletable, isFinalizable, isConvertible) - 상태 검증 메서드 (isEditable, isDeletable, isFinalizable, isConvertible)
### ⏸️ 일시 중단 ### 🆕 변경 요청 분석 (2025-12-04)
- **사유**: 요청서 변경으로 계획 재수립 필요
- **다음 단계**: 변경된 요청서 확인 후 계획 비교 분석 요청서에서 **문서 발송 API (Section 3.5)**가 신규 추가됨:
| Method | Endpoint | 설명 | 비고 |
|--------|----------|------|------|
| `POST` | `/api/v1/quotes/{id}/send/email` | 이메일 발송 | 첨부파일 포함 |
| `POST` | `/api/v1/quotes/{id}/send/fax` | 팩스 발송 | 팩스 서비스 연동 |
| `POST` | `/api/v1/quotes/{id}/send/kakao` | 카카오톡 발송 | 알림톡/친구톡 |
**영향 범위:**
- Phase 2: `QuoteDocumentService` 추가 필요
- Phase 3: 문서 발송 관련 Controller 메서드 + FormRequest 추가
- Phase 4: Swagger 문서에 발송 API 추가
**개발 우선순위 조정:**
- P1: 견적 CRUD, 자동 산출, 견적번호 생성 (기존 유지)
- P2: 상태 관리, 수정 이력 (기존 유지)
- P3: 문서 출력 + **문서 발송 API** (신규 추가)
### ⏭️ 다음 단계
- Phase 2: Service Layer 구현 (QuoteService, QuoteCalculationService, QuoteNumberService)
--- ---
@@ -272,6 +291,14 @@ ### 3.4 문서 출력 (P3 - 후순위)
| `GET` | `/api/v1/quotes/{id}/document/calculation` | 산출내역서 PDF | | `GET` | `/api/v1/quotes/{id}/document/calculation` | 산출내역서 PDF |
| `GET` | `/api/v1/quotes/{id}/document/purchase-order` | 발주서 PDF | | `GET` | `/api/v1/quotes/{id}/document/purchase-order` | 발주서 PDF |
### 3.5 문서 발송 (P3 - 신규 추가)
| Method | Endpoint | 설명 | 비고 |
|--------|----------|------|------|
| `POST` | `/api/v1/quotes/{id}/send/email` | 이메일 발송 | 첨부파일 포함 |
| `POST` | `/api/v1/quotes/{id}/send/fax` | 팩스 발송 | 팩스 서비스 연동 |
| `POST` | `/api/v1/quotes/{id}/send/kakao` | 카카오톡 발송 | 알림톡/친구톡 |
--- ---
## 4. 개발 Phase 계획 ## 4. 개발 Phase 계획
@@ -303,6 +330,7 @@ ### Phase 2: 핵심 서비스 (Service Layer)
2. QuoteCalculationService - 자동산출 (FormulaEvaluator 연동) 2. QuoteCalculationService - 자동산출 (FormulaEvaluator 연동)
3. QuoteNumberService - 번호채번 3. QuoteNumberService - 번호채번
4. FormulaEvaluatorService 이식 (mng → api) 4. FormulaEvaluatorService 이식 (mng → api)
5. QuoteDocumentService - 문서 생성 및 발송 (신규 추가)
**파일 목록:** **파일 목록:**
``` ```
@@ -310,12 +338,15 @@ ### Phase 2: 핵심 서비스 (Service Layer)
├── QuoteService.php ├── QuoteService.php
├── QuoteCalculationService.php ├── QuoteCalculationService.php
├── QuoteNumberService.php ├── QuoteNumberService.php
├── QuoteDocumentService.php (신규 - 문서 생성/발송)
└── FormulaEvaluatorService.php (mng에서 이식) └── FormulaEvaluatorService.php (mng에서 이식)
``` ```
**의존성:** **의존성:**
- PricingService (단가 조회) - PricingService (단가 조회)
- ClientService (발주처 연동) - ClientService (발주처 연동)
- MailService (이메일 발송)
- NotificationService (카카오톡/팩스 발송)
### Phase 3: API 구현 (Controller + Routes) ### Phase 3: API 구현 (Controller + Routes)
@@ -323,6 +354,7 @@ ### Phase 3: API 구현 (Controller + Routes)
1. QuoteController 구현 1. QuoteController 구현
2. FormRequest 생성 (검증 규칙) 2. FormRequest 생성 (검증 규칙)
3. 라우트 추가 3. 라우트 추가
4. 문서 발송 엔드포인트 추가 (신규)
**파일 목록:** **파일 목록:**
``` ```
@@ -334,7 +366,10 @@ ### Phase 3: API 구현 (Controller + Routes)
├── QuoteStoreRequest.php ├── QuoteStoreRequest.php
├── QuoteUpdateRequest.php ├── QuoteUpdateRequest.php
├── QuoteCalculateRequest.php ├── QuoteCalculateRequest.php
── QuoteFinalizeRequest.php ── QuoteFinalizeRequest.php
├── QuoteSendEmailRequest.php (신규)
├── QuoteSendFaxRequest.php (신규)
└── QuoteSendKakaoRequest.php (신규)
routes/ routes/
└── api_v1.php (라우트 추가) └── api_v1.php (라우트 추가)
@@ -417,9 +452,9 @@ ## 6. 예상 산출물 요약
|------|------|------| |------|------|------|
| 마이그레이션 | 3개 | quotes, quote_items, quote_revisions | | 마이그레이션 | 3개 | quotes, quote_items, quote_revisions |
| Model | 3개 | Quote, QuoteItem, QuoteRevision | | Model | 3개 | Quote, QuoteItem, QuoteRevision |
| Service | 4개 | QuoteService, QuoteCalculationService, QuoteNumberService, FormulaEvaluatorService | | Service | 5개 | QuoteService, QuoteCalculationService, QuoteNumberService, FormulaEvaluatorService, **QuoteDocumentService** |
| Controller | 1개 | QuoteController | | Controller | 1개 | QuoteController |
| FormRequest | 5개 | Index, Store, Update, Calculate, Finalize | | FormRequest | 8개 | Index, Store, Update, Calculate, Finalize, **SendEmail, SendFax, SendKakao** |
| Swagger | 1개 | QuoteApi.php | | Swagger | 1개 | QuoteApi.php |
| i18n | 2개 | message.php, error.php (키 추가) | | i18n | 2개 | message.php, error.php (키 추가) |
@@ -446,7 +481,11 @@ ### Q3. 문서 출력 PDF 라이브러리
- 대안: Dompdf (HTML → PDF 변환) - 대안: Dompdf (HTML → PDF 변환)
### Q4. 알림 (이메일/카카오톡) ### Q4. 알림 (이메일/카카오톡)
**답변:** 현재 요구사항에 없음, 추후 확장 가능 **답변:** ✅ 요청서 Section 3.5에 신규 추가됨
- 이메일 발송: `POST /api/v1/quotes/{id}/send/email`
- 팩스 발송: `POST /api/v1/quotes/{id}/send/fax`
- 카카오톡 발송: `POST /api/v1/quotes/{id}/send/kakao`
- P3 우선순위로 문서 출력과 함께 개발 예정
--- ---

View File

@@ -0,0 +1,82 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
* 거래처 2차 확장 필드 추가 (sam-design 기준)
*/
public function up(): void
{
Schema::table('clients', function (Blueprint $table) {
// 기본 정보
$table->enum('client_type', ['매입', '매출', '매입매출'])->default('매입')->after('is_active')->comment('거래처 유형');
// 연락처 정보
$table->string('mobile', 20)->nullable()->after('phone')->comment('모바일 번호');
$table->string('fax', 20)->nullable()->after('mobile')->comment('팩스 번호');
// 담당자 정보
$table->string('manager_name', 50)->nullable()->after('contact_person')->comment('담당자명');
$table->string('manager_tel', 20)->nullable()->after('manager_name')->comment('담당자 전화');
$table->string('system_manager', 50)->nullable()->after('manager_tel')->comment('시스템 관리자');
// 발주처 설정
$table->string('account_id', 50)->nullable()->after('address')->comment('계정 ID');
$table->string('account_password', 255)->nullable()->after('account_id')->comment('비밀번호 (암호화)');
$table->string('purchase_payment_day', 20)->nullable()->after('account_password')->comment('매입 결제일');
$table->string('sales_payment_day', 20)->nullable()->after('purchase_payment_day')->comment('매출 결제일');
// 약정 세금
$table->boolean('tax_agreement')->default(false)->after('business_item')->comment('세금 약정 여부');
$table->decimal('tax_amount', 15, 2)->nullable()->after('tax_agreement')->comment('약정 금액');
$table->date('tax_start_date')->nullable()->after('tax_amount')->comment('약정 시작일');
$table->date('tax_end_date')->nullable()->after('tax_start_date')->comment('약정 종료일');
// 악성채권 정보
$table->boolean('bad_debt')->default(false)->after('tax_end_date')->comment('악성채권 여부');
$table->decimal('bad_debt_amount', 15, 2)->nullable()->after('bad_debt')->comment('악성채권 금액');
$table->date('bad_debt_receive_date')->nullable()->after('bad_debt_amount')->comment('채권 발생일');
$table->date('bad_debt_end_date')->nullable()->after('bad_debt_receive_date')->comment('채권 만료일');
$table->enum('bad_debt_progress', ['협의중', '소송중', '회수완료', '대손처리'])->nullable()->after('bad_debt_end_date')->comment('진행 상태');
// 기타 정보
$table->text('memo')->nullable()->after('bad_debt_progress')->comment('메모');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('clients', function (Blueprint $table) {
$table->dropColumn([
'client_type',
'mobile',
'fax',
'manager_name',
'manager_tel',
'system_manager',
'account_id',
'account_password',
'purchase_payment_day',
'sales_payment_day',
'tax_agreement',
'tax_amount',
'tax_start_date',
'tax_end_date',
'bad_debt',
'bad_debt_amount',
'bad_debt_receive_date',
'bad_debt_end_date',
'bad_debt_progress',
'memo',
]);
});
}
};