feat: 보고서(Reports) API 구현

- 일일 일보 조회/엑셀 다운로드 API 추가
- 지출 예상 내역서 조회/엑셀 다운로드 API 추가
- ReportService: 전일/당일 잔액 계산, 월별 지출 예상 집계
- Laravel Excel을 이용한 엑셀 내보내기 구현
- Swagger 문서 작성 완료
This commit is contained in:
2025-12-17 22:51:17 +09:00
parent cbed92a95c
commit 77914da7b7
9 changed files with 815 additions and 2 deletions

View File

@@ -0,0 +1,95 @@
<?php
namespace App\Exports;
use Maatwebsite\Excel\Concerns\FromArray;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\WithTitle;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class DailyReportExport implements FromArray, ShouldAutoSize, WithHeadings, WithStyles, WithTitle
{
public function __construct(
private readonly array $report
) {}
/**
* 시트 제목
*/
public function title(): string
{
return '일일일보';
}
/**
* 헤더 정의
*/
public function headings(): array
{
return [
['일일 일보 - '.$this->report['date']],
[],
['전일 잔액', number_format($this->report['previous_balance']).'원'],
['당일 입금액', number_format($this->report['daily_deposit']).'원'],
['당일 출금액', number_format($this->report['daily_withdrawal']).'원'],
['당일 잔액', number_format($this->report['current_balance']).'원'],
[],
['구분', '거래처명', '계정과목', '입금액', '출금액', '적요'],
];
}
/**
* 데이터 배열
*/
public function array(): array
{
$rows = [];
foreach ($this->report['details'] as $detail) {
$rows[] = [
$detail['type_label'],
$detail['client_name'],
$detail['account_code'],
$detail['deposit_amount'] > 0 ? number_format($detail['deposit_amount']) : '',
$detail['withdrawal_amount'] > 0 ? number_format($detail['withdrawal_amount']) : '',
$detail['description'],
];
}
// 합계 행 추가
$rows[] = [];
$rows[] = [
'합계',
'',
'',
number_format($this->report['daily_deposit']),
number_format($this->report['daily_withdrawal']),
'',
];
return $rows;
}
/**
* 스타일 정의
*/
public function styles(Worksheet $sheet): array
{
return [
1 => ['font' => ['bold' => true, 'size' => 14]],
3 => ['font' => ['bold' => true]],
4 => ['font' => ['bold' => true]],
5 => ['font' => ['bold' => true]],
6 => ['font' => ['bold' => true]],
8 => [
'font' => ['bold' => true],
'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
'startColor' => ['rgb' => 'E0E0E0'],
],
],
];
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace App\Exports;
use Maatwebsite\Excel\Concerns\FromArray;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\WithTitle;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class ExpenseEstimateExport implements FromArray, ShouldAutoSize, WithHeadings, WithStyles, WithTitle
{
public function __construct(
private readonly array $report
) {}
/**
* 시트 제목
*/
public function title(): string
{
return '지출예상내역서';
}
/**
* 헤더 정의
*/
public function headings(): array
{
return [
['지출 예상 내역서 - '.$this->report['year_month']],
[],
['예상 지출 합계', number_format($this->report['total_estimate']).'원'],
['계좌 잔액', number_format($this->report['account_balance']).'원'],
['예상 잔액', number_format($this->report['expected_balance']).'원'],
[],
['예상 지급일', '품목', '지출금액', '거래처', '계좌'],
];
}
/**
* 데이터 배열
*/
public function array(): array
{
$rows = [];
foreach ($this->report['items'] as $item) {
$rows[] = [
$item['expected_date'],
$item['item_name'],
number_format($item['amount']),
$item['client_name'],
$item['account_name'],
];
}
// 빈 줄 추가
$rows[] = [];
// 월별 합계
$rows[] = ['[월별 합계]', '', '', '', ''];
foreach ($this->report['monthly_summary']['by_month'] as $month) {
$rows[] = [
$month['month'].' 계',
'',
number_format($month['total']),
'',
'',
];
}
// 최종 합계
$rows[] = [];
$rows[] = [
'지출 합계',
'',
number_format($this->report['monthly_summary']['total_expense']),
'',
'',
];
$rows[] = [
'계좌 잔액',
'',
number_format($this->report['monthly_summary']['account_balance']),
'',
'',
];
$rows[] = [
'최종 차액',
'',
number_format($this->report['monthly_summary']['final_difference']),
'',
'',
];
return $rows;
}
/**
* 스타일 정의
*/
public function styles(Worksheet $sheet): array
{
return [
1 => ['font' => ['bold' => true, 'size' => 14]],
3 => ['font' => ['bold' => true]],
4 => ['font' => ['bold' => true]],
5 => ['font' => ['bold' => true]],
7 => [
'font' => ['bold' => true],
'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
'startColor' => ['rgb' => 'E0E0E0'],
],
],
];
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Exports\DailyReportExport;
use App\Exports\ExpenseEstimateExport;
use App\Http\Controllers\Controller;
use App\Http\Requests\V1\Report\DailyReportRequest;
use App\Http\Requests\V1\Report\ExpenseEstimateRequest;
use App\Http\Responses\ApiResponse;
use App\Services\ReportService;
use Maatwebsite\Excel\Facades\Excel;
class ReportController extends Controller
{
public function __construct(
private readonly ReportService $service
) {}
/**
* 일일 일보 조회
*/
public function daily(DailyReportRequest $request)
{
$report = $this->service->dailyReport($request->validated());
return ApiResponse::handle(__('message.fetched'), $report);
}
/**
* 일일 일보 엑셀 다운로드
*/
public function dailyExport(DailyReportRequest $request)
{
$report = $this->service->dailyReport($request->validated());
$filename = 'daily_report_'.$report['date'].'.xlsx';
return Excel::download(new DailyReportExport($report), $filename);
}
/**
* 지출 예상 내역서 조회
*/
public function expenseEstimate(ExpenseEstimateRequest $request)
{
$report = $this->service->expenseEstimate($request->validated());
return ApiResponse::handle(__('message.fetched'), $report);
}
/**
* 지출 예상 내역서 엑셀 다운로드
*/
public function expenseEstimateExport(ExpenseEstimateRequest $request)
{
$report = $this->service->expenseEstimate($request->validated());
$filename = 'expense_estimate_'.$report['year_month'].'.xlsx';
return Excel::download(new ExpenseEstimateExport($report), $filename);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Http\Requests\V1\Report;
use Illuminate\Foundation\Http\FormRequest;
class DailyReportRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'date' => ['nullable', 'date', 'date_format:Y-m-d'],
];
}
/**
* Custom attribute names
*/
public function attributes(): array
{
return [
'date' => __('validation.attributes.date'),
];
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Http\Requests\V1\Report;
use Illuminate\Foundation\Http\FormRequest;
class ExpenseEstimateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'year_month' => ['nullable', 'regex:/^\d{4}-\d{2}$/'],
];
}
/**
* Custom attribute names
*/
public function attributes(): array
{
return [
'year_month' => __('validation.attributes.year_month'),
];
}
/**
* Custom validation messages
*/
public function messages(): array
{
return [
'year_month.regex' => __('validation.date_format', ['format' => 'YYYY-MM']),
];
}
}

