feat: 가지급금 대시보드 API 개발 (Phase 1.2)
- LoanService.dashboard() 메서드 추가 (요약 + 목록) - LoanController.dashboard() 액션 추가 - GET /api/v1/loans/dashboard 라우트 등록 - Swagger LoanDashboard 스키마 및 엔드포인트 문서화 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,16 @@ public function summary(LoanIndexRequest $request): JsonResponse
|
||||
return ApiResponse::handle('message.fetched', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 가지급금 대시보드
|
||||
*/
|
||||
public function dashboard(): JsonResponse
|
||||
{
|
||||
$result = $this->loanService->dashboard();
|
||||
|
||||
return ApiResponse::handle('message.fetched', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 가지급금 등록
|
||||
*/
|
||||
|
||||
@@ -362,6 +362,66 @@ public function calculateInterest(int $year, ?int $userId = null): array
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 가지급금 대시보드 데이터
|
||||
*
|
||||
* CEO 대시보드 카드/가지급금 관리 섹션(cm2) 모달용 데이터 제공
|
||||
*
|
||||
* @return array{
|
||||
* summary: array{
|
||||
* total_outstanding: float,
|
||||
* recognized_interest: float,
|
||||
* outstanding_count: int
|
||||
* },
|
||||
* loans: array
|
||||
* }
|
||||
*/
|
||||
public function dashboard(): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$currentYear = now()->year;
|
||||
|
||||
// 1. Summary 데이터
|
||||
$summaryData = $this->summary();
|
||||
|
||||
// 2. 인정이자 계산 (현재 연도 기준)
|
||||
$interestData = $this->calculateInterest($currentYear);
|
||||
$recognizedInterest = $interestData['summary']['total_recognized_interest'] ?? 0;
|
||||
|
||||
// 3. 가지급금 목록 (최근 10건, 미정산 우선)
|
||||
$loans = Loan::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->with(['user:id,name,email', 'withdrawal'])
|
||||
->orderByRaw('CASE WHEN status = ? THEN 0 WHEN status = ? THEN 1 ELSE 2 END', [
|
||||
Loan::STATUS_OUTSTANDING,
|
||||
Loan::STATUS_PARTIAL,
|
||||
])
|
||||
->orderByDesc('loan_date')
|
||||
->limit(10)
|
||||
->get()
|
||||
->map(function ($loan) {
|
||||
return [
|
||||
'id' => $loan->id,
|
||||
'loan_date' => $loan->loan_date->format('Y-m-d'),
|
||||
'user_name' => $loan->user?->name ?? '미지정',
|
||||
'category' => $loan->withdrawal_id ? '카드' : '계좌',
|
||||
'amount' => (float) $loan->amount,
|
||||
'status' => $loan->status,
|
||||
'content' => $loan->purpose ?? '',
|
||||
];
|
||||
})
|
||||
->toArray();
|
||||
|
||||
return [
|
||||
'summary' => [
|
||||
'total_outstanding' => (float) $summaryData['total_outstanding'],
|
||||
'recognized_interest' => (float) $recognizedInterest,
|
||||
'outstanding_count' => (int) $summaryData['outstanding_count'],
|
||||
],
|
||||
'loans' => $loans,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 인정이자 리포트 (연도별 요약)
|
||||
*/
|
||||
|
||||
@@ -171,6 +171,33 @@
|
||||
* description="전체 합계"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="LoanDashboard",
|
||||
* type="object",
|
||||
* description="가지급금 대시보드 응답",
|
||||
*
|
||||
* @OA\Property(property="summary", type="object",
|
||||
* @OA\Property(property="total_outstanding", type="number", format="float", example=20000000, description="미정산 가지급금 총액"),
|
||||
* @OA\Property(property="recognized_interest", type="number", format="float", example=920000, description="인정이자 (연 4.6%)"),
|
||||
* @OA\Property(property="outstanding_count", type="integer", example=5, description="미정산 건수"),
|
||||
* description="요약 정보"
|
||||
* ),
|
||||
* @OA\Property(property="loans", type="array",
|
||||
*
|
||||
* @OA\Items(
|
||||
*
|
||||
* @OA\Property(property="id", type="integer", example=1, description="가지급금 ID"),
|
||||
* @OA\Property(property="loan_date", type="string", format="date", example="2025-01-15", description="지급일"),
|
||||
* @OA\Property(property="user_name", type="string", example="홍길동", description="수령자명"),
|
||||
* @OA\Property(property="category", type="string", example="카드", enum={"카드","계좌"}, description="구분"),
|
||||
* @OA\Property(property="amount", type="number", format="float", example=5000000, description="금액"),
|
||||
* @OA\Property(property="status", type="string", example="outstanding", enum={"outstanding","settled","partial"}, description="상태"),
|
||||
* @OA\Property(property="content", type="string", example="출장 경비", description="내용/목적")
|
||||
* ),
|
||||
* description="최근 가지급금 목록 (미정산 우선, 최대 10건)"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
class LoanApi
|
||||
{
|
||||
@@ -290,6 +317,36 @@ public function store() {}
|
||||
*/
|
||||
public function summary() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/loans/dashboard",
|
||||
* tags={"Loans"},
|
||||
* summary="가지급금 대시보드 조회",
|
||||
* description="CEO 대시보드 카드/가지급금 관리 섹션(cm2) 모달용 데이터를 조회합니다. 요약 정보와 최근 가지급금 목록을 반환합니다.",
|
||||
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="조회 성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
*
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
*
|
||||
* @OA\Property(property="data", ref="#/components/schemas/LoanDashboard")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function dashboard() {}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/loans/calculate-interest",
|
||||
|
||||
@@ -98,19 +98,19 @@
|
||||
use App\Http\Controllers\Api\V1\SiteController;
|
||||
use App\Http\Controllers\Api\V1\StatusBoardController;
|
||||
use App\Http\Controllers\Api\V1\StockController;
|
||||
use App\Http\Controllers\Api\V1\TodayIssueController;
|
||||
use App\Http\Controllers\Api\V1\SubscriptionController;
|
||||
use App\Http\Controllers\Api\V1\SystemBoardController;
|
||||
use App\Http\Controllers\Api\V1\SystemPostController;
|
||||
// 설계 전용 (디자인 네임스페이스)
|
||||
use App\Http\Controllers\Api\V1\TaxInvoiceController;
|
||||
// 설계 전용 (디자인 네임스페이스)
|
||||
use App\Http\Controllers\Api\V1\TenantController;
|
||||
use App\Http\Controllers\Api\V1\TenantFieldSettingController;
|
||||
use App\Http\Controllers\Api\V1\TenantOptionGroupController;
|
||||
use App\Http\Controllers\Api\V1\TenantOptionValueController;
|
||||
// 모델셋 관리 (견적 시스템)
|
||||
use App\Http\Controllers\Api\V1\TenantStatFieldController;
|
||||
// 모델셋 관리 (견적 시스템)
|
||||
use App\Http\Controllers\Api\V1\TenantUserProfileController;
|
||||
use App\Http\Controllers\Api\V1\TodayIssueController;
|
||||
use App\Http\Controllers\Api\V1\UserController;
|
||||
use App\Http\Controllers\Api\V1\UserInvitationController;
|
||||
use App\Http\Controllers\Api\V1\UserRoleController;
|
||||
@@ -575,6 +575,7 @@
|
||||
Route::get('', [LoanController::class, 'index'])->name('v1.loans.index');
|
||||
Route::post('', [LoanController::class, 'store'])->name('v1.loans.store');
|
||||
Route::get('/summary', [LoanController::class, 'summary'])->name('v1.loans.summary');
|
||||
Route::get('/dashboard', [LoanController::class, 'dashboard'])->name('v1.loans.dashboard');
|
||||
Route::post('/calculate-interest', [LoanController::class, 'calculateInterest'])->name('v1.loans.calculate-interest');
|
||||
Route::get('/interest-report/{year}', [LoanController::class, 'interestReport'])->whereNumber('year')->name('v1.loans.interest-report');
|
||||
Route::get('/{id}', [LoanController::class, 'show'])->whereNumber('id')->name('v1.loans.show');
|
||||
@@ -807,6 +808,7 @@
|
||||
Route::get('', [BillController::class, 'index'])->name('v1.bills.index');
|
||||
Route::post('', [BillController::class, 'store'])->name('v1.bills.store');
|
||||
Route::get('/summary', [BillController::class, 'summary'])->name('v1.bills.summary');
|
||||
Route::get('/dashboard-detail', [BillController::class, 'dashboardDetail'])->name('v1.bills.dashboard-detail');
|
||||
Route::get('/{id}', [BillController::class, 'show'])->whereNumber('id')->name('v1.bills.show');
|
||||
Route::put('/{id}', [BillController::class, 'update'])->whereNumber('id')->name('v1.bills.update');
|
||||
Route::delete('/{id}', [BillController::class, 'destroy'])->whereNumber('id')->name('v1.bills.destroy');
|
||||
|
||||
Reference in New Issue
Block a user