laravel12/app/Services/StockService.php
2025-12-07 01:44:14 +09:00

62 lines
2.2 KiB
PHP

<?php
namespace App\Services;
use App\Models\Product;
use App\Models\StockLog;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
class StockService
{
public function setInitialStock(Product $product, int $qty): void
{
if($qty < 0) {
throw ValidationException::withMessages(['initial_stock' => '초기 재고는 0 이상이어야 합니다.']);
}
// use 명령을 통해서 클로저에서 $product와 $qty 값을 사용하겠다.
// transaction() 스태틱 메소드 안에서 동작하는 DB 작업은 에러 발생 시 전체가 롤백된다.
DB::transaction(function () use($product, $qty) {
// 행 잠금으로 동시성 방지
$p = Product::whereKey($product->getKey())->lockForUpdate()->first();
// 총 재고 갱신
$p->quantity = $qty;
$p->save();
// 로그 기록 (초기 세팅도 입고 형태로 남김)
if ($qty > 0) {
StockLog::create([
'product_id' => $p->id,
'change_type' => 'in',
'change_amount' => $qty
]);
}
});
}
public function adjust(Product $product, string $type, int $amount): void
{
if(!in_array($type, ['in', 'out'])) {
throw ValidationException::withMessages(['action' => 'action은 in/out 이어야 합니다.']);
}
if($amount <= 0) {
throw ValidationException::withMessages(['amount' => '수량은 1 이상이어야 합니다.']);
}
DB::transaction(function() use($product, $type, $amount) {
$p = Product::whereKey($product->getKey())->lockForUpdate()->first();
$newQty = $type === 'in' ? $p->quantity + $amount : $p->quantity - $amount;
if($newQty < 0) {
throw ValidationException::withMessages(['amount' => '현재 재고보다 많은 수량의 출고는 불가능합니다.']);
}
$p->quantity = $newQty;
$p->save();
StockLog::create([
'product_id' => $p->id,
'change_type' => $type,
'change_amount' => $amount
]);
});
}
}