2025-07-29 23:22:31 +09:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace App\Models\Orders;
|
|
|
|
|
|
|
2025-12-14 01:20:18 +09:00
|
|
|
|
use App\Models\Items\Item;
|
2026-01-05 15:56:46 +09:00
|
|
|
|
use App\Models\Quote\Quote;
|
|
|
|
|
|
use App\Models\Quote\QuoteItem;
|
2026-01-29 15:33:54 +09:00
|
|
|
|
use App\Traits\Auditable;
|
2026-01-05 15:56:46 +09:00
|
|
|
|
use App\Traits\BelongsToTenant;
|
2025-07-29 23:22:31 +09:00
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
2026-01-05 15:56:46 +09:00
|
|
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
|
|
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
2025-07-29 23:22:31 +09:00
|
|
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
|
|
|
2025-08-21 09:50:15 +09:00
|
|
|
|
/**
|
2026-01-05 15:56:46 +09:00
|
|
|
|
* 수주 상세 (Order Items)
|
|
|
|
|
|
*
|
2025-08-21 09:50:15 +09:00
|
|
|
|
* @mixin IdeHelperOrderItem
|
|
|
|
|
|
*/
|
2025-07-29 23:22:31 +09:00
|
|
|
|
class OrderItem extends Model
|
|
|
|
|
|
{
|
2026-01-29 15:33:54 +09:00
|
|
|
|
use Auditable, BelongsToTenant, SoftDeletes;
|
2025-07-29 23:22:31 +09:00
|
|
|
|
|
|
|
|
|
|
protected $table = 'order_items';
|
|
|
|
|
|
|
|
|
|
|
|
protected $fillable = [
|
2026-01-05 15:56:46 +09:00
|
|
|
|
'tenant_id',
|
|
|
|
|
|
'order_id',
|
2026-02-06 20:06:14 +09:00
|
|
|
|
'order_node_id',
|
2026-01-05 15:56:46 +09:00
|
|
|
|
'quote_id',
|
|
|
|
|
|
'quote_item_id',
|
|
|
|
|
|
'serial_no',
|
|
|
|
|
|
// 품목 정보
|
|
|
|
|
|
'item_id',
|
|
|
|
|
|
'item_code',
|
|
|
|
|
|
'item_name',
|
|
|
|
|
|
'specification',
|
2026-01-16 21:58:57 +09:00
|
|
|
|
// 제품-부품 매핑용 코드
|
|
|
|
|
|
'floor_code',
|
|
|
|
|
|
'symbol_code',
|
2026-01-05 15:56:46 +09:00
|
|
|
|
'unit',
|
|
|
|
|
|
// 수량/금액
|
|
|
|
|
|
'quantity',
|
|
|
|
|
|
'unit_price',
|
|
|
|
|
|
'supply_amount',
|
|
|
|
|
|
'tax_amount',
|
|
|
|
|
|
'total_amount',
|
|
|
|
|
|
// 할인
|
|
|
|
|
|
'discount_rate',
|
|
|
|
|
|
'discount_amount',
|
|
|
|
|
|
// 기타
|
|
|
|
|
|
'status_code',
|
|
|
|
|
|
'design_code',
|
|
|
|
|
|
'remarks',
|
|
|
|
|
|
'note',
|
|
|
|
|
|
'sort_order',
|
|
|
|
|
|
'attributes',
|
|
|
|
|
|
// 감사
|
|
|
|
|
|
'created_by',
|
|
|
|
|
|
'updated_by',
|
|
|
|
|
|
'deleted_by',
|
2025-07-29 23:22:31 +09:00
|
|
|
|
];
|
|
|
|
|
|
|
2026-01-05 15:56:46 +09:00
|
|
|
|
protected $casts = [
|
|
|
|
|
|
'quantity' => 'decimal:4',
|
|
|
|
|
|
'unit_price' => 'decimal:2',
|
|
|
|
|
|
'supply_amount' => 'decimal:2',
|
|
|
|
|
|
'tax_amount' => 'decimal:2',
|
|
|
|
|
|
'total_amount' => 'decimal:2',
|
|
|
|
|
|
'discount_rate' => 'decimal:2',
|
|
|
|
|
|
'discount_amount' => 'decimal:2',
|
|
|
|
|
|
'attributes' => 'array',
|
|
|
|
|
|
'created_at' => 'datetime',
|
|
|
|
|
|
'updated_at' => 'datetime',
|
|
|
|
|
|
'deleted_at' => 'datetime',
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 수주 마스터
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function order(): BelongsTo
|
|
|
|
|
|
{
|
|
|
|
|
|
return $this->belongsTo(Order::class);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 20:06:14 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 수주 노드 (개소/구역/층 등)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function node(): BelongsTo
|
|
|
|
|
|
{
|
|
|
|
|
|
return $this->belongsTo(OrderNode::class, 'order_node_id');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-05 15:56:46 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 품목 마스터
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function item(): BelongsTo
|
|
|
|
|
|
{
|
|
|
|
|
|
return $this->belongsTo(Item::class, 'item_id');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 원본 견적
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function quote(): BelongsTo
|
|
|
|
|
|
{
|
|
|
|
|
|
return $this->belongsTo(Quote::class);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 원본 견적 품목
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function quoteItem(): BelongsTo
|
|
|
|
|
|
{
|
|
|
|
|
|
return $this->belongsTo(QuoteItem::class);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 투입 구성 (자재/BOM 등)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function components(): HasMany
|
2025-07-29 23:22:31 +09:00
|
|
|
|
{
|
|
|
|
|
|
return $this->hasMany(OrderItemComponent::class);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-05 15:56:46 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 공급가액 계산 (수량 × 단가 - 할인)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function calculateSupplyAmount(): float
|
2025-07-29 23:22:31 +09:00
|
|
|
|
{
|
2026-01-05 15:56:46 +09:00
|
|
|
|
$gross = $this->quantity * $this->unit_price;
|
|
|
|
|
|
$discount = $this->discount_amount ?: ($gross * ($this->discount_rate / 100));
|
|
|
|
|
|
|
|
|
|
|
|
return round($gross - $discount, 2);
|
2025-07-29 23:22:31 +09:00
|
|
|
|
}
|
2025-12-14 01:20:18 +09:00
|
|
|
|
|
2026-01-05 15:56:46 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 세액 계산 (공급가액 × 10%)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function calculateTaxAmount(): float
|
2025-12-14 01:20:18 +09:00
|
|
|
|
{
|
2026-01-05 15:56:46 +09:00
|
|
|
|
return round($this->supply_amount * 0.1, 2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 총액 계산 (공급가액 + 세액)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function calculateTotalAmount(): float
|
|
|
|
|
|
{
|
|
|
|
|
|
return round($this->supply_amount + $this->tax_amount, 2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 금액 재계산 및 저장
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function recalculateAmounts(): self
|
|
|
|
|
|
{
|
|
|
|
|
|
$this->supply_amount = $this->calculateSupplyAmount();
|
|
|
|
|
|
$this->tax_amount = $this->calculateTaxAmount();
|
|
|
|
|
|
$this->total_amount = $this->calculateTotalAmount();
|
|
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 견적 품목에서 수주 품목 생성
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $serialIndex 품목 순번 (1부터 시작)
|
2026-01-16 21:58:57 +09:00
|
|
|
|
* @param array $productMapping 제품 매핑 정보 ['floor_code' => '10', 'symbol_code' => 'F1']
|
2026-01-05 15:56:46 +09:00
|
|
|
|
*/
|
2026-01-16 21:58:57 +09:00
|
|
|
|
public static function createFromQuoteItem(QuoteItem $quoteItem, int $orderId, int $serialIndex = 1, array $productMapping = []): self
|
2026-01-05 15:56:46 +09:00
|
|
|
|
{
|
|
|
|
|
|
$qty = $quoteItem->calculated_quantity ?? 1;
|
|
|
|
|
|
$supplyAmount = $quoteItem->unit_price * $qty;
|
|
|
|
|
|
$taxAmount = round($supplyAmount * 0.1, 2);
|
|
|
|
|
|
|
|
|
|
|
|
return new self([
|
|
|
|
|
|
'tenant_id' => $quoteItem->tenant_id,
|
|
|
|
|
|
'order_id' => $orderId,
|
2026-02-06 20:15:00 +09:00
|
|
|
|
'order_node_id' => $productMapping['order_node_id'] ?? null,
|
2026-01-05 15:56:46 +09:00
|
|
|
|
'quote_id' => $quoteItem->quote_id,
|
|
|
|
|
|
'quote_item_id' => $quoteItem->id,
|
|
|
|
|
|
'serial_no' => str_pad($serialIndex, 3, '0', STR_PAD_LEFT),
|
|
|
|
|
|
'item_id' => $quoteItem->item_id,
|
|
|
|
|
|
'item_code' => $quoteItem->item_code,
|
|
|
|
|
|
'item_name' => $quoteItem->item_name,
|
|
|
|
|
|
'specification' => $quoteItem->specification,
|
2026-01-16 21:58:57 +09:00
|
|
|
|
// 제품-부품 매핑 코드
|
|
|
|
|
|
'floor_code' => $productMapping['floor_code'] ?? null,
|
|
|
|
|
|
'symbol_code' => $productMapping['symbol_code'] ?? null,
|
2026-01-05 15:56:46 +09:00
|
|
|
|
'unit' => $quoteItem->unit ?? 'EA',
|
|
|
|
|
|
'quantity' => $qty,
|
|
|
|
|
|
'unit_price' => $quoteItem->unit_price,
|
|
|
|
|
|
'supply_amount' => $supplyAmount,
|
|
|
|
|
|
'tax_amount' => $taxAmount,
|
|
|
|
|
|
'total_amount' => $supplyAmount + $taxAmount,
|
|
|
|
|
|
'note' => $quoteItem->note,
|
|
|
|
|
|
'sort_order' => $quoteItem->sort_order ?? 0,
|
|
|
|
|
|
]);
|
2025-12-14 01:20:18 +09:00
|
|
|
|
}
|
2025-07-29 23:22:31 +09:00
|
|
|
|
}
|