feat: [finance] 경조사비 관리 API 구현

- Model: CondolenceExpense (BelongsToTenant, Auditable, SoftDeletes)
- Service: CRUD + summary 통계 (total_amount 자동 계산)
- Controller: 6개 엔드포인트 (목록/등록/상세/수정/삭제/통계)
- FormRequest: Store/Update 검증 규칙 분리
- Route: /api/v1/condolence-expenses
- Migration: updated_by, deleted_by 컬럼 추가
This commit is contained in:
김보곤
2026-03-19 13:07:20 +09:00
parent 5e5aecd992
commit abb024f0bd
7 changed files with 594 additions and 1 deletions

View File

@@ -0,0 +1,88 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Helpers\ApiResponse;
use App\Http\Controllers\Controller;
use App\Http\Requests\V1\CondolenceExpense\StoreCondolenceExpenseRequest;
use App\Http\Requests\V1\CondolenceExpense\UpdateCondolenceExpenseRequest;
use App\Services\CondolenceExpenseService;
use Illuminate\Http\Request;
class CondolenceExpenseController extends Controller
{
public function __construct(
private readonly CondolenceExpenseService $service
) {}
/**
* 경조사비 목록
*/
public function index(Request $request)
{
$params = $request->only([
'year',
'category',
'search',
'sort_by',
'sort_order',
'per_page',
'page',
]);
$expenses = $this->service->index($params);
return ApiResponse::success($expenses, __('message.fetched'));
}
/**
* 경조사비 통계
*/
public function summary(Request $request)
{
$params = $request->only(['year', 'category']);
$summary = $this->service->summary($params);
return ApiResponse::success($summary, __('message.fetched'));
}
/**
* 경조사비 상세
*/
public function show(int $id)
{
$expense = $this->service->show($id);
return ApiResponse::success($expense, __('message.fetched'));
}
/**
* 경조사비 등록
*/
public function store(StoreCondolenceExpenseRequest $request)
{
$expense = $this->service->store($request->validated());
return ApiResponse::success($expense, __('message.created'), [], 201);
}
/**
* 경조사비 수정
*/
public function update(int $id, UpdateCondolenceExpenseRequest $request)
{
$expense = $this->service->update($id, $request->validated());
return ApiResponse::success($expense, __('message.updated'));
}
/**
* 경조사비 삭제
*/
public function destroy(int $id)
{
$this->service->destroy($id);
return ApiResponse::success(null, __('message.deleted'));
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Http\Requests\V1\CondolenceExpense;
use Illuminate\Foundation\Http\FormRequest;
class StoreCondolenceExpenseRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'event_date' => ['nullable', 'date'],
'expense_date' => ['nullable', 'date'],
'partner_name' => ['required', 'string', 'max:100'],
'description' => ['nullable', 'string', 'max:200'],
'category' => ['required', 'string', 'in:congratulation,condolence'],
'has_cash' => ['nullable', 'boolean'],
'cash_method' => ['required_if:has_cash,true', 'nullable', 'string', 'in:cash,transfer,card'],
'cash_amount' => ['required_if:has_cash,true', 'nullable', 'integer', 'min:0'],
'has_gift' => ['nullable', 'boolean'],
'gift_type' => ['nullable', 'string', 'max:50'],
'gift_amount' => ['required_if:has_gift,true', 'nullable', 'integer', 'min:0'],
'memo' => ['nullable', 'string'],
];
}
public function attributes(): array
{
return [
'event_date' => '경조사일자',
'expense_date' => '지출일자',
'partner_name' => '거래처명',
'description' => '내역',
'category' => '구분',
'has_cash' => '부조금 여부',
'cash_method' => '지출방법',
'cash_amount' => '부조금액',
'has_gift' => '선물 여부',
'gift_type' => '선물종류',
'gift_amount' => '선물금액',
'memo' => '비고',
];
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Http\Requests\V1\CondolenceExpense;
use Illuminate\Foundation\Http\FormRequest;
class UpdateCondolenceExpenseRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'event_date' => ['nullable', 'date'],
'expense_date' => ['nullable', 'date'],
'partner_name' => ['sometimes', 'required', 'string', 'max:100'],
'description' => ['nullable', 'string', 'max:200'],
'category' => ['sometimes', 'required', 'string', 'in:congratulation,condolence'],
'has_cash' => ['nullable', 'boolean'],
'cash_method' => ['required_if:has_cash,true', 'nullable', 'string', 'in:cash,transfer,card'],
'cash_amount' => ['required_if:has_cash,true', 'nullable', 'integer', 'min:0'],
'has_gift' => ['nullable', 'boolean'],
'gift_type' => ['nullable', 'string', 'max:50'],
'gift_amount' => ['required_if:has_gift,true', 'nullable', 'integer', 'min:0'],
'memo' => ['nullable', 'string'],
];
}
public function attributes(): array
{
return [
'event_date' => '경조사일자',
'expense_date' => '지출일자',
'partner_name' => '거래처명',
'description' => '내역',
'category' => '구분',
'has_cash' => '부조금 여부',
'cash_method' => '지출방법',
'cash_amount' => '부조금액',
'has_gift' => '선물 여부',
'gift_type' => '선물종류',
'gift_amount' => '선물금액',
'memo' => '비고',
];
}
}