Files
sam-api/app/Models/Materials/NonconformingReport.php
김보곤 6e50fbd1fa feat: [material] 부적합관리 결재 연동 구현
- Migration: approval_id FK 추가
- Model: approval() BelongsTo 관계
- Service: submitForApproval() 결재상신 (결재문서+결재선 생성)
- ApprovalService: 승인→CLOSED, 반려/회수→approval_id 해제
- Controller: POST /{id}/submit-approval 엔드포인트
- Route: submit-approval 라우트 등록
2026-03-19 09:03:12 +09:00

180 lines
4.4 KiB
PHP

<?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;
use App\Models\Tenants\Approval;
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',
'approval_id',
'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');
}
public function approval(): BelongsTo
{
return $this->belongsTo(Approval::class);
}
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;
}
}