feat: 입찰(Bidding) 관리 기능 구현
- Bidding 모델, 서비스, 컨트롤러, FormRequest 추가 - 마이그레이션 및 시더 추가 - Swagger API 문서 추가 - 견적에서 입찰 전환 시 중복 체크 로직 추가 - per_page 파라미터 100 초과 시 자동 클램핑 처리 - error.bidding.already_registered 에러 메시지 추가
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('biddings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->comment('테넌트 ID');
|
||||
|
||||
// 기본 정보
|
||||
$table->string('bidding_code', 50)->comment('입찰번호 (예: BID-2025-001)');
|
||||
$table->foreignId('quote_id')->nullable()->comment('연결된 견적 ID (quotes.id)');
|
||||
|
||||
// 거래처/현장
|
||||
$table->unsignedBigInteger('client_id')->nullable()->comment('거래처 ID');
|
||||
$table->string('client_name', 100)->nullable()->comment('거래처명 (스냅샷)');
|
||||
$table->string('project_name', 200)->nullable()->comment('현장명');
|
||||
|
||||
// 입찰 정보
|
||||
$table->date('bidding_date')->nullable()->comment('입찰일');
|
||||
$table->date('bid_date')->nullable()->comment('입찰일 (레거시 호환)');
|
||||
$table->date('submission_date')->nullable()->comment('투찰일');
|
||||
$table->date('confirm_date')->nullable()->comment('확정일');
|
||||
$table->unsignedInteger('total_count')->default(0)->comment('총 개소');
|
||||
$table->decimal('bidding_amount', 15, 2)->default(0)->comment('입찰금액');
|
||||
|
||||
// 상태 (waiting/submitted/failed/invalid/awarded/hold)
|
||||
$table->string('status', 20)->default('waiting')->comment('상태');
|
||||
|
||||
// 입찰자
|
||||
$table->unsignedBigInteger('bidder_id')->nullable()->comment('입찰자 ID');
|
||||
$table->string('bidder_name', 50)->nullable()->comment('입찰자명 (스냅샷)');
|
||||
|
||||
// 공사기간
|
||||
$table->date('construction_start_date')->nullable()->comment('공사 시작일');
|
||||
$table->date('construction_end_date')->nullable()->comment('공사 종료일');
|
||||
$table->string('vat_type', 20)->default('excluded')->comment('부가세 (included/excluded)');
|
||||
|
||||
// 비고
|
||||
$table->text('remarks')->nullable()->comment('비고');
|
||||
|
||||
// 견적 데이터 스냅샷 (JSON)
|
||||
$table->json('expense_items')->nullable()->comment('공과 항목 스냅샷');
|
||||
$table->json('estimate_detail_items')->nullable()->comment('견적 상세 항목 스냅샷');
|
||||
|
||||
// 감사
|
||||
$table->foreignId('created_by')->nullable()->comment('생성자');
|
||||
$table->foreignId('updated_by')->nullable()->comment('수정자');
|
||||
$table->foreignId('deleted_by')->nullable()->comment('삭제자');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// 인덱스
|
||||
$table->index('tenant_id', 'idx_biddings_tenant_id');
|
||||
$table->index('status', 'idx_biddings_status');
|
||||
$table->index('bidding_date', 'idx_biddings_bidding_date');
|
||||
$table->index('quote_id', 'idx_biddings_quote_id');
|
||||
$table->unique(['tenant_id', 'bidding_code', 'deleted_at'], 'uq_tenant_bidding_code');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('biddings');
|
||||
}
|
||||
};
|
||||
198
database/seeders/BiddingSeeder.php
Normal file
198
database/seeders/BiddingSeeder.php
Normal file
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Bidding\Bidding;
|
||||
use App\Models\Tenants\Tenant;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class BiddingSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* 입찰 더미데이터 10건 Seed
|
||||
* React 목업 데이터 기준
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// 첫 번째 테넌트 ID 가져오기
|
||||
$tenant = Tenant::first();
|
||||
if (! $tenant) {
|
||||
$this->command->warn('테넌트가 없습니다. TenantSeeder를 먼저 실행하세요.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$tenantId = $tenant->id;
|
||||
|
||||
$biddings = [
|
||||
[
|
||||
'bidding_code' => 'BID-2025-001',
|
||||
'client_name' => '이사대표',
|
||||
'project_name' => '광장 아파트',
|
||||
'bidding_date' => '2025-01-25',
|
||||
'total_count' => 15,
|
||||
'bidding_amount' => 71000000,
|
||||
'bid_date' => '2025-01-20',
|
||||
'submission_date' => '2025-01-22',
|
||||
'confirm_date' => '2025-01-25',
|
||||
'status' => 'awarded',
|
||||
'bidder_name' => '홍길동',
|
||||
'remarks' => '',
|
||||
],
|
||||
[
|
||||
'bidding_code' => 'BID-2025-002',
|
||||
'client_name' => '야사건설',
|
||||
'project_name' => '대림아파트',
|
||||
'bidding_date' => '2025-01-20',
|
||||
'total_count' => 22,
|
||||
'bidding_amount' => 100000000,
|
||||
'bid_date' => '2025-01-18',
|
||||
'submission_date' => null,
|
||||
'confirm_date' => null,
|
||||
'status' => 'waiting',
|
||||
'bidder_name' => '김철수',
|
||||
'remarks' => '',
|
||||
],
|
||||
[
|
||||
'bidding_code' => 'BID-2025-003',
|
||||
'client_name' => '여의건설',
|
||||
'project_name' => '현장아파트',
|
||||
'bidding_date' => '2025-01-18',
|
||||
'total_count' => 18,
|
||||
'bidding_amount' => 85000000,
|
||||
'bid_date' => '2025-01-15',
|
||||
'submission_date' => '2025-01-16',
|
||||
'confirm_date' => '2025-01-18',
|
||||
'status' => 'awarded',
|
||||
'bidder_name' => '홍길동',
|
||||
'remarks' => '',
|
||||
],
|
||||
[
|
||||
'bidding_code' => 'BID-2025-004',
|
||||
'client_name' => '이사대표',
|
||||
'project_name' => '송파타워',
|
||||
'bidding_date' => '2025-01-15',
|
||||
'total_count' => 30,
|
||||
'bidding_amount' => 120000000,
|
||||
'bid_date' => '2025-01-12',
|
||||
'submission_date' => '2025-01-13',
|
||||
'confirm_date' => '2025-01-15',
|
||||
'status' => 'failed',
|
||||
'bidder_name' => '이영희',
|
||||
'remarks' => '가격 경쟁력 부족',
|
||||
],
|
||||
[
|
||||
'bidding_code' => 'BID-2025-005',
|
||||
'client_name' => '야사건설',
|
||||
'project_name' => '강남센터',
|
||||
'bidding_date' => '2025-01-12',
|
||||
'total_count' => 25,
|
||||
'bidding_amount' => 95000000,
|
||||
'bid_date' => '2025-01-10',
|
||||
'submission_date' => '2025-01-11',
|
||||
'confirm_date' => null,
|
||||
'status' => 'submitted',
|
||||
'bidder_name' => '홍길동',
|
||||
'remarks' => '',
|
||||
],
|
||||
[
|
||||
'bidding_code' => 'BID-2025-006',
|
||||
'client_name' => '여의건설',
|
||||
'project_name' => '목동센터',
|
||||
'bidding_date' => '2025-01-10',
|
||||
'total_count' => 12,
|
||||
'bidding_amount' => 78000000,
|
||||
'bid_date' => '2025-01-08',
|
||||
'submission_date' => '2025-01-09',
|
||||
'confirm_date' => '2025-01-10',
|
||||
'status' => 'invalid',
|
||||
'bidder_name' => '김철수',
|
||||
'remarks' => '입찰 조건 미충족',
|
||||
],
|
||||
[
|
||||
'bidding_code' => 'BID-2025-007',
|
||||
'client_name' => '이사대표',
|
||||
'project_name' => '서초타워',
|
||||
'bidding_date' => '2025-01-08',
|
||||
'total_count' => 35,
|
||||
'bidding_amount' => 150000000,
|
||||
'bid_date' => '2025-01-05',
|
||||
'submission_date' => null,
|
||||
'confirm_date' => null,
|
||||
'status' => 'waiting',
|
||||
'bidder_name' => '이영희',
|
||||
'remarks' => '',
|
||||
],
|
||||
[
|
||||
'bidding_code' => 'BID-2025-008',
|
||||
'client_name' => '야사건설',
|
||||
'project_name' => '청담프로젝트',
|
||||
'bidding_date' => '2025-01-05',
|
||||
'total_count' => 40,
|
||||
'bidding_amount' => 200000000,
|
||||
'bid_date' => '2025-01-03',
|
||||
'submission_date' => '2025-01-04',
|
||||
'confirm_date' => '2025-01-05',
|
||||
'status' => 'awarded',
|
||||
'bidder_name' => '홍길동',
|
||||
'remarks' => '',
|
||||
],
|
||||
[
|
||||
'bidding_code' => 'BID-2025-009',
|
||||
'client_name' => '여의건설',
|
||||
'project_name' => '잠실센터',
|
||||
'bidding_date' => '2025-01-03',
|
||||
'total_count' => 20,
|
||||
'bidding_amount' => 88000000,
|
||||
'bid_date' => '2025-01-01',
|
||||
'submission_date' => null,
|
||||
'confirm_date' => null,
|
||||
'status' => 'hold',
|
||||
'bidder_name' => '김철수',
|
||||
'remarks' => '검토 대기 중',
|
||||
],
|
||||
[
|
||||
'bidding_code' => 'BID-2025-010',
|
||||
'client_name' => '이사대표',
|
||||
'project_name' => '역삼빌딩',
|
||||
'bidding_date' => '2025-01-01',
|
||||
'total_count' => 10,
|
||||
'bidding_amount' => 65000000,
|
||||
'bid_date' => '2024-12-28',
|
||||
'submission_date' => null,
|
||||
'confirm_date' => null,
|
||||
'status' => 'waiting',
|
||||
'bidder_name' => '이영희',
|
||||
'remarks' => '',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($biddings as $data) {
|
||||
Bidding::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'bidding_code' => $data['bidding_code'],
|
||||
'client_name' => $data['client_name'],
|
||||
'project_name' => $data['project_name'],
|
||||
'bidding_date' => $data['bidding_date'],
|
||||
'total_count' => $data['total_count'],
|
||||
'bidding_amount' => $data['bidding_amount'],
|
||||
'bid_date' => $data['bid_date'],
|
||||
'submission_date' => $data['submission_date'],
|
||||
'confirm_date' => $data['confirm_date'],
|
||||
'status' => $data['status'],
|
||||
'bidder_name' => $data['bidder_name'],
|
||||
'remarks' => $data['remarks'],
|
||||
'vat_type' => 'excluded',
|
||||
'created_by' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->command->info('입찰 더미데이터 10건이 생성되었습니다.');
|
||||
$this->command->info('- waiting: 3건 (BID-002, 007, 010)');
|
||||
$this->command->info('- awarded: 3건 (BID-001, 003, 008)');
|
||||
$this->command->info('- submitted: 1건 (BID-005)');
|
||||
$this->command->info('- failed: 1건 (BID-004)');
|
||||
$this->command->info('- invalid: 1건 (BID-006)');
|
||||
$this->command->info('- hold: 1건 (BID-009)');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user