feat: [공정관리] parent_id 트리 구조 도입 — 마이그레이션, 모델 관계, 2depth 검증
This commit is contained in:
@@ -14,6 +14,7 @@ public function authorize(): bool
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'parent_id' => ['nullable', 'integer', 'exists:processes,id'],
|
||||
'process_name' => ['required', 'string', 'max:100'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'process_type' => ['required', 'string', 'in:생산,검사,포장,조립'],
|
||||
|
||||
@@ -13,7 +13,10 @@ public function authorize(): bool
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$processId = $this->route('id');
|
||||
|
||||
return [
|
||||
'parent_id' => ['nullable', 'integer', 'exists:processes,id', "not_in:{$processId}"],
|
||||
'process_name' => ['sometimes', 'required', 'string', 'max:100'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'process_type' => ['sometimes', 'required', 'string', 'in:생산,검사,포장,조립'],
|
||||
|
||||
@@ -21,6 +21,7 @@ class Process extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'parent_id',
|
||||
'process_code',
|
||||
'process_name',
|
||||
'description',
|
||||
@@ -47,6 +48,24 @@ class Process extends Model
|
||||
'required_workers' => 'integer',
|
||||
];
|
||||
|
||||
/** 부모 공정 */
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id');
|
||||
}
|
||||
|
||||
/** 자식 공정 */
|
||||
public function children(): HasMany
|
||||
{
|
||||
return $this->hasMany(self::class, 'parent_id')->orderBy('process_code');
|
||||
}
|
||||
|
||||
/** 루트 공정만 조회 */
|
||||
public function scopeRoots($query)
|
||||
{
|
||||
return $query->whereNull('parent_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 중간검사 양식
|
||||
*/
|
||||
|
||||
@@ -23,9 +23,11 @@ public function index(array $params)
|
||||
$status = $params['status'] ?? null;
|
||||
$processType = $params['process_type'] ?? null;
|
||||
|
||||
$eagerLoad = ['classificationRules', 'processItems.item:id,code,name', 'steps', 'documentTemplate:id,name,category', 'workLogTemplateRelation:id,name,category', 'parent:id,process_code,process_name', 'children:id,parent_id,process_code,process_name,is_active'];
|
||||
|
||||
$query = Process::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->with(['classificationRules', 'processItems.item:id,code,name', 'steps', 'documentTemplate:id,name,category', 'workLogTemplateRelation:id,name,category']);
|
||||
->with($eagerLoad);
|
||||
|
||||
// 검색어
|
||||
if ($q !== '') {
|
||||
@@ -62,7 +64,7 @@ public function show(int $id)
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$process = Process::where('tenant_id', $tenantId)
|
||||
->with(['classificationRules', 'processItems.item:id,code,name', 'steps', 'documentTemplate:id,name,category', 'workLogTemplateRelation:id,name,category'])
|
||||
->with(['classificationRules', 'processItems.item:id,code,name', 'steps', 'documentTemplate:id,name,category', 'workLogTemplateRelation:id,name,category', 'parent:id,process_code,process_name', 'children:id,parent_id,process_code,process_name,is_active'])
|
||||
->find($id);
|
||||
|
||||
if (! $process) {
|
||||
@@ -81,6 +83,16 @@ public function store(array $data)
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
return DB::transaction(function () use ($data, $tenantId, $userId) {
|
||||
// 2depth 제한: 부모가 이미 자식이면 거부
|
||||
if (! empty($data['parent_id'])) {
|
||||
$parent = Process::find($data['parent_id']);
|
||||
if ($parent && $parent->parent_id) {
|
||||
throw \Illuminate\Validation\ValidationException::withMessages([
|
||||
'parent_id' => ['2단계까지만 허용됩니다. 선택한 부모 공정이 이미 하위 공정입니다.'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 공정코드 자동 생성
|
||||
$data['process_code'] = $this->generateProcessCode($tenantId);
|
||||
$data['tenant_id'] = $tenantId;
|
||||
@@ -122,6 +134,22 @@ public function update(int $id, array $data)
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($process, $data, $userId) {
|
||||
// parent_id 변경 시 2depth + 순환 참조 검증
|
||||
if (array_key_exists('parent_id', $data) && $data['parent_id']) {
|
||||
$parent = Process::find($data['parent_id']);
|
||||
if ($parent && $parent->parent_id) {
|
||||
throw \Illuminate\Validation\ValidationException::withMessages([
|
||||
'parent_id' => ['2단계까지만 허용됩니다.'],
|
||||
]);
|
||||
}
|
||||
// 자기 자식을 부모로 설정하는 것 방지
|
||||
if ($process->children()->where('id', $data['parent_id'])->exists()) {
|
||||
throw \Illuminate\Validation\ValidationException::withMessages([
|
||||
'parent_id' => ['하위 공정을 부모로 설정할 수 없습니다.'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$data['updated_by'] = $userId;
|
||||
|
||||
// work_steps가 문자열이면 배열로 변환
|
||||
@@ -288,6 +316,7 @@ public function duplicate(int $id)
|
||||
|
||||
$newProcess = Process::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'parent_id' => $source->parent_id,
|
||||
'process_code' => $newCode,
|
||||
'process_name' => $source->process_name.' (복사)',
|
||||
'description' => $source->description,
|
||||
|
||||
Reference in New Issue
Block a user