feat: [재고] 적정재고 관리 기능 추가 (max_stock + over 상태 + update API)
This commit is contained in:
@@ -73,6 +73,32 @@ public function statsByItemType(): JsonResponse
|
||||
return ApiResponse::success($stats, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 재고 수정 (안전재고, 최대재고, 사용상태)
|
||||
*/
|
||||
public function update(int $id, Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$data = $request->validate([
|
||||
'safety_stock' => 'nullable|numeric|min:0',
|
||||
'max_stock' => 'nullable|numeric|min:0',
|
||||
'is_active' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
// 최대재고가 설정된 경우 안전재고 이상이어야 함
|
||||
if (isset($data['max_stock']) && $data['max_stock'] > 0
|
||||
&& isset($data['safety_stock']) && $data['safety_stock'] > $data['max_stock']) {
|
||||
return ApiResponse::error('최대재고는 안전재고 이상이어야 합니다.', 422);
|
||||
}
|
||||
|
||||
$stock = $this->service->updateStock($id, $data);
|
||||
|
||||
return ApiResponse::success($stock, __('message.updated'));
|
||||
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
|
||||
return ApiResponse::error(__('error.stock.not_found'), 404);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 재고 조정 이력 조회
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,7 @@ class Stock extends Model
|
||||
'unit',
|
||||
'stock_qty',
|
||||
'safety_stock',
|
||||
'max_stock',
|
||||
'reserved_qty',
|
||||
'available_qty',
|
||||
'lot_count',
|
||||
@@ -39,6 +40,7 @@ class Stock extends Model
|
||||
protected $casts = [
|
||||
'stock_qty' => 'decimal:3',
|
||||
'safety_stock' => 'decimal:3',
|
||||
'max_stock' => 'decimal:3',
|
||||
'reserved_qty' => 'decimal:3',
|
||||
'available_qty' => 'decimal:3',
|
||||
'lot_count' => 'integer',
|
||||
@@ -65,6 +67,7 @@ class Stock extends Model
|
||||
'normal' => '정상',
|
||||
'low' => '부족',
|
||||
'out' => '없음',
|
||||
'over' => '초과',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -140,6 +143,10 @@ public function calculateStatus(): string
|
||||
return 'low';
|
||||
}
|
||||
|
||||
if ($this->max_stock > 0 && $this->stock_qty > $this->max_stock) {
|
||||
return 'over';
|
||||
}
|
||||
|
||||
return 'normal';
|
||||
}
|
||||
|
||||
|
||||
@@ -191,6 +191,36 @@ public function show(int $id): Item
|
||||
->findOrFail($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 재고 수정 (안전재고, 최대재고, 사용상태)
|
||||
*/
|
||||
public function updateStock(int $id, array $data): Item
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$item = Item::where('tenant_id', $tenantId)->findOrFail($id);
|
||||
$stock = $item->stock;
|
||||
|
||||
if ($stock) {
|
||||
if (isset($data['safety_stock'])) {
|
||||
$stock->safety_stock = $data['safety_stock'];
|
||||
}
|
||||
if (isset($data['max_stock'])) {
|
||||
$stock->max_stock = $data['max_stock'];
|
||||
}
|
||||
$stock->status = $stock->calculateStatus();
|
||||
$stock->updated_by = $this->apiUserId();
|
||||
$stock->save();
|
||||
}
|
||||
|
||||
if (isset($data['is_active'])) {
|
||||
$item->is_active = $data['is_active'];
|
||||
$item->save();
|
||||
}
|
||||
|
||||
return $item->load(['stock.lots' => fn ($q) => $q->orderBy('fifo_order')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 재고 조정 이력 조회
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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::table('stocks', function (Blueprint $table) {
|
||||
$table->decimal('max_stock', 15, 3)->default(0)
|
||||
->comment('최대 재고 (적정재고 상한)')
|
||||
->after('safety_stock');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('stocks', function (Blueprint $table) {
|
||||
$table->dropColumn('max_stock');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -109,6 +109,7 @@
|
||||
Route::get('/stats', [StockController::class, 'stats'])->name('v1.stocks.stats');
|
||||
Route::get('/stats-by-type', [StockController::class, 'statsByItemType'])->name('v1.stocks.stats-by-type');
|
||||
Route::get('/{id}', [StockController::class, 'show'])->whereNumber('id')->name('v1.stocks.show');
|
||||
Route::put('/{id}', [StockController::class, 'update'])->whereNumber('id')->name('v1.stocks.update');
|
||||
Route::get('/{id}/adjustments', [StockController::class, 'adjustments'])->whereNumber('id')->name('v1.stocks.adjustments');
|
||||
Route::post('/{id}/adjustments', [StockController::class, 'storeAdjustment'])->whereNumber('id')->name('v1.stocks.adjustments.store');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user