View File

@@ -0,0 +1,222 @@
<?php
namespace App\Services;
use App\Models\Tenants\Deposit;
use App\Models\Tenants\Purchase;
use App\Models\Tenants\Withdrawal;
use Carbon\Carbon;
class ReportService extends Service
{
/**
* 일일 일보 조회
*
* @param array $params [date: 기준일]
*/
public function dailyReport(array $params): array
{
$tenantId = $this->tenantId();
$date = Carbon::parse($params['date'] ?? now()->toDateString());
$previousDate = $date->copy()->subDay();
// 전일 잔액 계산 (기준일 전일까지의 모든 입출금 합계)
$previousBalance = $this->calculateBalanceUntilDate($tenantId, $previousDate);
// 당일 입금 합계
$dailyDeposit = Deposit::query()
->where('tenant_id', $tenantId)
->whereDate('deposit_date', $date)
->sum('amount');
// 당일 출금 합계
$dailyWithdrawal = Withdrawal::query()
->where('tenant_id', $tenantId)
->whereDate('withdrawal_date', $date)
->sum('amount');
// 당일 잔액
$currentBalance = $previousBalance + $dailyDeposit - $dailyWithdrawal;
// 상세 내역 (입출금 통합)
$details = $this->getDailyDetails($tenantId, $date);
return [
'date' => $date->toDateString(),
'previous_balance' => (float) $previousBalance,
'daily_deposit' => (float) $dailyDeposit,
'daily_withdrawal' => (float) $dailyWithdrawal,
'current_balance' => (float) $currentBalance,
'details' => $details,
];
}
/**
* 지출 예상 내역서 조회
*
* @param array $params [year_month: YYYY-MM 형식]
*/
public function expenseEstimate(array $params): array
{
$tenantId = $this->tenantId();
$yearMonth = $params['year_month'] ?? now()->format('Y-m');
[$year, $month] = explode('-', $yearMonth);
$startDate = Carbon::createFromDate($year, $month, 1)->startOfMonth();
$endDate = $startDate->copy()->endOfMonth();
// 미결제 매입 내역 조회 (지출 예상)
$items = Purchase::query()
->where('tenant_id', $tenantId)
->whereIn('status', ['draft', 'confirmed'])
->whereNull('withdrawal_id')
->whereBetween('purchase_date', [$startDate, $endDate])
->with(['client:id,name'])
->orderBy('purchase_date')
->get()
->map(function ($purchase) {
return [
'id' => $purchase->id,
'expected_date' => $purchase->purchase_date->toDateString(),
'item_name' => $purchase->description ?? __('message.report.purchase'),
'amount' => (float) $purchase->total_amount,
'client_name' => $purchase->client?->name ?? '',
'account_name' => '',
];
});
// 예상 지출 합계
$totalEstimate = $items->sum('amount');
// 계좌 잔액 (대표 계좌 기준, 없으면 모든 계좌 합산)
$accountBalance = $this->getAccountBalance($tenantId);
// 예상 잔액
$expectedBalance = $accountBalance - $totalEstimate;
// 월별 합계
$monthlySummary = $this->getMonthlySummary($tenantId, $startDate);
return [
'year_month' => $yearMonth,
'total_estimate' => (float) $totalEstimate,
'account_balance' => (float) $accountBalance,
'expected_balance' => (float) $expectedBalance,
'items' => $items->values()->toArray(),
'monthly_summary' => $monthlySummary,
];
}
/**
* 특정 날짜까지의 잔액 계산
*/
private function calculateBalanceUntilDate(int $tenantId, Carbon $date): float
{
$totalDeposits = Deposit::query()
->where('tenant_id', $tenantId)
->whereDate('deposit_date', '<=', $date)
->sum('amount');
$totalWithdrawals = Withdrawal::query()
->where('tenant_id', $tenantId)
->whereDate('withdrawal_date', '<=', $date)
->sum('amount');
return (float) ($totalDeposits - $totalWithdrawals);
}
/**
* 일일 상세 내역 조회 (입출금 통합)
*/
private function getDailyDetails(int $tenantId, Carbon $date): array
{
// 입금 내역
$deposits = Deposit::query()
->where('tenant_id', $tenantId)
->whereDate('deposit_date', $date)
->with(['client:id,name'])
->get()
->map(function ($deposit) {
return [
'type' => 'deposit',
'type_label' => __('message.report.deposit'),
'client_name' => $deposit->client?->name ?? $deposit->client_name ?? '',
'account_code' => $deposit->account_code ?? '',
'deposit_amount' => (float) $deposit->amount,
'withdrawal_amount' => 0,
'description' => $deposit->description ?? '',
'payment_method' => $deposit->payment_method,
];
});
// 출금 내역
$withdrawals = Withdrawal::query()
->where('tenant_id', $tenantId)
->whereDate('withdrawal_date', $date)
->with(['client:id,name'])
->get()
->map(function ($withdrawal) {
return [
'type' => 'withdrawal',
'type_label' => __('message.report.withdrawal'),
'client_name' => $withdrawal->client?->name ?? $withdrawal->client_name ?? '',
'account_code' => $withdrawal->account_code ?? '',
'deposit_amount' => 0,
'withdrawal_amount' => (float) $withdrawal->amount,
'description' => $withdrawal->description ?? '',
'payment_method' => $withdrawal->payment_method,
];
});
return $deposits->concat($withdrawals)->values()->toArray();
}
/**
* 계좌 잔액 조회
* 입출금 내역 기반으로 계산
*/
private function getAccountBalance(int $tenantId): float
{
return $this->calculateBalanceUntilDate($tenantId, Carbon::now());
}
/**
* 월별 지출 예상 합계
*/
private function getMonthlySummary(int $tenantId, Carbon $baseDate): array
{
$summary = [];
// 기준월부터 3개월간 집계
for ($i = 0; $i < 3; $i++) {
$targetDate = $baseDate->copy()->addMonths($i);
$startOfMonth = $targetDate->copy()->startOfMonth();
$endOfMonth = $targetDate->copy()->endOfMonth();
$total = Purchase::query()
->where('tenant_id', $tenantId)
->whereIn('status', ['draft', 'confirmed'])
->whereNull('withdrawal_id')
->whereBetween('purchase_date', [$startOfMonth, $endOfMonth])
->sum('total_amount');
$summary[] = [
'month' => $targetDate->format('Y/m'),
'total' => (float) $total,
];
}
// 전체 합계
$grandTotal = collect($summary)->sum('total');
// 계좌 잔액
$accountBalance = $this->getAccountBalance($tenantId);
return [
'by_month' => $summary,
'total_expense' => $grandTotal,
'account_balance' => $accountBalance,
'final_difference' => $accountBalance - $grandTotal,
];
}
}

