2026-03-19 08:36:45 +09:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Models\Materials;
|
|
|
|
|
|
|
|
|
|
use App\Models\Commons\File;
|
|
|
|
|
use App\Models\Departments\Department;
|
|
|
|
|
use App\Models\Items\Item;
|
|
|
|
|
use App\Models\Orders\Order;
|
2026-03-19 09:03:12 +09:00
|
|
|
use App\Models\Tenants\Approval;
|
2026-03-19 08:36:45 +09:00
|
|
|
use App\Models\Users\User;
|
|
|
|
|
use App\Traits\Auditable;
|
|
|
|
|
use App\Traits\BelongsToTenant;
|
|
|
|
|
use App\Traits\ModelTrait;
|
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
|
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
|
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
|
|
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
|
|
|
|
|
|
class NonconformingReport extends Model
|
|
|
|
|
{
|
|
|
|
|
use Auditable, BelongsToTenant, ModelTrait, SoftDeletes;
|
|
|
|
|
|
|
|
|
|
protected $fillable = [
|
|
|
|
|
'tenant_id',
|
|
|
|
|
'nc_number',
|
|
|
|
|
'status',
|
2026-03-19 09:03:12 +09:00
|
|
|
'approval_id',
|
2026-03-19 08:36:45 +09:00
|
|
|
'nc_type',
|
|
|
|
|
'occurred_at',
|
|
|
|
|
'confirmed_at',
|
|
|
|
|
'site_name',
|
|
|
|
|
'department_id',
|
|
|
|
|
'order_id',
|
|
|
|
|
'item_id',
|
|
|
|
|
'defect_quantity',
|
|
|
|
|
'unit',
|
|
|
|
|
'defect_description',
|
|
|
|
|
'cause_analysis',
|
|
|
|
|
'corrective_action',
|
|
|
|
|
'action_completed_at',
|
|
|
|
|
'action_manager_id',
|
|
|
|
|
'related_employee_id',
|
|
|
|
|
'material_cost',
|
|
|
|
|
'shipping_cost',
|
|
|
|
|
'construction_cost',
|
|
|
|
|
'other_cost',
|
|
|
|
|
'total_cost',
|
|
|
|
|
'remarks',
|
|
|
|
|
'drawing_location',
|
|
|
|
|
'options',
|
|
|
|
|
'created_by',
|
|
|
|
|
'updated_by',
|
|
|
|
|
'deleted_by',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
protected $casts = [
|
|
|
|
|
'options' => 'array',
|
|
|
|
|
'occurred_at' => 'date',
|
|
|
|
|
'confirmed_at' => 'date',
|
|
|
|
|
'action_completed_at' => 'date',
|
|
|
|
|
'material_cost' => 'integer',
|
|
|
|
|
'shipping_cost' => 'integer',
|
|
|
|
|
'construction_cost' => 'integer',
|
|
|
|
|
'other_cost' => 'integer',
|
|
|
|
|
'total_cost' => 'integer',
|
|
|
|
|
'defect_quantity' => 'decimal:2',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// 상태 상수
|
|
|
|
|
public const STATUS_RECEIVED = 'RECEIVED';
|
|
|
|
|
|
|
|
|
|
public const STATUS_ANALYZING = 'ANALYZING';
|
|
|
|
|
|
|
|
|
|
public const STATUS_RESOLVED = 'RESOLVED';
|
|
|
|
|
|
|
|
|
|
public const STATUS_CLOSED = 'CLOSED';
|
|
|
|
|
|
|
|
|
|
// 부적합 유형 상수
|
|
|
|
|
public const TYPE_MATERIAL = 'material';
|
|
|
|
|
|
|
|
|
|
public const TYPE_PROCESS = 'process';
|
|
|
|
|
|
|
|
|
|
public const TYPE_CONSTRUCTION = 'construction';
|
|
|
|
|
|
|
|
|
|
public const TYPE_OTHER = 'other';
|
|
|
|
|
|
|
|
|
|
public const NC_TYPES = [
|
|
|
|
|
self::TYPE_MATERIAL => '자재불량',
|
|
|
|
|
self::TYPE_PROCESS => '공정불량',
|
|
|
|
|
self::TYPE_CONSTRUCTION => '시공불량',
|
|
|
|
|
self::TYPE_OTHER => '기타',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
public const STATUSES = [
|
|
|
|
|
self::STATUS_RECEIVED => '접수',
|
|
|
|
|
self::STATUS_ANALYZING => '분석중',
|
|
|
|
|
self::STATUS_RESOLVED => '조치완료',
|
|
|
|
|
self::STATUS_CLOSED => '종결',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// ── 관계 ──
|
|
|
|
|
|
|
|
|
|
public function items(): HasMany
|
|
|
|
|
{
|
|
|
|
|
return $this->hasMany(NonconformingReportItem::class)->orderBy('sort_order');
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 09:03:12 +09:00
|
|
|
public function approval(): BelongsTo
|
|
|
|
|
{
|
|
|
|
|
return $this->belongsTo(Approval::class);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 08:36:45 +09:00
|
|
|
public function order(): BelongsTo
|
|
|
|
|
{
|
|
|
|
|
return $this->belongsTo(Order::class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function item(): BelongsTo
|
|
|
|
|
{
|
|
|
|
|
return $this->belongsTo(Item::class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function department(): BelongsTo
|
|
|
|
|
{
|
|
|
|
|
return $this->belongsTo(Department::class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function creator(): BelongsTo
|
|
|
|
|
{
|
|
|
|
|
return $this->belongsTo(User::class, 'created_by');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function actionManager(): BelongsTo
|
|
|
|
|
{
|
|
|
|
|
return $this->belongsTo(User::class, 'action_manager_id');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function relatedEmployee(): BelongsTo
|
|
|
|
|
{
|
|
|
|
|
return $this->belongsTo(User::class, 'related_employee_id');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function files(): MorphMany
|
|
|
|
|
{
|
|
|
|
|
return $this->morphMany(File::class, 'fileable');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── 스코프 ──
|
|
|
|
|
|
|
|
|
|
public function scopeStatus($query, string $status)
|
|
|
|
|
{
|
|
|
|
|
return $query->where('status', $status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function scopeNcType($query, string $type)
|
|
|
|
|
{
|
|
|
|
|
return $query->where('nc_type', $type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── 헬퍼 ──
|
|
|
|
|
|
|
|
|
|
public function recalculateTotalCost(): void
|
|
|
|
|
{
|
|
|
|
|
$this->total_cost = $this->material_cost + $this->shipping_cost
|
|
|
|
|
+ $this->construction_cost + $this->other_cost;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function recalculateMaterialCost(): void
|
|
|
|
|
{
|
|
|
|
|
$this->material_cost = (int) $this->items()->sum('amount');
|
|
|
|
|
$this->recalculateTotalCost();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function isClosed(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->status === self::STATUS_CLOSED;
|
|
|
|
|
}
|
|
|
|
|
}
|