feat: H-2 재고 현황 API 구현

- StockController: 재고 조회 및 통계 API
- StockService: 재고 비즈니스 로직
- Stock, StockLot 모델: 재고/로트 관리
- Swagger 문서화
- stocks, stock_lots 테이블 마이그레이션

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-26 15:45:48 +09:00
parent 43ccd1e6e0
commit 5ec201b985
7 changed files with 900 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
<?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('stocks', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
// 품목 정보
$table->string('item_code', 50)->comment('품목코드');
$table->string('item_name', 200)->comment('품목명');
$table->string('item_type', 30)->default('raw_material')
->comment('품목유형: raw_material, bent_part, purchased_part, sub_material, consumable');
$table->string('specification', 200)->nullable()->comment('규격');
$table->string('unit', 20)->default('EA')->comment('단위');
// 재고 수량
$table->decimal('stock_qty', 15, 3)->default(0)->comment('현재 재고량');
$table->decimal('safety_stock', 15, 3)->default(0)->comment('안전 재고');
$table->decimal('reserved_qty', 15, 3)->default(0)->comment('예약 수량');
$table->decimal('available_qty', 15, 3)->default(0)->comment('가용 재고량');
// LOT 정보
$table->unsignedInteger('lot_count')->default(0)->comment('LOT 개수');
$table->date('oldest_lot_date')->nullable()->comment('가장 오래된 LOT 입고일');
// 위치 및 상태
$table->string('location', 50)->nullable()->comment('재고 위치');
$table->string('status', 20)->default('normal')
->comment('상태: normal(정상), low(부족), out(없음)');
// 최근 입고/출고 정보
$table->date('last_receipt_date')->nullable()->comment('최근 입고일');
$table->date('last_issue_date')->nullable()->comment('최근 출고일');
// 감사 정보
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자');
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자');
$table->timestamps();
$table->softDeletes();
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자');
// 인덱스
$table->index('tenant_id');
$table->index('item_code');
$table->index('item_type');
$table->index('status');
$table->unique(['tenant_id', 'item_code']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('stocks');
}
};

View File

@@ -0,0 +1,76 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* 재고 LOT 테이블
*
* 재고의 LOT별 상세 정보를 관리합니다.
* FIFO 기반 출고 관리에 사용됩니다.
*/
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('stock_lots', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
$table->unsignedBigInteger('stock_id')->comment('재고 ID');
// LOT 정보
$table->string('lot_no', 50)->comment('LOT번호');
$table->unsignedInteger('fifo_order')->default(1)->comment('FIFO 순서');
$table->date('receipt_date')->comment('입고일');
// 수량 정보
$table->decimal('qty', 15, 3)->default(0)->comment('수량');
$table->decimal('reserved_qty', 15, 3)->default(0)->comment('예약 수량');
$table->decimal('available_qty', 15, 3)->default(0)->comment('가용 수량');
$table->string('unit', 20)->default('EA')->comment('단위');
// 공급업체 정보
$table->string('supplier', 100)->nullable()->comment('공급업체');
$table->string('supplier_lot', 50)->nullable()->comment('공급업체 LOT');
$table->string('po_number', 50)->nullable()->comment('발주번호');
// 위치 및 상태
$table->string('location', 50)->nullable()->comment('위치');
$table->string('status', 20)->default('available')
->comment('상태: available(사용가능), reserved(예약됨), used(사용완료)');
// 연결 정보
$table->unsignedBigInteger('receiving_id')->nullable()->comment('입고 ID');
// 감사 정보
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자');
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자');
$table->timestamps();
$table->softDeletes();
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자');
// 인덱스
$table->index('tenant_id');
$table->index('stock_id');
$table->index('lot_no');
$table->index('status');
$table->index(['stock_id', 'fifo_order']);
$table->unique(['tenant_id', 'stock_id', 'lot_no']);
// 외래키
$table->foreign('stock_id')->references('id')->on('stocks')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('stock_lots');
}
};