refactor(work-orders): 코드 리뷰 기반 전면 개선

## Critical 수정
- Multi-tenancy: WorkOrderItem, BendingDetail, Issue에 BelongsToTenant 적용
- 감사 로그: 상태변경, 품목수정, 이슈 등록/해결 시 로깅 추가
- 상태 전이 규칙: STATUS_TRANSITIONS + canTransitionTo() 구현

## High 수정
- 다중 담당자: work_order_assignees 피벗 테이블 및 관계 추가
- 부분 수정: 품목 ID 기반 upsert/delete 로직 구현

## 변경 파일
- Models: WorkOrder, WorkOrderAssignee(신규), 하위 모델들
- Services: WorkOrderService (assign, update 메서드 개선)
- Migrations: tenant_id 추가, assignees 테이블 생성

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-01-09 08:32:44 +09:00
parent 18aa745518
commit 349917f019
8 changed files with 490 additions and 22 deletions

View File

@@ -98,6 +98,19 @@ class WorkOrder extends Model
self::STATUS_SHIPPED,
];
/**
* 상태 전이 규칙
* [현재 상태 => [허용되는 다음 상태들]]
*/
public const STATUS_TRANSITIONS = [
self::STATUS_UNASSIGNED => [self::STATUS_PENDING],
self::STATUS_PENDING => [self::STATUS_UNASSIGNED, self::STATUS_WAITING],
self::STATUS_WAITING => [self::STATUS_PENDING, self::STATUS_IN_PROGRESS],
self::STATUS_IN_PROGRESS => [self::STATUS_WAITING, self::STATUS_COMPLETED],
self::STATUS_COMPLETED => [self::STATUS_IN_PROGRESS, self::STATUS_SHIPPED],
self::STATUS_SHIPPED => [self::STATUS_COMPLETED],
];
// ──────────────────────────────────────────────────────────────
// 관계
// ──────────────────────────────────────────────────────────────
@@ -111,13 +124,29 @@ public function salesOrder(): BelongsTo
}
/**
* 담당자
* 담당자 (주 담당자 - 하위 호환)
*/
public function assignee(): BelongsTo
{
return $this->belongsTo(User::class, 'assignee_id');
}
/**
* 담당자들 (다중 담당자)
*/
public function assignees(): HasMany
{
return $this->hasMany(WorkOrderAssignee::class);
}
/**
* 주 담당자 (다중 담당자 중)
*/
public function primaryAssignee(): HasMany
{
return $this->hasMany(WorkOrderAssignee::class)->where('is_primary', true);
}
/**
* 팀
*/
@@ -291,4 +320,41 @@ public function getOpenIssuesCountAttribute(): int
{
return $this->issues()->where('status', '!=', 'resolved')->count();
}
/**
* 특정 상태로 전이 가능한지 확인
*/
public function canTransitionTo(string $newStatus): bool
{
$allowedTransitions = self::STATUS_TRANSITIONS[$this->status] ?? [];
return in_array($newStatus, $allowedTransitions);
}
/**
* 현재 상태에서 허용되는 다음 상태 목록 조회
*/
public function getAllowedTransitions(): array
{
return self::STATUS_TRANSITIONS[$this->status] ?? [];
}
/**
* 상태 전이 실행 (유효성 검증 포함)
*
* @throws \InvalidArgumentException 허용되지 않은 전이인 경우
*/
public function transitionTo(string $newStatus): bool
{
if (! $this->canTransitionTo($newStatus)) {
$allowed = implode(', ', $this->getAllowedTransitions());
throw new \InvalidArgumentException(
"상태를 '{$this->status}'에서 '{$newStatus}'(으)로 변경할 수 없습니다. 허용된 상태: {$allowed}"
);
}
$this->status = $newStatus;
return true;
}
}