feat(재고): stock_transactions 입출고 거래 이력 테이블 추가
- stock_transactions 마이그레이션 생성 (type, qty, balance_qty, reference) - StockTransaction 모델 (IN/OUT/RESERVE/RELEASE 타입, 사유 상수) - StockService 5개 메서드에 거래 이력 기록 추가 - increaseFromReceiving → IN - decreaseFIFO → OUT (LOT별) - reserve → RESERVE (LOT별) - releaseReservation → RELEASE (LOT별) - decreaseForShipment → OUT (LOT별) - Stock 모델에 transactions() 관계 추가 - 기존 audit_logs 기록은 유지 (감사 로그와 거래 이력 목적 분리) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
use App\Models\Tenants\Receiving;
|
||||
use App\Models\Tenants\Stock;
|
||||
use App\Models\Tenants\StockLot;
|
||||
use App\Models\Tenants\StockTransaction;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -277,7 +278,19 @@ public function increaseFromReceiving(Receiving $receiving): StockLot
|
||||
// 4. Stock 정보 갱신 (LOT 기반)
|
||||
$stock->refreshFromLots();
|
||||
|
||||
// 5. 감사 로그 기록
|
||||
// 5. 거래 이력 기록
|
||||
$this->recordTransaction(
|
||||
stock: $stock,
|
||||
type: StockTransaction::TYPE_IN,
|
||||
qty: $receiving->receiving_qty,
|
||||
reason: StockTransaction::REASON_RECEIVING,
|
||||
referenceType: 'receiving',
|
||||
referenceId: $receiving->id,
|
||||
lotNo: $receiving->lot_no,
|
||||
stockLotId: $stockLot->id
|
||||
);
|
||||
|
||||
// 6. 감사 로그 기록
|
||||
$this->logStockChange(
|
||||
stock: $stock,
|
||||
action: 'stock_increase',
|
||||
@@ -448,7 +461,21 @@ public function decreaseFIFO(int $itemId, float $qty, string $reason, int $refer
|
||||
$stock->last_issue_date = now();
|
||||
$stock->save();
|
||||
|
||||
// 6. 감사 로그 기록
|
||||
// 6. 거래 이력 기록 (LOT별)
|
||||
foreach ($deductedLots as $dl) {
|
||||
$this->recordTransaction(
|
||||
stock: $stock,
|
||||
type: StockTransaction::TYPE_OUT,
|
||||
qty: -$dl['deducted_qty'],
|
||||
reason: $reason,
|
||||
referenceType: $reason,
|
||||
referenceId: $referenceId,
|
||||
lotNo: $dl['lot_no'],
|
||||
stockLotId: $dl['lot_id']
|
||||
);
|
||||
}
|
||||
|
||||
// 7. 감사 로그 기록
|
||||
$this->logStockChange(
|
||||
stock: $stock,
|
||||
action: 'stock_decrease',
|
||||
@@ -591,7 +618,21 @@ public function reserve(int $itemId, float $qty, int $orderId): void
|
||||
// 4. Stock 정보 갱신
|
||||
$stock->refreshFromLots();
|
||||
|
||||
// 5. 감사 로그 기록
|
||||
// 5. 거래 이력 기록 (LOT별)
|
||||
foreach ($reservedLots as $rl) {
|
||||
$this->recordTransaction(
|
||||
stock: $stock,
|
||||
type: StockTransaction::TYPE_RESERVE,
|
||||
qty: $rl['reserved_qty'],
|
||||
reason: StockTransaction::REASON_ORDER_CONFIRM,
|
||||
referenceType: 'order',
|
||||
referenceId: $orderId,
|
||||
lotNo: $rl['lot_no'],
|
||||
stockLotId: $rl['lot_id']
|
||||
);
|
||||
}
|
||||
|
||||
// 6. 감사 로그 기록
|
||||
$this->logStockChange(
|
||||
stock: $stock,
|
||||
action: 'stock_reserve',
|
||||
@@ -680,7 +721,21 @@ public function releaseReservation(int $itemId, float $qty, int $orderId): void
|
||||
// 3. Stock 정보 갱신
|
||||
$stock->refreshFromLots();
|
||||
|
||||
// 4. 감사 로그 기록
|
||||
// 4. 거래 이력 기록 (LOT별)
|
||||
foreach ($releasedLots as $rl) {
|
||||
$this->recordTransaction(
|
||||
stock: $stock,
|
||||
type: StockTransaction::TYPE_RELEASE,
|
||||
qty: -$rl['released_qty'],
|
||||
reason: StockTransaction::REASON_ORDER_CANCEL,
|
||||
referenceType: 'order',
|
||||
referenceId: $orderId,
|
||||
lotNo: $rl['lot_no'],
|
||||
stockLotId: $rl['lot_id']
|
||||
);
|
||||
}
|
||||
|
||||
// 5. 감사 로그 기록
|
||||
$this->logStockChange(
|
||||
stock: $stock,
|
||||
action: 'stock_release',
|
||||
@@ -839,7 +894,21 @@ public function decreaseForShipment(int $itemId, float $qty, int $shipmentId, ?i
|
||||
$stock->last_issue_date = now();
|
||||
$stock->save();
|
||||
|
||||
// 5. 감사 로그 기록
|
||||
// 5. 거래 이력 기록 (LOT별)
|
||||
foreach ($deductedLots as $dl) {
|
||||
$this->recordTransaction(
|
||||
stock: $stock,
|
||||
type: StockTransaction::TYPE_OUT,
|
||||
qty: -$dl['deducted_qty'],
|
||||
reason: StockTransaction::REASON_SHIPMENT,
|
||||
referenceType: 'shipment',
|
||||
referenceId: $shipmentId,
|
||||
lotNo: $dl['lot_no'],
|
||||
stockLotId: $dl['lot_id']
|
||||
);
|
||||
}
|
||||
|
||||
// 6. 감사 로그 기록
|
||||
$this->logStockChange(
|
||||
stock: $stock,
|
||||
action: 'stock_decrease',
|
||||
@@ -861,6 +930,58 @@ public function decreaseForShipment(int $itemId, float $qty, int $shipmentId, ?i
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 재고 거래 이력 기록
|
||||
*
|
||||
* @param Stock $stock 재고
|
||||
* @param string $type 거래유형 (IN, OUT, RESERVE, RELEASE)
|
||||
* @param float $qty 변동 수량 (입고: 양수, 출고: 음수)
|
||||
* @param string $reason 사유
|
||||
* @param string $referenceType 참조 유형
|
||||
* @param int $referenceId 참조 ID
|
||||
* @param string|null $lotNo LOT번호
|
||||
* @param int|null $stockLotId StockLot ID
|
||||
* @param string|null $remark 비고
|
||||
*/
|
||||
private function recordTransaction(
|
||||
Stock $stock,
|
||||
string $type,
|
||||
float $qty,
|
||||
string $reason,
|
||||
string $referenceType,
|
||||
int $referenceId,
|
||||
?string $lotNo = null,
|
||||
?int $stockLotId = null,
|
||||
?string $remark = null
|
||||
): void {
|
||||
try {
|
||||
StockTransaction::create([
|
||||
'tenant_id' => $stock->tenant_id,
|
||||
'stock_id' => $stock->id,
|
||||
'stock_lot_id' => $stockLotId,
|
||||
'type' => $type,
|
||||
'qty' => $qty,
|
||||
'balance_qty' => (float) $stock->stock_qty,
|
||||
'reference_type' => $referenceType,
|
||||
'reference_id' => $referenceId,
|
||||
'lot_no' => $lotNo,
|
||||
'reason' => $reason,
|
||||
'remark' => $remark,
|
||||
'item_code' => $stock->item_code,
|
||||
'item_name' => $stock->item_name,
|
||||
'created_by' => $this->apiUserId(),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
// 거래 이력 기록 실패는 비즈니스 로직에 영향을 주지 않음
|
||||
Log::warning('Failed to record stock transaction', [
|
||||
'stock_id' => $stock->id,
|
||||
'type' => $type,
|
||||
'qty' => $qty,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 재고 변경 감사 로그 기록
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user