feat: 견적 수식 품목 단가 연동 구현

- Price 모델 생성 (prices 테이블 연동)
- getCurrentPrice(): 현재 유효 단가 조회
- getSalesPriceByItemCode(): 품목 코드로 판매단가 조회
- FormulaEvaluatorService.getItemPrice() 구현 완료 (TODO 해결)
This commit is contained in:
2025-12-19 16:02:48 +09:00
parent 7fbb937ce0
commit 866e2602a2
2 changed files with 157 additions and 2 deletions

148
app/Models/Price.php Normal file
View File

@@ -0,0 +1,148 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\DB;
class Price extends Model
{
use SoftDeletes;
protected $table = 'prices';
protected $fillable = [
'tenant_id',
'item_type_code',
'item_id',
'client_group_id',
'purchase_price',
'processing_cost',
'loss_rate',
'margin_rate',
'sales_price',
'rounding_rule',
'rounding_unit',
'supplier',
'effective_from',
'effective_to',
'note',
'status',
'is_final',
'finalized_at',
'finalized_by',
'created_by',
'updated_by',
'deleted_by',
];
protected function casts(): array
{
return [
'purchase_price' => 'decimal:4',
'processing_cost' => 'decimal:4',
'loss_rate' => 'decimal:2',
'margin_rate' => 'decimal:2',
'sales_price' => 'decimal:4',
'rounding_unit' => 'integer',
'effective_from' => 'date',
'effective_to' => 'date',
'is_final' => 'boolean',
'finalized_at' => 'datetime',
];
}
/**
* 상태 상수
*/
public const STATUS_DRAFT = 'draft';
public const STATUS_ACTIVE = 'active';
public const STATUS_INACTIVE = 'inactive';
public const STATUS_FINALIZED = 'finalized';
/**
* 품목 유형 상수
*/
public const ITEM_TYPE_PRODUCT = 'PRODUCT';
public const ITEM_TYPE_MATERIAL = 'MATERIAL';
/**
* 반올림 규칙 상수
*/
public const ROUNDING_ROUND = 'round';
public const ROUNDING_CEIL = 'ceil';
public const ROUNDING_FLOOR = 'floor';
/**
* 특정 품목의 현재 유효 단가 조회
*/
public static function getCurrentPrice(
int $tenantId,
string $itemTypeCode,
int $itemId,
?int $clientGroupId = null
): ?self {
return static::where('tenant_id', $tenantId)
->where('item_type_code', $itemTypeCode)
->where('item_id', $itemId)
->where(function ($query) use ($clientGroupId) {
if ($clientGroupId) {
$query->where('client_group_id', $clientGroupId)
->orWhereNull('client_group_id');
} else {
$query->whereNull('client_group_id');
}
})
->where('status', self::STATUS_ACTIVE)
->where('effective_from', '<=', now())
->where(function ($query) {
$query->whereNull('effective_to')
->orWhere('effective_to', '>=', now());
})
->orderByRaw('client_group_id IS NULL') // 고객그룹 지정된 것 우선
->orderBy('effective_from', 'desc')
->first();
}
/**
* 품목 코드로 현재 유효 판매단가 조회
* (quote_formula_items.item_code와 연동용)
*/
public static function getSalesPriceByItemCode(int $tenantId, string $itemCode): float
{
// products 테이블에서 품목 코드로 검색
$product = DB::table('products')
->where('tenant_id', $tenantId)
->where('code', $itemCode)
->whereNull('deleted_at')
->first();
if ($product) {
$price = static::getCurrentPrice($tenantId, self::ITEM_TYPE_PRODUCT, $product->id);
return (float) ($price?->sales_price ?? 0);
}
// materials 테이블에서도 검색
$material = DB::table('materials')
->where('tenant_id', $tenantId)
->where('code', $itemCode)
->whereNull('deleted_at')
->first();
if ($material) {
$price = static::getCurrentPrice($tenantId, self::ITEM_TYPE_MATERIAL, $material->id);
return (float) ($price?->sales_price ?? 0);
}
return 0;
}
}

View File

@@ -323,8 +323,15 @@ private function evaluateCondition(string $condition): bool
private function getItemPrice(string $itemCode): float
{
// TODO: 품목 마스터에서 단가 조회
return 0;
$tenantId = session('selected_tenant_id');
if (! $tenantId) {
$this->errors[] = "테넌트 ID가 설정되지 않았습니다.";
return 0;
}
return \App\Models\Price::getSalesPriceByItemCode($tenantId, $itemCode);
}
/**