feat: [material] 부적합관리 API Phase 1-A 구현
- Migration: nonconforming_reports, nonconforming_report_items 테이블 - Model: NonconformingReport, NonconformingReportItem (관계, cast, scope) - FormRequest: Store/Update 검증 (items 배열 포함) - Service: CRUD + 채번(NC-YYYYMMDD-NNN) + 비용 자동 계산 + 상태 전이 - Controller: REST 7개 엔드포인트 (목록/통계/상세/등록/수정/삭제/상태변경) - Route: /api/v1/material/nonconforming-reports - i18n: 부적합관리 에러 메시지 (ko)
This commit is contained in:
172
app/Models/Materials/NonconformingReport.php
Normal file
172
app/Models/Materials/NonconformingReport.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?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\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',
|
||||
'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 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user