View File

@@ -0,0 +1,212 @@
<?php
namespace App\Swagger\v1;
/**
* @OA\Tag(name="Reports", description="보고서 관리")
*
* @OA\Schema(
* schema="DailyReport",
* type="object",
* description="일일 일보",
*
* @OA\Property(property="date", type="string", format="date", example="2025-01-15", description="기준일"),
* @OA\Property(property="previous_balance", type="number", format="float", example=10000000, description="전일 잔액"),
* @OA\Property(property="daily_deposit", type="number", format="float", example=5000000, description="당일 입금액"),
* @OA\Property(property="daily_withdrawal", type="number", format="float", example=3000000, description="당일 출금액"),
* @OA\Property(property="current_balance", type="number", format="float", example=12000000, description="당일 잔액"),
* @OA\Property(property="details", type="array", description="상세 내역",
*
* @OA\Items(
* type="object",
*
* @OA\Property(property="type", type="string", enum={"deposit","withdrawal"}, description="유형"),
* @OA\Property(property="type_label", type="string", example="입금", description="유형 라벨"),
* @OA\Property(property="client_name", type="string", example="(주)테스트", description="거래처명"),
* @OA\Property(property="account_code", type="string", example="401", description="계정과목"),
* @OA\Property(property="deposit_amount", type="number", format="float", example=1000000, description="입금액"),
* @OA\Property(property="withdrawal_amount", type="number", format="float", example=0, description="출금액"),
* @OA\Property(property="description", type="string", example="1월 매출 입금", description="적요"),
* @OA\Property(property="payment_method", type="string", example="transfer", description="결제수단")
* )
* )
* )
*
* @OA\Schema(
* schema="ExpenseEstimate",
* type="object",
* description="지출 예상 내역서",
*
* @OA\Property(property="year_month", type="string", example="2025-01", description="기준 연월"),
* @OA\Property(property="total_estimate", type="number", format="float", example=15000000, description="예상 지출 합계"),
* @OA\Property(property="account_balance", type="number", format="float", example=20000000, description="계좌 잔액"),
* @OA\Property(property="expected_balance", type="number", format="float", example=5000000, description="예상 잔액"),
* @OA\Property(property="items", type="array", description="지출 예상 내역",
*
* @OA\Items(
* type="object",
*
* @OA\Property(property="id", type="integer", example=1, description="매입 ID"),
* @OA\Property(property="expected_date", type="string", format="date", example="2025-01-20", description="예상 지급일"),
* @OA\Property(property="item_name", type="string", example="원자재 구매", description="품목"),
* @OA\Property(property="amount", type="number", format="float", example=5000000, description="지출금액"),
* @OA\Property(property="client_name", type="string", example="(주)공급사", description="거래처"),
* @OA\Property(property="account_name", type="string", example="법인통장", description="계좌")
* )
* ),
* @OA\Property(property="monthly_summary", type="object", description="월별 합계",
* @OA\Property(property="by_month", type="array",
*
* @OA\Items(
* type="object",
*
* @OA\Property(property="month", type="string", example="2025/01", description="월"),
* @OA\Property(property="total", type="number", format="float", example=5000000, description="합계")
* )
* ),
* @OA\Property(property="total_expense", type="number", format="float", example=15000000, description="지출 합계"),
* @OA\Property(property="account_balance", type="number", format="float", example=20000000, description="계좌 잔액"),
* @OA\Property(property="final_difference", type="number", format="float", example=5000000, description="최종 차액")
* )
* )
*/
class ReportApi
{
/**
* @OA\Get(
* path="/api/v1/reports/daily",
* tags={"Reports"},
* summary="일일 일보 조회",
* description="매일 전일의 입출금 및 매출 매입 현황을 자동 집계합니다.",
* security={{"BearerAuth": {}}, {"ApiKeyAuth": {}}},
*
* @OA\Parameter(
* name="date",
* in="query",
* description="기준일 (YYYY-MM-DD, 기본값: 오늘)",
* required=false,
*
* @OA\Schema(type="string", format="date", example="2025-01-15")
* ),
*
* @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/DailyReport")
* )
* ),
*
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=422, description="유효성 검사 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function daily() {}
/**
* @OA\Get(
* path="/api/v1/reports/daily/export",
* tags={"Reports"},
* summary="일일 일보 엑셀 다운로드",
* description="일일 일보를 엑셀 파일로 다운로드합니다.",
* security={{"BearerAuth": {}}, {"ApiKeyAuth": {}}},
*
* @OA\Parameter(
* name="date",
* in="query",
* description="기준일 (YYYY-MM-DD, 기본값: 오늘)",
* required=false,
*
* @OA\Schema(type="string", format="date", example="2025-01-15")
* ),
*
* @OA\Response(
* response=200,
* description="엑셀 파일",
*
* @OA\MediaType(
* mediaType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
*
* @OA\Schema(type="string", format="binary")
* )
* ),
*
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=422, description="유효성 검사 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function dailyExport() {}
/**
* @OA\Get(
* path="/api/v1/reports/expense-estimate",
* tags={"Reports"},
* summary="지출 예상 내역서 조회",
* description="예상 지출 금액 및 일정을 조회합니다.",
* security={{"BearerAuth": {}}, {"ApiKeyAuth": {}}},
*
* @OA\Parameter(
* name="year_month",
* in="query",
* description="기준 연월 (YYYY-MM, 기본값: 이번달)",
* required=false,
*
* @OA\Schema(type="string", example="2025-01")
* ),
*
* @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/ExpenseEstimate")
* )
* ),
*
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=422, description="유효성 검사 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function expenseEstimate() {}
/**
* @OA\Get(
* path="/api/v1/reports/expense-estimate/export",
* tags={"Reports"},
* summary="지출 예상 내역서 엑셀 다운로드",
* description="지출 예상 내역서를 엑셀 파일로 다운로드합니다.",
* security={{"BearerAuth": {}}, {"ApiKeyAuth": {}}},
*
* @OA\Parameter(
* name="year_month",
* in="query",
* description="기준 연월 (YYYY-MM, 기본값: 이번달)",
* required=false,
*
* @OA\Schema(type="string", example="2025-01")
* ),
*
* @OA\Response(
* response=200,
* description="엑셀 파일",
*
* @OA\MediaType(
* mediaType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
*
* @OA\Schema(type="string", format="binary")
* )
* ),
*
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=422, description="유효성 검사 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function expenseEstimateExport() {}
}

View File

@@ -238,4 +238,15 @@
'deleted' => '현장이 삭제되었습니다.', 'deleted' => '현장이 삭제되었습니다.',
'active_fetched' => '활성화된 현장 목록을 조회했습니다.', 'active_fetched' => '활성화된 현장 목록을 조회했습니다.',
], ],
// 보고서 관리
'report' => [
'fetched' => '보고서를 조회했습니다.',
'daily_fetched' => '일일 일보를 조회했습니다.',
'expense_estimate_fetched' => '지출 예상 내역서를 조회했습니다.',
'exported' => '보고서가 다운로드되었습니다.',
'deposit' => '입금',
'withdrawal' => '출금',
'purchase' => '매입',
],
]; ];

View File

@@ -51,20 +51,21 @@
use App\Http\Controllers\Api\V1\QuoteController; use App\Http\Controllers\Api\V1\QuoteController;
use App\Http\Controllers\Api\V1\RefreshController; use App\Http\Controllers\Api\V1\RefreshController;
use App\Http\Controllers\Api\V1\RegisterController; use App\Http\Controllers\Api\V1\RegisterController;
use App\Http\Controllers\Api\V1\ReportController;
use App\Http\Controllers\Api\V1\RoleController; use App\Http\Controllers\Api\V1\RoleController;
use App\Http\Controllers\Api\V1\RolePermissionController; use App\Http\Controllers\Api\V1\RolePermissionController;
use App\Http\Controllers\Api\V1\SaleController; use App\Http\Controllers\Api\V1\SaleController;
use App\Http\Controllers\Api\V1\SiteController; use App\Http\Controllers\Api\V1\SiteController;
use App\Http\Controllers\Api\V1\TenantController; use App\Http\Controllers\Api\V1\TenantController;
use App\Http\Controllers\Api\V1\TenantFieldSettingController; use App\Http\Controllers\Api\V1\TenantFieldSettingController;
use App\Http\Controllers\Api\V1\TenantOptionGroupController;
// 설계 전용 (디자인 네임스페이스) // 설계 전용 (디자인 네임스페이스)
use App\Http\Controllers\Api\V1\TenantOptionGroupController;
use App\Http\Controllers\Api\V1\TenantOptionValueController; use App\Http\Controllers\Api\V1\TenantOptionValueController;
use App\Http\Controllers\Api\V1\TenantStatFieldController; use App\Http\Controllers\Api\V1\TenantStatFieldController;
use App\Http\Controllers\Api\V1\TenantUserProfileController; use App\Http\Controllers\Api\V1\TenantUserProfileController;
use App\Http\Controllers\Api\V1\UserController; use App\Http\Controllers\Api\V1\UserController;
use App\Http\Controllers\Api\V1\UserRoleController;
// 모델셋 관리 (견적 시스템) // 모델셋 관리 (견적 시스템)
use App\Http\Controllers\Api\V1\UserRoleController;
use App\Http\Controllers\Api\V1\WithdrawalController; use App\Http\Controllers\Api\V1\WithdrawalController;
use App\Http\Controllers\Api\V1\WorkSettingController; use App\Http\Controllers\Api\V1\WorkSettingController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@@ -340,6 +341,14 @@
Route::post('/{id}/confirm', [PurchaseController::class, 'confirm'])->whereNumber('id')->name('v1.purchases.confirm'); Route::post('/{id}/confirm', [PurchaseController::class, 'confirm'])->whereNumber('id')->name('v1.purchases.confirm');
}); });
// Report API (보고서)
Route::prefix('reports')->group(function () {
Route::get('/daily', [ReportController::class, 'daily'])->name('v1.reports.daily');
Route::get('/daily/export', [ReportController::class, 'dailyExport'])->name('v1.reports.daily.export');
Route::get('/expense-estimate', [ReportController::class, 'expenseEstimate'])->name('v1.reports.expense-estimate');
Route::get('/expense-estimate/export', [ReportController::class, 'expenseEstimateExport'])->name('v1.reports.expense-estimate.export');
});
// Permission API // Permission API
Route::prefix('permissions')->group(function () { Route::prefix('permissions')->group(function () {
Route::get('departments/{dept_id}/menu-matrix', [PermissionController::class, 'deptMenuMatrix'])->name('v1.permissions.deptMenuMatrix'); // 부서별 권한 메트릭스 Route::get('departments/{dept_id}/menu-matrix', [PermissionController::class, 'deptMenuMatrix'])->name('v1.permissions.deptMenuMatrix'); // 부서별 권한 메트릭스