## 변경 사항 ### 검사 시스템 통합 - 레거시 검사 모델 삭제 (MaterialInspection, MaterialInspectionItem, MaterialReceipt) - 통합 검사 모델 추가 (Inspection.php) - IQC/PQC/FQC 지원 - 품목 입고 모델 추가 (ItemReceipt.php) - PricingService에서 ItemReceipt 참조로 변경 ### 출하 대시보드 수정 - ShipmentService stats() 프론트엔드 호환 필드 추가 - today_shipment_count, scheduled_count, shipping_count, urgent_count ### 마이그레이션 - inspections 테이블 생성 (IQC/PQC/FQC 통합) - item_receipts로 테이블명 변경 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
222 lines
5.5 KiB
PHP
222 lines
5.5 KiB
PHP
<?php
|
|
|
|
namespace App\Models\Qualitys;
|
|
|
|
use App\Models\Items\Item;
|
|
use App\Models\Scopes\BelongsToTenant;
|
|
use App\Models\Tenants\User;
|
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
/**
|
|
* 통합 검사 모델 (IQC/PQC/FQC)
|
|
*
|
|
* @property int $id
|
|
* @property int $tenant_id
|
|
* @property string $inspection_no 검사번호
|
|
* @property string $inspection_type 검사유형 (IQC, PQC, FQC)
|
|
* @property string $status 상태 (waiting, in_progress, completed)
|
|
* @property string|null $result 판정결과 (pass, fail)
|
|
* @property string $request_date 검사요청일
|
|
* @property string|null $inspection_date 검사일
|
|
* @property int|null $item_id 품목 ID
|
|
* @property string $lot_no LOT번호
|
|
* @property int|null $inspector_id 검사자 ID
|
|
* @property array|null $meta 메타정보 (process_name, quantity, unit 등)
|
|
* @property array|null $items 검사항목 배열
|
|
* @property array|null $attachments 첨부파일 배열
|
|
* @property array|null $extra 추가정보 (remarks, opinion 등)
|
|
*
|
|
* @mixin IdeHelperInspection
|
|
*/
|
|
class Inspection extends Model
|
|
{
|
|
use SoftDeletes;
|
|
|
|
protected $table = 'inspections';
|
|
|
|
protected $fillable = [
|
|
'tenant_id',
|
|
'inspection_no',
|
|
'inspection_type',
|
|
'status',
|
|
'result',
|
|
'request_date',
|
|
'inspection_date',
|
|
'item_id',
|
|
'lot_no',
|
|
'inspector_id',
|
|
'meta',
|
|
'items',
|
|
'attachments',
|
|
'extra',
|
|
'created_by',
|
|
'updated_by',
|
|
];
|
|
|
|
protected $casts = [
|
|
'meta' => 'array',
|
|
'items' => 'array',
|
|
'attachments' => 'array',
|
|
'extra' => 'array',
|
|
'request_date' => 'date',
|
|
'inspection_date' => 'date',
|
|
];
|
|
|
|
/**
|
|
* 검사 유형 상수
|
|
*/
|
|
public const TYPE_IQC = 'IQC'; // 수입검사 (Incoming Quality Control)
|
|
|
|
public const TYPE_PQC = 'PQC'; // 공정검사 (Process Quality Control)
|
|
|
|
public const TYPE_FQC = 'FQC'; // 최종검사 (Final Quality Control)
|
|
|
|
/**
|
|
* 상태 상수
|
|
*/
|
|
public const STATUS_WAITING = 'waiting';
|
|
|
|
public const STATUS_IN_PROGRESS = 'in_progress';
|
|
|
|
public const STATUS_COMPLETED = 'completed';
|
|
|
|
/**
|
|
* 판정결과 상수
|
|
*/
|
|
public const RESULT_PASS = 'pass';
|
|
|
|
public const RESULT_FAIL = 'fail';
|
|
|
|
protected static function booted(): void
|
|
{
|
|
static::addGlobalScope(new BelongsToTenant);
|
|
}
|
|
|
|
// ===== Relationships =====
|
|
|
|
/**
|
|
* 품목
|
|
*/
|
|
public function item()
|
|
{
|
|
return $this->belongsTo(Item::class, 'item_id');
|
|
}
|
|
|
|
/**
|
|
* 검사자
|
|
*/
|
|
public function inspector()
|
|
{
|
|
return $this->belongsTo(User::class, 'inspector_id');
|
|
}
|
|
|
|
/**
|
|
* 생성자
|
|
*/
|
|
public function creator()
|
|
{
|
|
return $this->belongsTo(User::class, 'created_by');
|
|
}
|
|
|
|
// ===== Accessors (meta JSON 필드) =====
|
|
|
|
protected function processName(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: fn () => $this->meta['process_name'] ?? null,
|
|
set: fn ($value) => $this->setMetaValue('process_name', $value),
|
|
);
|
|
}
|
|
|
|
protected function quantity(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: fn () => $this->meta['quantity'] ?? null,
|
|
set: fn ($value) => $this->setMetaValue('quantity', $value),
|
|
);
|
|
}
|
|
|
|
protected function unit(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: fn () => $this->meta['unit'] ?? null,
|
|
set: fn ($value) => $this->setMetaValue('unit', $value),
|
|
);
|
|
}
|
|
|
|
// ===== Accessors (extra JSON 필드) =====
|
|
|
|
protected function remarks(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: fn () => $this->extra['remarks'] ?? null,
|
|
set: fn ($value) => $this->setExtraValue('remarks', $value),
|
|
);
|
|
}
|
|
|
|
protected function opinion(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: fn () => $this->extra['opinion'] ?? null,
|
|
set: fn ($value) => $this->setExtraValue('opinion', $value),
|
|
);
|
|
}
|
|
|
|
// ===== Helper Methods =====
|
|
|
|
/**
|
|
* meta JSON 필드에 값 설정
|
|
*/
|
|
protected function setMetaValue(string $key, $value): array
|
|
{
|
|
$meta = $this->meta ?? [];
|
|
$meta[$key] = $value;
|
|
$this->attributes['meta'] = json_encode($meta);
|
|
|
|
return $meta;
|
|
}
|
|
|
|
/**
|
|
* extra JSON 필드에 값 설정
|
|
*/
|
|
protected function setExtraValue(string $key, $value): array
|
|
{
|
|
$extra = $this->extra ?? [];
|
|
$extra[$key] = $value;
|
|
$this->attributes['extra'] = json_encode($extra);
|
|
|
|
return $extra;
|
|
}
|
|
|
|
/**
|
|
* 검사번호 자동 생성
|
|
*/
|
|
public static function generateInspectionNo(int $tenantId, string $type): string
|
|
{
|
|
$prefix = match ($type) {
|
|
self::TYPE_IQC => 'IQC',
|
|
self::TYPE_PQC => 'PQC',
|
|
self::TYPE_FQC => 'FQC',
|
|
default => 'INS',
|
|
};
|
|
|
|
$date = now()->format('Ymd');
|
|
|
|
$lastNo = static::withoutGlobalScopes()
|
|
->where('tenant_id', $tenantId)
|
|
->where('inspection_no', 'like', "{$prefix}-{$date}-%")
|
|
->orderByDesc('inspection_no')
|
|
->value('inspection_no');
|
|
|
|
if ($lastNo) {
|
|
$seq = (int) substr($lastNo, -4) + 1;
|
|
} else {
|
|
$seq = 1;
|
|
}
|
|
|
|
return sprintf('%s-%s-%04d', $prefix, $date, $seq);
|
|
}
|
|
}
|