requireAuth(); if ($auth instanceof ResponseInterface) { return $auth; } try { $page = (int) ($this->request->getGet('page') ?? 1); $perPage = (int) ($this->request->getGet('per_page') ?? 10); if ($page < 1) $page = 1; if ($perPage < 1) $perPage = 10; $offset = ($page - 1) * $perPage; $search = trim((string) $this->request->getGet('search')); $status = trim((string) $this->request->getGet('status')); $db = $this->getDB(); $builder = $db->table($this->table)->where('deleted_YN', 'N'); if ($search !== '') $builder->like('name', $search); if ($status === 'Y' || $status === 'N') $builder->where('status_YN', $status); $total = $builder->countAllResults(false); $items = $builder ->select('id, name, sort_order, status_YN, created_at') ->orderBy('sort_order', 'ASC') ->orderBy('id', 'ASC') ->limit($perPage, $offset) ->get() ->getResult(); return $this->respondSuccess([ 'items' => $items, 'total' => $total, 'page' => $page, 'per_page' => $perPage, 'total_pages' => (int) ceil($total / $perPage), ]); } catch (\Exception $e) { log_message('error', 'SpeciesController index error: ' . $e->getMessage()); return $this->respondError('목록 조회 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * 어종구분 등록 * POST /api/species */ public function create() { $auth = $this->requireAuth(); if ($auth instanceof ResponseInterface) { return $auth; } try { $payload = $this->request->getJSON(true); if (!is_array($payload) || empty($payload)) { $payload = $this->request->getPost() ?? []; } $name = trim((string) ($payload['name'] ?? '')); $sortOrder = $payload['sort_order'] ?? 1; $status = trim((string) ($payload['status_YN'] ?? 'Y')); if ($name === '') { return $this->respondError('구분명을 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST); } if (mb_strlen($name) > 30) { return $this->respondError('구분명은 30자 이내로 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST); } if (!is_numeric($sortOrder) || (int) $sortOrder < 0) { return $this->respondError('정렬순서는 0 이상의 숫자여야 합니다.', ResponseInterface::HTTP_BAD_REQUEST); } $status = ($status === 'N') ? 'N' : 'Y'; $db = $this->getDB(); // 중복 검사 $dupe = $db->table($this->table) ->where('name', $name)->where('deleted_YN', 'N')->countAllResults(); if ($dupe > 0) { return $this->respondError('이미 등록된 구분명입니다.', ResponseInterface::HTTP_CONFLICT); } $insertData = [ 'name' => $name, 'sort_order' => (int) $sortOrder, 'status_YN' => $status, 'created_at' => date('Y-m-d H:i:s'), ]; if (!$db->table($this->table)->insert($insertData)) { return $this->respondError('등록에 실패했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } $newId = $db->insertID(); $row = $db->table($this->table)->where('id', $newId)->get()->getRow(); return $this->respondSuccess($row, '어종구분이 등록되었습니다.', ResponseInterface::HTTP_CREATED); } catch (\Exception $e) { log_message('error', 'SpeciesController create error: ' . $e->getMessage()); return $this->respondError('등록 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * 어종구분 수정 (인라인) * PUT /api/species/:id */ public function update($id = null) { $auth = $this->requireAuth(); if ($auth instanceof ResponseInterface) { return $auth; } if (empty($id)) { return $this->respondError('ID가 필요합니다.', ResponseInterface::HTTP_BAD_REQUEST); } try { $payload = $this->request->getJSON(true); if (!is_array($payload) || empty($payload)) { $payload = $this->request->getRawInput() ?? []; } $name = trim((string) ($payload['name'] ?? '')); $sortOrder = $payload['sort_order'] ?? 1; $status = trim((string) ($payload['status_YN'] ?? 'Y')); if ($name === '') { return $this->respondError('구분명을 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST); } if (mb_strlen($name) > 30) { return $this->respondError('구분명은 30자 이내로 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST); } if (!is_numeric($sortOrder) || (int) $sortOrder < 0) { return $this->respondError('정렬순서는 0 이상의 숫자여야 합니다.', ResponseInterface::HTTP_BAD_REQUEST); } $status = ($status === 'N') ? 'N' : 'Y'; $db = $this->getDB(); $exists = $db->table($this->table) ->where('id', (int) $id)->where('deleted_YN', 'N')->countAllResults(); if ($exists === 0) { return $this->respondError('해당 항목을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND); } // 자기 자신 제외 중복 검사 $dupe = $db->table($this->table) ->where('name', $name) ->where('id !=', (int) $id) ->where('deleted_YN', 'N') ->countAllResults(); if ($dupe > 0) { return $this->respondError('이미 등록된 구분명입니다.', ResponseInterface::HTTP_CONFLICT); } $db->table($this->table)->where('id', (int) $id)->update([ 'name' => $name, 'sort_order' => (int) $sortOrder, 'status_YN' => $status, ]); $row = $db->table($this->table)->where('id', (int) $id)->get()->getRow(); return $this->respondSuccess($row, '수정되었습니다.'); } catch (\Exception $e) { log_message('error', 'SpeciesController update error: ' . $e->getMessage()); return $this->respondError('수정 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * 어종구분 일괄 저장 (신규 + 수정 한 번에, 트랜잭션) * POST /api/species/bulk-save * { creates: [{name, sort_order, status_YN}, ...], * updates: [{id, name, sort_order, status_YN}, ...] } */ public function bulkSave() { $auth = $this->requireAuth(); if ($auth instanceof ResponseInterface) { return $auth; } try { $payload = $this->request->getJSON(true); if (!is_array($payload) || empty($payload)) { $payload = $this->request->getRawInput() ?? []; } $creates = is_array($payload['creates'] ?? null) ? $payload['creates'] : []; $updates = is_array($payload['updates'] ?? null) ? $payload['updates'] : []; $deletes = is_array($payload['deletes'] ?? null) ? $payload['deletes'] : []; if (empty($creates) && empty($updates) && empty($deletes)) { return $this->respondError('저장할 내용이 없습니다.', ResponseInterface::HTTP_BAD_REQUEST); } $db = $this->getDB(); $db->transBegin(); $seenNames = []; $createdCount = 0; $updatedCount = 0; $deletedCount = 0; // creates foreach ($creates as $i => $c) { $rowLabel = '신규 ' . ($i + 1) . '행'; $name = trim((string) ($c['name'] ?? '')); $sortOrder = $c['sort_order'] ?? 1; $status = trim((string) ($c['status_YN'] ?? 'Y')); if ($name === '') { $db->transRollback(); return $this->respondError("{$rowLabel}: 구분명을 입력하세요.", ResponseInterface::HTTP_BAD_REQUEST); } if (mb_strlen($name) > 30) { $db->transRollback(); return $this->respondError("{$rowLabel}: 구분명은 30자 이내", ResponseInterface::HTTP_BAD_REQUEST); } if (!is_numeric($sortOrder) || (int) $sortOrder < 0) { $db->transRollback(); return $this->respondError("{$rowLabel}: 정렬순서는 0 이상 숫자", ResponseInterface::HTTP_BAD_REQUEST); } if (isset($seenNames[$name])) { $db->transRollback(); return $this->respondError("{$rowLabel}: 같은 batch에 '{$name}' 중복", ResponseInterface::HTTP_CONFLICT); } $dupe = $db->table($this->table)->where('name', $name)->where('deleted_YN', 'N')->countAllResults(); if ($dupe > 0) { $db->transRollback(); return $this->respondError("{$rowLabel}: 이미 등록된 '{$name}'", ResponseInterface::HTTP_CONFLICT); } $seenNames[$name] = true; $db->table($this->table)->insert([ 'name' => $name, 'sort_order' => (int) $sortOrder, 'status_YN' => ($status === 'N') ? 'N' : 'Y', 'created_at' => date('Y-m-d H:i:s'), ]); $createdCount++; } // updates foreach ($updates as $i => $u) { $rowLabel = '수정 ' . ($i + 1) . '행'; $id = (int) ($u['id'] ?? 0); $name = trim((string) ($u['name'] ?? '')); $sortOrder = $u['sort_order'] ?? 1; $status = trim((string) ($u['status_YN'] ?? 'Y')); if ($id <= 0) { $db->transRollback(); return $this->respondError("{$rowLabel}: ID가 올바르지 않습니다.", ResponseInterface::HTTP_BAD_REQUEST); } if ($name === '') { $db->transRollback(); return $this->respondError("{$rowLabel}: 구분명을 입력하세요.", ResponseInterface::HTTP_BAD_REQUEST); } if (mb_strlen($name) > 30) { $db->transRollback(); return $this->respondError("{$rowLabel}: 구분명은 30자 이내", ResponseInterface::HTTP_BAD_REQUEST); } if (!is_numeric($sortOrder) || (int) $sortOrder < 0) { $db->transRollback(); return $this->respondError("{$rowLabel}: 정렬순서는 0 이상 숫자", ResponseInterface::HTTP_BAD_REQUEST); } if (isset($seenNames[$name])) { $db->transRollback(); return $this->respondError("{$rowLabel}: 같은 batch에 '{$name}' 중복", ResponseInterface::HTTP_CONFLICT); } $exists = $db->table($this->table)->where('id', $id)->where('deleted_YN', 'N')->countAllResults(); if ($exists === 0) { $db->transRollback(); return $this->respondError("{$rowLabel}: 대상이 없습니다.", ResponseInterface::HTTP_NOT_FOUND); } $dupe = $db->table($this->table) ->where('name', $name)->where('id !=', $id)->where('deleted_YN', 'N')->countAllResults(); if ($dupe > 0) { $db->transRollback(); return $this->respondError("{$rowLabel}: 이미 등록된 '{$name}'", ResponseInterface::HTTP_CONFLICT); } $seenNames[$name] = true; $db->table($this->table)->where('id', $id)->update([ 'name' => $name, 'sort_order' => (int) $sortOrder, 'status_YN' => ($status === 'N') ? 'N' : 'Y', ]); $updatedCount++; } // deletes 처리 (soft delete) $deleteIds = []; foreach ($deletes as $d) { $id = (int) (is_array($d) ? ($d['id'] ?? 0) : $d); if ($id > 0) $deleteIds[] = $id; } if (!empty($deleteIds)) { $db->table($this->table) ->whereIn('id', array_values(array_unique($deleteIds))) ->where('deleted_YN', 'N') ->update(['deleted_YN' => 'Y']); $deletedCount = count(array_unique($deleteIds)); } if ($db->transStatus() === false) { $db->transRollback(); return $this->respondError('저장 중 오류가 발생했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } $db->transCommit(); $total = $createdCount + $updatedCount + $deletedCount; return $this->respondSuccess( ['created' => $createdCount, 'updated' => $updatedCount, 'deleted' => $deletedCount], "{$total}건이 저장되었습니다. (신규 {$createdCount} / 수정 {$updatedCount} / 삭제 {$deletedCount})" ); } catch (\Exception $e) { log_message('error', 'SpeciesController bulkSave error: ' . $e->getMessage()); return $this->respondError('저장 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * 어종구분 일괄 삭제 (soft delete) * POST /api/species/bulk-delete { ids: [1,2,3] } */ public function bulkDelete() { $auth = $this->requireAuth(); if ($auth instanceof ResponseInterface) { return $auth; } try { $payload = $this->request->getJSON(true); if (!is_array($payload) || empty($payload)) { $payload = $this->request->getPost() ?? []; } $ids = $payload['ids'] ?? []; if (!is_array($ids) || empty($ids)) { return $this->respondError('삭제할 항목을 선택하세요.', ResponseInterface::HTTP_BAD_REQUEST); } $intIds = []; foreach ($ids as $v) { $n = (int) $v; if ($n > 0) $intIds[] = $n; } if (empty($intIds)) { return $this->respondError('올바른 ID가 없습니다.', ResponseInterface::HTTP_BAD_REQUEST); } $db = $this->getDB(); $db->table($this->table) ->whereIn('id', $intIds) ->where('deleted_YN', 'N') ->update(['deleted_YN' => 'Y']); return $this->respondSuccess(['count' => count($intIds)], count($intIds) . '건이 삭제되었습니다.'); } catch (\Exception $e) { log_message('error', 'SpeciesController bulkDelete error: ' . $e->getMessage()); return $this->respondError('삭제 중 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } }