feat: I-6 일일 생산현황 API 구현
- DailyReportController: 일일 리포트 조회 API - DailyReportService: 생산현황 집계 로직 - Swagger 문서화 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
61
app/Http/Controllers/Api/V1/DailyReportController.php
Normal file
61
app/Http/Controllers/Api/V1/DailyReportController.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\DailyReportService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* 일일 보고서 컨트롤러
|
||||
*/
|
||||
class DailyReportController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected DailyReportService $service
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 어음 및 외상매출채권 현황 조회
|
||||
*/
|
||||
public function noteReceivables(Request $request): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$params = $request->validate([
|
||||
'date' => 'nullable|date',
|
||||
]);
|
||||
|
||||
return $this->service->noteReceivables($params);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 일별 계좌 현황 조회
|
||||
*/
|
||||
public function dailyAccounts(Request $request): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$params = $request->validate([
|
||||
'date' => 'nullable|date',
|
||||
]);
|
||||
|
||||
return $this->service->dailyAccounts($params);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 일일 보고서 요약 통계
|
||||
*/
|
||||
public function summary(Request $request): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$params = $request->validate([
|
||||
'date' => 'nullable|date',
|
||||
]);
|
||||
|
||||
return $this->service->summary($params);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
}
|
||||
161
app/Services/DailyReportService.php
Normal file
161
app/Services/DailyReportService.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Tenants\BankAccount;
|
||||
use App\Models\Tenants\Bill;
|
||||
use App\Models\Tenants\Deposit;
|
||||
use App\Models\Tenants\Withdrawal;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* 일일 보고서 서비스
|
||||
*/
|
||||
class DailyReportService extends Service
|
||||
{
|
||||
/**
|
||||
* 어음 및 외상매출채권 현황 조회
|
||||
*/
|
||||
public function noteReceivables(array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$date = isset($params['date']) ? Carbon::parse($params['date']) : Carbon::today();
|
||||
|
||||
// 수취어음 중 보관중 상태인 것만 조회 (만기일 기준)
|
||||
$bills = Bill::where('tenant_id', $tenantId)
|
||||
->where('bill_type', 'received')
|
||||
->where('status', 'stored')
|
||||
->where('maturity_date', '>=', $date->copy()->startOfDay())
|
||||
->orderBy('maturity_date', 'asc')
|
||||
->get();
|
||||
|
||||
return $bills->map(function ($bill) {
|
||||
return [
|
||||
'id' => (string) $bill->id,
|
||||
'content' => "(수취어음) {$bill->display_client_name} - {$bill->bill_number}",
|
||||
'current_balance' => (float) $bill->amount,
|
||||
'issue_date' => $bill->issue_date?->format('Y-m-d'),
|
||||
'due_date' => $bill->maturity_date?->format('Y-m-d'),
|
||||
];
|
||||
})->values()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 일별 계좌 현황 조회
|
||||
*/
|
||||
public function dailyAccounts(array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$date = isset($params['date']) ? Carbon::parse($params['date']) : Carbon::today();
|
||||
$startOfMonth = $date->copy()->startOfMonth();
|
||||
$endOfDay = $date->copy()->endOfDay();
|
||||
|
||||
// 활성 계좌 목록
|
||||
$accounts = BankAccount::where('tenant_id', $tenantId)
|
||||
->where('status', 'active')
|
||||
->orderBy('is_primary', 'desc')
|
||||
->orderBy('bank_name', 'asc')
|
||||
->get();
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
// 전월 이월: 이번 달 1일 이전까지의 누적 잔액
|
||||
$carryoverDeposits = Deposit::where('tenant_id', $tenantId)
|
||||
->where('bank_account_id', $account->id)
|
||||
->where('deposit_date', '<', $startOfMonth)
|
||||
->sum('amount');
|
||||
|
||||
$carryoverWithdrawals = Withdrawal::where('tenant_id', $tenantId)
|
||||
->where('bank_account_id', $account->id)
|
||||
->where('withdrawal_date', '<', $startOfMonth)
|
||||
->sum('amount');
|
||||
|
||||
$carryover = $carryoverDeposits - $carryoverWithdrawals;
|
||||
|
||||
// 당일 수입 (입금)
|
||||
$income = Deposit::where('tenant_id', $tenantId)
|
||||
->where('bank_account_id', $account->id)
|
||||
->whereBetween('deposit_date', [$startOfMonth, $endOfDay])
|
||||
->sum('amount');
|
||||
|
||||
// 당일 지출 (출금)
|
||||
$expense = Withdrawal::where('tenant_id', $tenantId)
|
||||
->where('bank_account_id', $account->id)
|
||||
->whereBetween('withdrawal_date', [$startOfMonth, $endOfDay])
|
||||
->sum('amount');
|
||||
|
||||
// 잔액 = 전월이월 + 수입 - 지출
|
||||
$balance = $carryover + $income - $expense;
|
||||
|
||||
// 매칭 상태: Deposit과 Withdrawal 금액이 일치하면 matched
|
||||
$matchStatus = abs($income - $expense) < 0.01 ? 'matched' : 'unmatched';
|
||||
|
||||
$result[] = [
|
||||
'id' => (string) $account->id,
|
||||
'category' => "{$account->bank_name} {$account->getMaskedAccountNumber()}",
|
||||
'match_status' => $matchStatus,
|
||||
'carryover' => (float) $carryover,
|
||||
'income' => (float) $income,
|
||||
'expense' => (float) $expense,
|
||||
'balance' => (float) $balance,
|
||||
'currency' => 'KRW', // 현재는 KRW만 지원
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 일일 보고서 요약 통계
|
||||
*/
|
||||
public function summary(array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$date = isset($params['date']) ? Carbon::parse($params['date']) : Carbon::today();
|
||||
|
||||
// 어음 합계
|
||||
$noteReceivableTotal = Bill::where('tenant_id', $tenantId)
|
||||
->where('bill_type', 'received')
|
||||
->where('status', 'stored')
|
||||
->where('maturity_date', '>=', $date->copy()->startOfDay())
|
||||
->sum('amount');
|
||||
|
||||
// 계좌별 현황
|
||||
$dailyAccounts = $this->dailyAccounts($params);
|
||||
|
||||
// 통화별 합계
|
||||
$krwTotal = collect($dailyAccounts)
|
||||
->where('currency', 'KRW')
|
||||
->reduce(function ($carry, $item) {
|
||||
return [
|
||||
'carryover' => $carry['carryover'] + $item['carryover'],
|
||||
'income' => $carry['income'] + $item['income'],
|
||||
'expense' => $carry['expense'] + $item['expense'],
|
||||
'balance' => $carry['balance'] + $item['balance'],
|
||||
];
|
||||
}, ['carryover' => 0, 'income' => 0, 'expense' => 0, 'balance' => 0]);
|
||||
|
||||
$usdTotal = collect($dailyAccounts)
|
||||
->where('currency', 'USD')
|
||||
->reduce(function ($carry, $item) {
|
||||
return [
|
||||
'carryover' => $carry['carryover'] + $item['carryover'],
|
||||
'income' => $carry['income'] + $item['income'],
|
||||
'expense' => $carry['expense'] + $item['expense'],
|
||||
'balance' => $carry['balance'] + $item['balance'],
|
||||
];
|
||||
}, ['carryover' => 0, 'income' => 0, 'expense' => 0, 'balance' => 0]);
|
||||
|
||||
return [
|
||||
'date' => $date->format('Y-m-d'),
|
||||
'day_of_week' => $date->locale('ko')->dayName,
|
||||
'note_receivable_total' => (float) $noteReceivableTotal,
|
||||
'foreign_currency_total' => (float) $usdTotal['balance'],
|
||||
'cash_asset_total' => (float) $krwTotal['balance'],
|
||||
'krw_totals' => $krwTotal,
|
||||
'usd_totals' => $usdTotal,
|
||||
];
|
||||
}
|
||||
}
|
||||
183
app/Swagger/v1/DailyReportApi.php
Normal file
183
app/Swagger/v1/DailyReportApi.php
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace App\Swagger\v1;
|
||||
|
||||
/**
|
||||
* @OA\Tag(
|
||||
* name="DailyReport",
|
||||
* description="일일 보고서 API - 일일 자금 현황 조회"
|
||||
* )
|
||||
*/
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="NoteReceivableItem",
|
||||
* description="어음 및 외상매출채권 아이템",
|
||||
*
|
||||
* @OA\Property(property="id", type="string", description="ID", example="1"),
|
||||
* @OA\Property(property="content", type="string", description="내용", example="(수취어음) 거래처명 - 12312123"),
|
||||
* @OA\Property(property="current_balance", type="number", format="float", description="현재 잔액", example=1000000),
|
||||
* @OA\Property(property="issue_date", type="string", format="date", description="발행일", example="2025-12-12"),
|
||||
* @OA\Property(property="due_date", type="string", format="date", description="만기일", example="2025-12-13")
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="DailyAccountItem",
|
||||
* description="일별 계좌 현황 아이템",
|
||||
*
|
||||
* @OA\Property(property="id", type="string", description="계좌 ID", example="1"),
|
||||
* @OA\Property(property="category", type="string", description="구분 (은행명 계좌번호)", example="국민 ****3121"),
|
||||
* @OA\Property(property="match_status", type="string", enum={"matched", "unmatched"}, description="매칭 상태", example="matched"),
|
||||
* @OA\Property(property="carryover", type="number", format="float", description="전월 이월", example=1000000),
|
||||
* @OA\Property(property="income", type="number", format="float", description="수입", example=500000),
|
||||
* @OA\Property(property="expense", type="number", format="float", description="지출", example=300000),
|
||||
* @OA\Property(property="balance", type="number", format="float", description="잔액", example=1200000),
|
||||
* @OA\Property(property="currency", type="string", enum={"KRW", "USD"}, description="통화", example="KRW")
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="DailyReportSummary",
|
||||
* description="일일 보고서 요약",
|
||||
*
|
||||
* @OA\Property(property="date", type="string", format="date", description="조회 일자", example="2025-12-26"),
|
||||
* @OA\Property(property="day_of_week", type="string", description="요일", example="목요일"),
|
||||
* @OA\Property(property="note_receivable_total", type="number", format="float", description="어음 합계", example=5000000),
|
||||
* @OA\Property(property="foreign_currency_total", type="number", format="float", description="외화 합계 (USD)", example=0),
|
||||
* @OA\Property(property="cash_asset_total", type="number", format="float", description="현금성 자산 합계", example=10000000),
|
||||
* @OA\Property(
|
||||
* property="krw_totals",
|
||||
* type="object",
|
||||
* description="KRW 합계",
|
||||
* @OA\Property(property="carryover", type="number", format="float"),
|
||||
* @OA\Property(property="income", type="number", format="float"),
|
||||
* @OA\Property(property="expense", type="number", format="float"),
|
||||
* @OA\Property(property="balance", type="number", format="float")
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="usd_totals",
|
||||
* type="object",
|
||||
* description="USD 합계",
|
||||
* @OA\Property(property="carryover", type="number", format="float"),
|
||||
* @OA\Property(property="income", type="number", format="float"),
|
||||
* @OA\Property(property="expense", type="number", format="float"),
|
||||
* @OA\Property(property="balance", type="number", format="float")
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
class DailyReportApi
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/daily-report/note-receivables",
|
||||
* operationId="getDailyReportNoteReceivables",
|
||||
* tags={"DailyReport"},
|
||||
* summary="어음 및 외상매출채권 현황 조회",
|
||||
* description="수취어음 현황을 조회합니다.",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
*
|
||||
* @OA\Parameter(
|
||||
* name="date",
|
||||
* in="query",
|
||||
* description="조회 일자 (기본: 오늘)",
|
||||
*
|
||||
* @OA\Schema(type="string", format="date", example="2025-12-26")
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="데이터를 조회했습니다."),
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* type="array",
|
||||
*
|
||||
* @OA\Items(ref="#/components/schemas/NoteReceivableItem")
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(response=401, description="인증 실패"),
|
||||
* @OA\Response(response=403, description="권한 없음")
|
||||
* )
|
||||
*/
|
||||
public function noteReceivables() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/daily-report/daily-accounts",
|
||||
* operationId="getDailyReportDailyAccounts",
|
||||
* tags={"DailyReport"},
|
||||
* summary="일별 계좌 현황 조회",
|
||||
* description="일별 계좌별 수입/지출 현황을 조회합니다.",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
*
|
||||
* @OA\Parameter(
|
||||
* name="date",
|
||||
* in="query",
|
||||
* description="조회 일자 (기본: 오늘)",
|
||||
*
|
||||
* @OA\Schema(type="string", format="date", example="2025-12-26")
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="데이터를 조회했습니다."),
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* type="array",
|
||||
*
|
||||
* @OA\Items(ref="#/components/schemas/DailyAccountItem")
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(response=401, description="인증 실패"),
|
||||
* @OA\Response(response=403, description="권한 없음")
|
||||
* )
|
||||
*/
|
||||
public function dailyAccounts() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/daily-report/summary",
|
||||
* operationId="getDailyReportSummary",
|
||||
* tags={"DailyReport"},
|
||||
* summary="일일 보고서 요약",
|
||||
* description="일일 자금 현황 요약 통계를 조회합니다.",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
*
|
||||
* @OA\Parameter(
|
||||
* name="date",
|
||||
* in="query",
|
||||
* description="조회 일자 (기본: 오늘)",
|
||||
*
|
||||
* @OA\Schema(type="string", format="date", example="2025-12-26")
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="성공",
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean", example=true),
|
||||
* @OA\Property(property="message", type="string", example="데이터를 조회했습니다."),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/DailyReportSummary")
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(response=401, description="인증 실패"),
|
||||
* @OA\Response(response=403, description="권한 없음")
|
||||
* )
|
||||
*/
|
||||
public function summary() {}
|
||||
}
|
||||
Reference in New Issue
Block a user