feat: [org-chart] 조직도 관리 API 이관 (8개 엔드포인트)
- OrgChartController + OrgChartService 신규 생성 - FormRequest 5개 (Assign/Unassign/ReorderEmployees/ReorderDepartments/ToggleHide) - Department 모델 options cast 추가 - Swagger 문서 (OrgChartApi.php) 생성 - hr.php 라우트 그룹 추가 (/v1/org-chart)
This commit is contained in:
82
app/Http/Controllers/Api/V1/OrgChartController.php
Normal file
82
app/Http/Controllers/Api/V1/OrgChartController.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\OrgChart\AssignRequest;
|
||||
use App\Http\Requests\OrgChart\ReorderDepartmentsRequest;
|
||||
use App\Http\Requests\OrgChart\ReorderEmployeesRequest;
|
||||
use App\Http\Requests\OrgChart\ToggleHideRequest;
|
||||
use App\Http\Requests\OrgChart\UnassignRequest;
|
||||
use App\Services\OrgChartService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class OrgChartController extends Controller
|
||||
{
|
||||
public function __construct(private OrgChartService $service) {}
|
||||
|
||||
// GET /v1/org-chart
|
||||
public function index(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->getOrgChart($request->all());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
// GET /v1/org-chart/stats
|
||||
public function stats()
|
||||
{
|
||||
return ApiResponse::handle(function () {
|
||||
return $this->service->getStats();
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
// GET /v1/org-chart/unassigned
|
||||
public function unassigned(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->getUnassigned($request->all());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
// POST /v1/org-chart/assign
|
||||
public function assign(AssignRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->assign($request->validated());
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
// POST /v1/org-chart/unassign
|
||||
public function unassign(UnassignRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->unassign($request->validated());
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
// PUT /v1/org-chart/reorder-employees
|
||||
public function reorderEmployees(ReorderEmployeesRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->reorderEmployees($request->validated());
|
||||
}, __('message.reordered'));
|
||||
}
|
||||
|
||||
// PUT /v1/org-chart/reorder-departments
|
||||
public function reorderDepartments(ReorderDepartmentsRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->reorderDepartments($request->validated());
|
||||
}, __('message.reordered'));
|
||||
}
|
||||
|
||||
// PATCH /v1/org-chart/departments/{id}/toggle-hide
|
||||
public function toggleHide(int $id, ToggleHideRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id, $request) {
|
||||
return $this->service->toggleHide($id, $request->validated());
|
||||
}, __('message.updated'));
|
||||
}
|
||||
}
|
||||
21
app/Http/Requests/OrgChart/AssignRequest.php
Normal file
21
app/Http/Requests/OrgChart/AssignRequest.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\OrgChart;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class AssignRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'employee_id' => 'required|integer',
|
||||
'department_id' => 'required|integer',
|
||||
];
|
||||
}
|
||||
}
|
||||
23
app/Http/Requests/OrgChart/ReorderDepartmentsRequest.php
Normal file
23
app/Http/Requests/OrgChart/ReorderDepartmentsRequest.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\OrgChart;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ReorderDepartmentsRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'orders' => 'required|array',
|
||||
'orders.*.id' => 'required|integer',
|
||||
'orders.*.parent_id' => 'nullable|integer',
|
||||
'orders.*.sort_order' => 'required|integer',
|
||||
];
|
||||
}
|
||||
}
|
||||
22
app/Http/Requests/OrgChart/ReorderEmployeesRequest.php
Normal file
22
app/Http/Requests/OrgChart/ReorderEmployeesRequest.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\OrgChart;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ReorderEmployeesRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'moves' => 'required|array',
|
||||
'moves.*.employee_id' => 'required|integer',
|
||||
'moves.*.department_id' => 'nullable|integer',
|
||||
];
|
||||
}
|
||||
}
|
||||
20
app/Http/Requests/OrgChart/ToggleHideRequest.php
Normal file
20
app/Http/Requests/OrgChart/ToggleHideRequest.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\OrgChart;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ToggleHideRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'hidden' => 'required|boolean',
|
||||
];
|
||||
}
|
||||
}
|
||||
20
app/Http/Requests/OrgChart/UnassignRequest.php
Normal file
20
app/Http/Requests/OrgChart/UnassignRequest.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\OrgChart;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UnassignRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'employee_id' => 'required|integer',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ class Department extends Model
|
||||
'parent_id' => 'int',
|
||||
'is_active' => 'bool',
|
||||
'sort_order' => 'int',
|
||||
'options' => 'array',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
|
||||
308
app/Services/OrgChartService.php
Normal file
308
app/Services/OrgChartService.php
Normal file
@@ -0,0 +1,308 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Tenants\Department;
|
||||
use App\Models\Tenants\TenantUserProfile;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class OrgChartService extends Service
|
||||
{
|
||||
/**
|
||||
* 조직도 전체 조회 (부서 트리 + 직원 + 통계)
|
||||
*/
|
||||
public function getOrgChart(array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$includeHidden = filter_var($params['include_hidden'] ?? true, FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
// 부서 전체 조회 (트리 구성을 위해 flat으로 가져옴)
|
||||
$deptQuery = Department::where('tenant_id', $tenantId)
|
||||
->where('is_active', true);
|
||||
|
||||
if (! $includeHidden) {
|
||||
$deptQuery->where(function ($q) {
|
||||
$q->whereNull('options->orgchart_hidden')
|
||||
->orWhere('options->orgchart_hidden', false);
|
||||
});
|
||||
}
|
||||
|
||||
$departments = $deptQuery
|
||||
->orderBy('sort_order')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
// 전체 활성 직원
|
||||
$employees = TenantUserProfile::where('tenant_id', $tenantId)
|
||||
->active()
|
||||
->with(['user:id,name,email'])
|
||||
->orderBy('display_name')
|
||||
->get()
|
||||
->map(fn ($e) => [
|
||||
'id' => $e->id,
|
||||
'user_id' => $e->user_id,
|
||||
'department_id' => $e->department_id,
|
||||
'display_name' => $e->display_name ?? $e->user?->name ?? '(이름없음)',
|
||||
'position_label' => $e->position_label,
|
||||
])
|
||||
->values();
|
||||
|
||||
// 회사 정보
|
||||
$tenant = \App\Models\Tenants\Tenant::find($tenantId);
|
||||
|
||||
// 통계
|
||||
$total = $employees->count();
|
||||
$assigned = $employees->whereNotNull('department_id')->count();
|
||||
|
||||
// 부서 트리 구성
|
||||
$deptTree = $this->buildTree($departments, $employees);
|
||||
|
||||
// 숨겨진 부서 목록 (include_hidden=true일 때만 의미)
|
||||
$hiddenDepts = $departments->filter(fn ($d) => ($d->options['orgchart_hidden'] ?? false) === true)
|
||||
->map(fn ($d) => ['id' => $d->id, 'name' => $d->name, 'code' => $d->code])
|
||||
->values();
|
||||
|
||||
return [
|
||||
'company' => [
|
||||
'name' => $tenant->company_name ?? 'SAM',
|
||||
'ceo_name' => $tenant->ceo_name ?? '',
|
||||
],
|
||||
'departments' => $deptTree,
|
||||
'hidden_departments' => $hiddenDepts,
|
||||
'unassigned' => $employees->whereNull('department_id')->values(),
|
||||
'stats' => [
|
||||
'total' => $total,
|
||||
'assigned' => $assigned,
|
||||
'unassigned' => $total - $assigned,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 조직도 통계
|
||||
*/
|
||||
public function getStats(): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$total = TenantUserProfile::where('tenant_id', $tenantId)->active()->count();
|
||||
$assigned = TenantUserProfile::where('tenant_id', $tenantId)->active()->whereNotNull('department_id')->count();
|
||||
|
||||
return [
|
||||
'total' => $total,
|
||||
'assigned' => $assigned,
|
||||
'unassigned' => $total - $assigned,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 미배치 직원 목록
|
||||
*/
|
||||
public function getUnassigned(array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$query = TenantUserProfile::where('tenant_id', $tenantId)
|
||||
->active()
|
||||
->whereNull('department_id')
|
||||
->with(['user:id,name,email'])
|
||||
->orderBy('display_name');
|
||||
|
||||
if (! empty($params['q'])) {
|
||||
$q = $params['q'];
|
||||
$query->where(function ($w) use ($q) {
|
||||
$w->where('display_name', 'like', "%{$q}%")
|
||||
->orWhereHas('user', function ($u) use ($q) {
|
||||
$u->where('name', 'like', "%{$q}%");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return $query->get()
|
||||
->map(fn ($e) => [
|
||||
'id' => $e->id,
|
||||
'user_id' => $e->user_id,
|
||||
'display_name' => $e->display_name ?? $e->user?->name ?? '(이름없음)',
|
||||
'position_label' => $e->position_label,
|
||||
])
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 직원 부서 배치
|
||||
*/
|
||||
public function assign(array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$employee = TenantUserProfile::where('tenant_id', $tenantId)
|
||||
->where('id', $params['employee_id'])
|
||||
->first();
|
||||
|
||||
if (! $employee) {
|
||||
return ['error' => __('error.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
$dept = Department::where('tenant_id', $tenantId)
|
||||
->where('id', $params['department_id'])
|
||||
->first();
|
||||
|
||||
if (! $dept) {
|
||||
return ['error' => __('error.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
$employee->department_id = $params['department_id'];
|
||||
$employee->save();
|
||||
|
||||
return [
|
||||
'employee_id' => $employee->id,
|
||||
'department_id' => $params['department_id'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 직원 미배치 처리
|
||||
*/
|
||||
public function unassign(array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$employee = TenantUserProfile::where('tenant_id', $tenantId)
|
||||
->where('id', $params['employee_id'])
|
||||
->first();
|
||||
|
||||
if (! $employee) {
|
||||
return ['error' => __('error.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
$employee->department_id = null;
|
||||
$employee->save();
|
||||
|
||||
return ['employee_id' => $employee->id, 'department_id' => null];
|
||||
}
|
||||
|
||||
/**
|
||||
* 직원 일괄 배치/이동
|
||||
*/
|
||||
public function reorderEmployees(array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
DB::transaction(function () use ($params, $tenantId) {
|
||||
foreach ($params['moves'] as $move) {
|
||||
TenantUserProfile::where('tenant_id', $tenantId)
|
||||
->where('id', $move['employee_id'])
|
||||
->update(['department_id' => $move['department_id']]);
|
||||
}
|
||||
});
|
||||
|
||||
return ['processed' => count($params['moves'])];
|
||||
}
|
||||
|
||||
/**
|
||||
* 부서 순서/계층 일괄 변경
|
||||
*/
|
||||
public function reorderDepartments(array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
// 순환 참조 검증
|
||||
$orders = collect($params['orders']);
|
||||
foreach ($orders as $order) {
|
||||
if ($order['parent_id'] !== null && $order['parent_id'] === $order['id']) {
|
||||
return ['error' => '자기 자신을 상위 부서로 설정할 수 없습니다.', 'code' => 422];
|
||||
}
|
||||
}
|
||||
|
||||
// 순환 참조 심층 검증 (A→B→C→A 같은 케이스)
|
||||
$parentMap = $orders->pluck('parent_id', 'id')->toArray();
|
||||
foreach ($parentMap as $id => $parentId) {
|
||||
if ($parentId === null) {
|
||||
continue;
|
||||
}
|
||||
$visited = [$id];
|
||||
$current = $parentId;
|
||||
while ($current !== null && isset($parentMap[$current])) {
|
||||
if (in_array($current, $visited)) {
|
||||
return ['error' => '순환 참조가 감지되었습니다.', 'code' => 422];
|
||||
}
|
||||
$visited[] = $current;
|
||||
$current = $parentMap[$current];
|
||||
}
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($params, $tenantId) {
|
||||
foreach ($params['orders'] as $order) {
|
||||
Department::where('tenant_id', $tenantId)
|
||||
->where('id', $order['id'])
|
||||
->update([
|
||||
'parent_id' => $order['parent_id'],
|
||||
'sort_order' => $order['sort_order'],
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
return ['processed' => count($params['orders'])];
|
||||
}
|
||||
|
||||
/**
|
||||
* 부서 숨기기/표시 토글
|
||||
*/
|
||||
public function toggleHide(int $departmentId, array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$dept = Department::where('tenant_id', $tenantId)
|
||||
->where('id', $departmentId)
|
||||
->first();
|
||||
|
||||
if (! $dept) {
|
||||
return ['error' => __('error.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
$options = $dept->options ?? [];
|
||||
$options['orgchart_hidden'] = $params['hidden'];
|
||||
$dept->options = $options;
|
||||
$dept->save();
|
||||
|
||||
return [
|
||||
'id' => $dept->id,
|
||||
'orgchart_hidden' => $params['hidden'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* flat 부서 목록 → 트리 구조 변환
|
||||
*/
|
||||
private function buildTree($departments, $employees): array
|
||||
{
|
||||
$deptMap = [];
|
||||
foreach ($departments as $dept) {
|
||||
$deptMap[$dept->id] = [
|
||||
'id' => $dept->id,
|
||||
'name' => $dept->name,
|
||||
'code' => $dept->code,
|
||||
'parent_id' => $dept->parent_id,
|
||||
'sort_order' => $dept->sort_order,
|
||||
'is_active' => $dept->is_active,
|
||||
'orgchart_hidden' => $dept->options['orgchart_hidden'] ?? false,
|
||||
'children' => [],
|
||||
'employees' => $employees->where('department_id', $dept->id)->values()->toArray(),
|
||||
];
|
||||
}
|
||||
|
||||
$tree = [];
|
||||
foreach ($deptMap as $id => &$node) {
|
||||
if ($node['parent_id'] === null || ! isset($deptMap[$node['parent_id']])) {
|
||||
$tree[] = &$node;
|
||||
} else {
|
||||
$deptMap[$node['parent_id']]['children'][] = &$node;
|
||||
}
|
||||
}
|
||||
unset($node);
|
||||
|
||||
return $tree;
|
||||
}
|
||||
}
|
||||
359
app/Swagger/v1/OrgChartApi.php
Normal file
359
app/Swagger/v1/OrgChartApi.php
Normal file
@@ -0,0 +1,359 @@
|
||||
<?php
|
||||
|
||||
namespace App\Swagger\v1;
|
||||
|
||||
/**
|
||||
* @OA\Tag(
|
||||
* name="OrgChart",
|
||||
* description="조직도 관리"
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="OrgChartEmployee",
|
||||
* type="object",
|
||||
*
|
||||
* @OA\Property(property="id", type="integer", example=5, description="프로필 ID"),
|
||||
* @OA\Property(property="user_id", type="integer", example=3),
|
||||
* @OA\Property(property="department_id", type="integer", nullable=true, example=1),
|
||||
* @OA\Property(property="display_name", type="string", example="홍길동"),
|
||||
* @OA\Property(property="position_label", type="string", nullable=true, example="과장")
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="OrgChartDepartmentNode",
|
||||
* type="object",
|
||||
*
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="name", type="string", example="경영지원팀"),
|
||||
* @OA\Property(property="code", type="string", nullable=true, example="MGT"),
|
||||
* @OA\Property(property="parent_id", type="integer", nullable=true),
|
||||
* @OA\Property(property="sort_order", type="integer", example=1),
|
||||
* @OA\Property(property="is_active", type="boolean", example=true),
|
||||
* @OA\Property(property="orgchart_hidden", type="boolean", example=false),
|
||||
* @OA\Property(property="children", type="array", @OA\Items(ref="#/components/schemas/OrgChartDepartmentNode")),
|
||||
* @OA\Property(property="employees", type="array", @OA\Items(ref="#/components/schemas/OrgChartEmployee"))
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="OrgChartStats",
|
||||
* type="object",
|
||||
*
|
||||
* @OA\Property(property="total", type="integer", example=50),
|
||||
* @OA\Property(property="assigned", type="integer", example=42),
|
||||
* @OA\Property(property="unassigned", type="integer", example=8)
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="OrgChartAssignRequest",
|
||||
* type="object",
|
||||
* required={"employee_id", "department_id"},
|
||||
*
|
||||
* @OA\Property(property="employee_id", type="integer", example=5, description="직원 프로필 ID"),
|
||||
* @OA\Property(property="department_id", type="integer", example=3, description="배치할 부서 ID")
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="OrgChartUnassignRequest",
|
||||
* type="object",
|
||||
* required={"employee_id"},
|
||||
*
|
||||
* @OA\Property(property="employee_id", type="integer", example=5, description="직원 프로필 ID")
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="OrgChartReorderEmployeesRequest",
|
||||
* type="object",
|
||||
* required={"moves"},
|
||||
*
|
||||
* @OA\Property(
|
||||
* property="moves",
|
||||
* type="array",
|
||||
*
|
||||
* @OA\Items(
|
||||
* type="object",
|
||||
* required={"employee_id"},
|
||||
*
|
||||
* @OA\Property(property="employee_id", type="integer", example=5),
|
||||
* @OA\Property(property="department_id", type="integer", nullable=true, example=3)
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="OrgChartReorderDepartmentsRequest",
|
||||
* type="object",
|
||||
* required={"orders"},
|
||||
*
|
||||
* @OA\Property(
|
||||
* property="orders",
|
||||
* type="array",
|
||||
*
|
||||
* @OA\Items(
|
||||
* type="object",
|
||||
* required={"id", "parent_id", "sort_order"},
|
||||
*
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="parent_id", type="integer", nullable=true, example=null),
|
||||
* @OA\Property(property="sort_order", type="integer", example=0)
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="OrgChartToggleHideRequest",
|
||||
* type="object",
|
||||
* required={"hidden"},
|
||||
*
|
||||
* @OA\Property(property="hidden", type="boolean", example=true, description="숨기기 여부")
|
||||
* )
|
||||
*/
|
||||
class OrgChartApi
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/org-chart",
|
||||
* tags={"OrgChart"},
|
||||
* summary="조직도 전체 조회 (부서 트리 + 직원 + 통계)",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
*
|
||||
* @OA\Parameter(name="include_hidden", in="query", description="숨겨진 부서 포함 여부 (기본: true)", @OA\Schema(type="boolean")),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
* type="object",
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* type="object",
|
||||
* @OA\Property(property="company", type="object",
|
||||
* @OA\Property(property="name", type="string", example="주일산업"),
|
||||
* @OA\Property(property="ceo_name", type="string", example="홍길동")
|
||||
* ),
|
||||
* @OA\Property(property="departments", type="array", @OA\Items(ref="#/components/schemas/OrgChartDepartmentNode")),
|
||||
* @OA\Property(property="hidden_departments", type="array", @OA\Items(type="object",
|
||||
* @OA\Property(property="id", type="integer"),
|
||||
* @OA\Property(property="name", type="string"),
|
||||
* @OA\Property(property="code", type="string")
|
||||
* )),
|
||||
* @OA\Property(property="unassigned", type="array", @OA\Items(ref="#/components/schemas/OrgChartEmployee")),
|
||||
* @OA\Property(property="stats", ref="#/components/schemas/OrgChartStats")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function index() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/org-chart/stats",
|
||||
* tags={"OrgChart"},
|
||||
* summary="조직도 통계 (전체/배치/미배치 인원)",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
* type="object",
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/OrgChartStats")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function stats() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/org-chart/unassigned",
|
||||
* tags={"OrgChart"},
|
||||
* summary="미배치 직원 목록",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
*
|
||||
* @OA\Parameter(name="q", in="query", description="이름 검색", @OA\Schema(type="string")),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
* type="object",
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/OrgChartEmployee"))
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function unassigned() {}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/org-chart/assign",
|
||||
* tags={"OrgChart"},
|
||||
* summary="직원 부서 배치",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
*
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
*
|
||||
* @OA\JsonContent(ref="#/components/schemas/OrgChartAssignRequest")
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
* type="object",
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="employee_id", type="integer"),
|
||||
* @OA\Property(property="department_id", type="integer")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function assign() {}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/org-chart/unassign",
|
||||
* tags={"OrgChart"},
|
||||
* summary="직원 미배치 처리",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
*
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
*
|
||||
* @OA\JsonContent(ref="#/components/schemas/OrgChartUnassignRequest")
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
* type="object",
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="employee_id", type="integer"),
|
||||
* @OA\Property(property="department_id", type="integer", nullable=true)
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function unassign() {}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/api/v1/org-chart/reorder-employees",
|
||||
* tags={"OrgChart"},
|
||||
* summary="직원 일괄 배치/이동",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
*
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
*
|
||||
* @OA\JsonContent(ref="#/components/schemas/OrgChartReorderEmployeesRequest")
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
* type="object",
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="processed", type="integer", example=3)
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function reorderEmployees() {}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/api/v1/org-chart/reorder-departments",
|
||||
* tags={"OrgChart"},
|
||||
* summary="부서 순서/계층 일괄 변경",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
*
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
*
|
||||
* @OA\JsonContent(ref="#/components/schemas/OrgChartReorderDepartmentsRequest")
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
* type="object",
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="processed", type="integer", example=5)
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function reorderDepartments() {}
|
||||
|
||||
/**
|
||||
* @OA\Patch(
|
||||
* path="/api/v1/org-chart/departments/{id}/toggle-hide",
|
||||
* tags={"OrgChart"},
|
||||
* summary="부서 숨기기/표시 토글",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
*
|
||||
* @OA\Parameter(name="id", in="path", required=true, description="부서 ID", @OA\Schema(type="integer")),
|
||||
*
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
*
|
||||
* @OA\JsonContent(ref="#/components/schemas/OrgChartToggleHideRequest")
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
* type="object",
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="id", type="integer"),
|
||||
* @OA\Property(property="orgchart_hidden", type="boolean")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function toggleHide() {}
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
use App\Http\Controllers\Api\V1\Construction\StructureReviewController;
|
||||
use App\Http\Controllers\Api\V1\DepartmentController;
|
||||
use App\Http\Controllers\Api\V1\EmployeeController;
|
||||
use App\Http\Controllers\Api\V1\OrgChartController;
|
||||
use App\Http\Controllers\Api\V1\LeaveController;
|
||||
use App\Http\Controllers\Api\V1\LeavePolicyController;
|
||||
use App\Http\Controllers\Api\V1\PositionController;
|
||||
@@ -50,6 +51,18 @@
|
||||
Route::delete('/{id}/permissions/{permission}', [DepartmentController::class, 'revokePermissions'])->name('v1.departments.permissions.revoke'); // 권한 제거(해당 메뉴 범위까지)
|
||||
});
|
||||
|
||||
// OrgChart API (조직도 관리)
|
||||
Route::prefix('org-chart')->group(function () {
|
||||
Route::get('', [OrgChartController::class, 'index'])->name('v1.org-chart.index');
|
||||
Route::get('/stats', [OrgChartController::class, 'stats'])->name('v1.org-chart.stats');
|
||||
Route::get('/unassigned', [OrgChartController::class, 'unassigned'])->name('v1.org-chart.unassigned');
|
||||
Route::post('/assign', [OrgChartController::class, 'assign'])->name('v1.org-chart.assign');
|
||||
Route::post('/unassign', [OrgChartController::class, 'unassign'])->name('v1.org-chart.unassign');
|
||||
Route::put('/reorder-employees', [OrgChartController::class, 'reorderEmployees'])->name('v1.org-chart.reorder-employees');
|
||||
Route::put('/reorder-departments', [OrgChartController::class, 'reorderDepartments'])->name('v1.org-chart.reorder-departments');
|
||||
Route::patch('/departments/{id}/toggle-hide', [OrgChartController::class, 'toggleHide'])->whereNumber('id')->name('v1.org-chart.toggle-hide');
|
||||
});
|
||||
|
||||
// Position API (직급/직책 통합 관리)
|
||||
Route::prefix('positions')->group(function () {
|
||||
Route::get('', [PositionController::class, 'index'])->name('v1.positions.index');
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"servers": [
|
||||
{
|
||||
"url": "https://api.sam.kr/",
|
||||
"description": "SAM API 서버"
|
||||
"description": "SAM관리시스템 API 서버"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
@@ -9577,9 +9577,15 @@
|
||||
],
|
||||
"properties": {
|
||||
"code": {
|
||||
"description": "LOT 코드",
|
||||
"description": "코드 체계 (prod+spec)",
|
||||
"type": "string",
|
||||
"example": "RM260319"
|
||||
"example": "RM"
|
||||
},
|
||||
"lot_no": {
|
||||
"description": "LOT 번호",
|
||||
"type": "string",
|
||||
"example": "RM260319",
|
||||
"nullable": true
|
||||
},
|
||||
"item_name": {
|
||||
"type": "string",
|
||||
@@ -36518,6 +36524,502 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/org-chart": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"OrgChart"
|
||||
],
|
||||
"summary": "조직도 전체 조회 (부서 트리 + 직원 + 통계)",
|
||||
"operationId": "d92f1fc2da2664ef3543e4e1328d25e9",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "include_hidden",
|
||||
"in": "query",
|
||||
"description": "숨겨진 부서 포함 여부 (기본: true)",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "성공",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"properties": {
|
||||
"company": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "주일산업"
|
||||
},
|
||||
"ceo_name": {
|
||||
"type": "string",
|
||||
"example": "홍길동"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"departments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/OrgChartDepartmentNode"
|
||||
}
|
||||
},
|
||||
"hidden_departments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"code": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"unassigned": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/OrgChartEmployee"
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"$ref": "#/components/schemas/OrgChartStats"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/org-chart/stats": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"OrgChart"
|
||||
],
|
||||
"summary": "조직도 통계 (전체/배치/미배치 인원)",
|
||||
"operationId": "c00470fa81707bdf862c9eec4139d0ba",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "성공",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/OrgChartStats"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/org-chart/unassigned": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"OrgChart"
|
||||
],
|
||||
"summary": "미배치 직원 목록",
|
||||
"operationId": "13121a2e9ccefcfbf7cc60c727522bc4",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
"description": "이름 검색",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "성공",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/OrgChartEmployee"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/org-chart/assign": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"OrgChart"
|
||||
],
|
||||
"summary": "직원 부서 배치",
|
||||
"operationId": "6a7281807e859f10409469dda17fdd89",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/OrgChartAssignRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "성공",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"properties": {
|
||||
"employee_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"department_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/org-chart/unassign": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"OrgChart"
|
||||
],
|
||||
"summary": "직원 미배치 처리",
|
||||
"operationId": "387e1c87037f9bf708b7255c35f5e86c",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/OrgChartUnassignRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "성공",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"properties": {
|
||||
"employee_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"department_id": {
|
||||
"type": "integer",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/org-chart/reorder-employees": {
|
||||
"put": {
|
||||
"tags": [
|
||||
"OrgChart"
|
||||
],
|
||||
"summary": "직원 일괄 배치/이동",
|
||||
"operationId": "7af302b074864287d8f4b09af4e27c9c",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/OrgChartReorderEmployeesRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "성공",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"properties": {
|
||||
"processed": {
|
||||
"type": "integer",
|
||||
"example": 3
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/org-chart/reorder-departments": {
|
||||
"put": {
|
||||
"tags": [
|
||||
"OrgChart"
|
||||
],
|
||||
"summary": "부서 순서/계층 일괄 변경",
|
||||
"operationId": "da0aa294c8ebf3ab4f7550f56ca901f3",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/OrgChartReorderDepartmentsRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "성공",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"properties": {
|
||||
"processed": {
|
||||
"type": "integer",
|
||||
"example": 5
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/org-chart/departments/{id}/toggle-hide": {
|
||||
"patch": {
|
||||
"tags": [
|
||||
"OrgChart"
|
||||
],
|
||||
"summary": "부서 숨기기/표시 토글",
|
||||
"operationId": "b3913a80123a5bc5b8f7e4d07beed9c8",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"description": "부서 ID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/OrgChartToggleHideRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "성공",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"orgchart_hidden": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/payments": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -49474,14 +49976,9 @@
|
||||
{
|
||||
"name": "delivery_method",
|
||||
"in": "query",
|
||||
"description": "배송방식",
|
||||
"description": "배송방식 (common_codes delivery_method 참조)",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pickup",
|
||||
"direct",
|
||||
"logistics"
|
||||
]
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -64904,9 +65401,15 @@
|
||||
"example": 431
|
||||
},
|
||||
"code": {
|
||||
"description": "LOT 코드: {제품Code}{종류Code}{YYMMDD}",
|
||||
"description": "코드 체계 (제품Code+종류Code)",
|
||||
"type": "string",
|
||||
"example": "RS260319"
|
||||
"example": "RS"
|
||||
},
|
||||
"lot_no": {
|
||||
"description": "LOT 번호 (code+날짜+일련번호)",
|
||||
"type": "string",
|
||||
"example": "RS260319",
|
||||
"nullable": true
|
||||
},
|
||||
"legacy_code": {
|
||||
"description": "이전 코드 (items 기반)",
|
||||
@@ -80193,6 +80696,204 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"OrgChartEmployee": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "프로필 ID",
|
||||
"type": "integer",
|
||||
"example": 5
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer",
|
||||
"example": 3
|
||||
},
|
||||
"department_id": {
|
||||
"type": "integer",
|
||||
"example": 1,
|
||||
"nullable": true
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string",
|
||||
"example": "홍길동"
|
||||
},
|
||||
"position_label": {
|
||||
"type": "string",
|
||||
"example": "과장",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"OrgChartDepartmentNode": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "경영지원팀"
|
||||
},
|
||||
"code": {
|
||||
"type": "string",
|
||||
"example": "MGT",
|
||||
"nullable": true
|
||||
},
|
||||
"parent_id": {
|
||||
"type": "integer",
|
||||
"nullable": true
|
||||
},
|
||||
"sort_order": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"is_active": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"orgchart_hidden": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"children": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/OrgChartDepartmentNode"
|
||||
}
|
||||
},
|
||||
"employees": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/OrgChartEmployee"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"OrgChartStats": {
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "integer",
|
||||
"example": 50
|
||||
},
|
||||
"assigned": {
|
||||
"type": "integer",
|
||||
"example": 42
|
||||
},
|
||||
"unassigned": {
|
||||
"type": "integer",
|
||||
"example": 8
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"OrgChartAssignRequest": {
|
||||
"required": [
|
||||
"employee_id",
|
||||
"department_id"
|
||||
],
|
||||
"properties": {
|
||||
"employee_id": {
|
||||
"description": "직원 프로필 ID",
|
||||
"type": "integer",
|
||||
"example": 5
|
||||
},
|
||||
"department_id": {
|
||||
"description": "배치할 부서 ID",
|
||||
"type": "integer",
|
||||
"example": 3
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"OrgChartUnassignRequest": {
|
||||
"required": [
|
||||
"employee_id"
|
||||
],
|
||||
"properties": {
|
||||
"employee_id": {
|
||||
"description": "직원 프로필 ID",
|
||||
"type": "integer",
|
||||
"example": 5
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"OrgChartReorderEmployeesRequest": {
|
||||
"required": [
|
||||
"moves"
|
||||
],
|
||||
"properties": {
|
||||
"moves": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"required": [
|
||||
"employee_id"
|
||||
],
|
||||
"properties": {
|
||||
"employee_id": {
|
||||
"type": "integer",
|
||||
"example": 5
|
||||
},
|
||||
"department_id": {
|
||||
"type": "integer",
|
||||
"example": 3,
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"OrgChartReorderDepartmentsRequest": {
|
||||
"required": [
|
||||
"orders"
|
||||
],
|
||||
"properties": {
|
||||
"orders": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"required": [
|
||||
"id",
|
||||
"parent_id",
|
||||
"sort_order"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"parent_id": {
|
||||
"type": "integer",
|
||||
"example": null,
|
||||
"nullable": true
|
||||
},
|
||||
"sort_order": {
|
||||
"type": "integer",
|
||||
"example": 0
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"OrgChartToggleHideRequest": {
|
||||
"required": [
|
||||
"hidden"
|
||||
],
|
||||
"properties": {
|
||||
"hidden": {
|
||||
"description": "숨기기 여부",
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Payment": {
|
||||
"description": "결제 정보",
|
||||
"properties": {
|
||||
@@ -87955,13 +88656,8 @@
|
||||
"example": "보통"
|
||||
},
|
||||
"delivery_method": {
|
||||
"description": "배송방식",
|
||||
"description": "배송방식 (common_codes delivery_method 참조)",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pickup",
|
||||
"direct",
|
||||
"logistics"
|
||||
],
|
||||
"example": "pickup"
|
||||
},
|
||||
"delivery_method_label": {
|
||||
@@ -88371,13 +89067,8 @@
|
||||
"example": "normal"
|
||||
},
|
||||
"delivery_method": {
|
||||
"description": "배송방식",
|
||||
"description": "배송방식 (common_codes delivery_method 참조)",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pickup",
|
||||
"direct",
|
||||
"logistics"
|
||||
],
|
||||
"example": "pickup"
|
||||
},
|
||||
"client_id": {
|
||||
@@ -95014,6 +95705,10 @@
|
||||
"name": "Order",
|
||||
"description": "수주관리 API"
|
||||
},
|
||||
{
|
||||
"name": "OrgChart",
|
||||
"description": "조직도 관리"
|
||||
},
|
||||
{
|
||||
"name": "Payments",
|
||||
"description": "결제 관리"
|
||||
|
||||
Reference in New Issue
Block a user