feat: 견적 단가 자동 적용 기능 추가

- 고객 그룹별 단가 조정 지원
- 견적 생성 시 자동 단가 조회
- 매출단가만 사용 (매입단가는 경고)
This commit is contained in:
2025-10-13 21:52:34 +09:00
parent be36073282
commit a6b06be61d
17 changed files with 3794 additions and 47 deletions

View File

@@ -34,6 +34,9 @@ public function children() { return $this->hasMany(self::class, 'parent_id'); }
// 카테고리의 제품들
public function products() { return $this->hasMany(\App\Models\Products\Product::class, 'category_id'); }
// 카테고리 필드
public function categoryFields() { return $this->hasMany(CategoryField::class, 'category_id'); }
// 태그(폴리모픽) — 이미 taggables 존재
public function tags() { return $this->morphToMany(\App\Models\Commons\Tag::class, 'taggable'); }

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Models\Orders;
use App\Traits\BelongsToTenant;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
class Client extends Model
{
use BelongsToTenant, ModelTrait;
protected $fillable = [
'tenant_id',
'client_group_id',
'client_code',
'name',
'contact_person',
'phone',
'email',
'address',
'is_active',
];
protected $casts = [
'is_active' => 'boolean',
];
// ClientGroup 관계
public function clientGroup()
{
return $this->belongsTo(ClientGroup::class, 'client_group_id');
}
// Orders 관계
public function orders()
{
return $this->hasMany(Order::class, 'client_id');
}
// 스코프
public function scopeActive($query)
{
return $query->where('is_active', 'Y');
}
public function scopeCode($query, string $code)
{
return $query->where('client_code', $code);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Models\Orders;
use App\Traits\BelongsToTenant;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class ClientGroup extends Model
{
use BelongsToTenant, ModelTrait, SoftDeletes;
protected $fillable = [
'tenant_id',
'group_code',
'group_name',
'price_rate',
'is_active',
'created_by',
'updated_by',
'deleted_by',
];
protected $casts = [
'price_rate' => 'decimal:4',
'is_active' => 'boolean',
];
// Clients 관계
public function clients()
{
return $this->hasMany(Client::class, 'client_group_id');
}
// 스코프
public function scopeActive($query)
{
return $query->where('is_active', 1);
}
public function scopeCode($query, string $code)
{
return $query->where('group_code', $code);
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Models\Products;
use App\Models\Orders\ClientGroup;
use App\Traits\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -10,6 +12,74 @@
*/
class PriceHistory extends Model
{
use SoftDeletes;
protected $fillable = ['tenant_id','item_type_code','item_id','price_type_code','price','started_at','ended_at'];
use BelongsToTenant, SoftDeletes;
protected $fillable = [
'tenant_id',
'item_type_code',
'item_id',
'price_type_code',
'client_group_id',
'price',
'started_at',
'ended_at',
'created_by',
'updated_by',
'deleted_by',
];
protected $casts = [
'price' => 'decimal:4',
'started_at' => 'date',
'ended_at' => 'date',
];
// ClientGroup 관계
public function clientGroup()
{
return $this->belongsTo(ClientGroup::class, 'client_group_id');
}
// Polymorphic 관계 (item_type_code에 따라 Product 또는 Material)
public function item()
{
if ($this->item_type_code === 'PRODUCT') {
return $this->belongsTo(Product::class, 'item_id');
} elseif ($this->item_type_code === 'MATERIAL') {
return $this->belongsTo(\App\Models\Materials\Material::class, 'item_id');
}
return null;
}
// 스코프
public function scopeForItem($query, string $itemType, int $itemId)
{
return $query->where('item_type_code', $itemType)
->where('item_id', $itemId);
}
public function scopeForClientGroup($query, ?int $clientGroupId)
{
return $query->where('client_group_id', $clientGroupId);
}
public function scopeValidAt($query, $date)
{
return $query->where('started_at', '<=', $date)
->where(function ($q) use ($date) {
$q->whereNull('ended_at')
->orWhere('ended_at', '>=', $date);
});
}
public function scopeSalePrice($query)
{
return $query->where('price_type_code', 'SALE');
}
public function scopePurchasePrice($query)
{
return $query->where('price_type_code', 'PURCHASE');
}
}