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:
189
app/Services/CondolenceExpenseService.php
Normal file
189
app/Services/CondolenceExpenseService.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Tenants\CondolenceExpense;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CondolenceExpenseService extends Service
|
||||
{
|
||||
/**
|
||||
* 목록 조회 (페이지네이션 + 필터)
|
||||
*/
|
||||
public function index(array $params): LengthAwarePaginator
|
||||
{
|
||||
$query = CondolenceExpense::query();
|
||||
|
||||
// 연도 필터
|
||||
if (! empty($params['year'])) {
|
||||
$query->inYear((int) $params['year']);
|
||||
}
|
||||
|
||||
// 카테고리 필터
|
||||
if (! empty($params['category']) && $params['category'] !== 'all') {
|
||||
$query->byCategory($params['category']);
|
||||
}
|
||||
|
||||
// 통합 검색
|
||||
if (! empty($params['search'])) {
|
||||
$search = $params['search'];
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('partner_name', 'like', "%{$search}%")
|
||||
->orWhere('description', 'like', "%{$search}%")
|
||||
->orWhere('memo', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
// 정렬
|
||||
$sortBy = $params['sort_by'] ?? 'event_date';
|
||||
$sortOrder = $params['sort_order'] ?? 'desc';
|
||||
$query->orderBy($sortBy, $sortOrder)->orderByDesc('id');
|
||||
|
||||
$perPage = $params['per_page'] ?? 50;
|
||||
|
||||
return $query->paginate($perPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 상세 조회
|
||||
*/
|
||||
public function show(int $id): CondolenceExpense
|
||||
{
|
||||
return CondolenceExpense::with('creator:id,name')->findOrFail($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 등록
|
||||
*/
|
||||
public function store(array $data): CondolenceExpense
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
return DB::transaction(function () use ($data, $tenantId, $userId) {
|
||||
return CondolenceExpense::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'event_date' => $data['event_date'] ?? null,
|
||||
'expense_date' => $data['expense_date'] ?? null,
|
||||
'partner_name' => $data['partner_name'],
|
||||
'description' => $data['description'] ?? null,
|
||||
'category' => $data['category'],
|
||||
'has_cash' => $data['has_cash'] ?? false,
|
||||
'cash_method' => ($data['has_cash'] ?? false) ? ($data['cash_method'] ?? null) : null,
|
||||
'cash_amount' => ($data['has_cash'] ?? false) ? ($data['cash_amount'] ?? 0) : 0,
|
||||
'has_gift' => $data['has_gift'] ?? false,
|
||||
'gift_type' => ($data['has_gift'] ?? false) ? ($data['gift_type'] ?? null) : null,
|
||||
'gift_amount' => ($data['has_gift'] ?? false) ? ($data['gift_amount'] ?? 0) : 0,
|
||||
'total_amount' => $this->calculateTotal($data),
|
||||
'memo' => $data['memo'] ?? null,
|
||||
'created_by' => $userId,
|
||||
'updated_by' => $userId,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 수정
|
||||
*/
|
||||
public function update(int $id, array $data): CondolenceExpense
|
||||
{
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
return DB::transaction(function () use ($id, $data, $userId) {
|
||||
$expense = CondolenceExpense::findOrFail($id);
|
||||
|
||||
$expense->fill([
|
||||
'event_date' => $data['event_date'] ?? $expense->event_date,
|
||||
'expense_date' => $data['expense_date'] ?? $expense->expense_date,
|
||||
'partner_name' => $data['partner_name'] ?? $expense->partner_name,
|
||||
'description' => $data['description'] ?? $expense->description,
|
||||
'category' => $data['category'] ?? $expense->category,
|
||||
'has_cash' => $data['has_cash'] ?? $expense->has_cash,
|
||||
'cash_method' => ($data['has_cash'] ?? $expense->has_cash) ? ($data['cash_method'] ?? $expense->cash_method) : null,
|
||||
'cash_amount' => ($data['has_cash'] ?? $expense->has_cash) ? ($data['cash_amount'] ?? $expense->cash_amount) : 0,
|
||||
'has_gift' => $data['has_gift'] ?? $expense->has_gift,
|
||||
'gift_type' => ($data['has_gift'] ?? $expense->has_gift) ? ($data['gift_type'] ?? $expense->gift_type) : null,
|
||||
'gift_amount' => ($data['has_gift'] ?? $expense->has_gift) ? ($data['gift_amount'] ?? $expense->gift_amount) : 0,
|
||||
'total_amount' => $this->calculateTotal(array_merge($expense->toArray(), $data)),
|
||||
'memo' => $data['memo'] ?? $expense->memo,
|
||||
'updated_by' => $userId,
|
||||
]);
|
||||
|
||||
$expense->save();
|
||||
|
||||
return $expense->fresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 삭제 (소프트)
|
||||
*/
|
||||
public function destroy(int $id): bool
|
||||
{
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
return DB::transaction(function () use ($id, $userId) {
|
||||
$expense = CondolenceExpense::findOrFail($id);
|
||||
$expense->deleted_by = $userId;
|
||||
$expense->save();
|
||||
$expense->delete();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 통계 조회
|
||||
*/
|
||||
public function summary(array $params): array
|
||||
{
|
||||
$query = CondolenceExpense::query();
|
||||
|
||||
if (! empty($params['year'])) {
|
||||
$query->inYear((int) $params['year']);
|
||||
}
|
||||
|
||||
if (! empty($params['category']) && $params['category'] !== 'all') {
|
||||
$query->byCategory($params['category']);
|
||||
}
|
||||
|
||||
$stats = $query->selectRaw('
|
||||
COUNT(*) as total_count,
|
||||
COALESCE(SUM(total_amount), 0) as total_amount,
|
||||
COALESCE(SUM(cash_amount), 0) as cash_total,
|
||||
COALESCE(SUM(gift_amount), 0) as gift_total,
|
||||
COALESCE(SUM(CASE WHEN category = ? THEN 1 ELSE 0 END), 0) as congratulation_count,
|
||||
COALESCE(SUM(CASE WHEN category = ? THEN 1 ELSE 0 END), 0) as condolence_count,
|
||||
COALESCE(SUM(CASE WHEN category = ? THEN total_amount ELSE 0 END), 0) as congratulation_amount,
|
||||
COALESCE(SUM(CASE WHEN category = ? THEN total_amount ELSE 0 END), 0) as condolence_amount
|
||||
', [
|
||||
CondolenceExpense::CATEGORY_CONGRATULATION,
|
||||
CondolenceExpense::CATEGORY_CONDOLENCE,
|
||||
CondolenceExpense::CATEGORY_CONGRATULATION,
|
||||
CondolenceExpense::CATEGORY_CONDOLENCE,
|
||||
])->first();
|
||||
|
||||
return [
|
||||
'total_count' => (int) $stats->total_count,
|
||||
'total_amount' => (int) $stats->total_amount,
|
||||
'cash_total' => (int) $stats->cash_total,
|
||||
'gift_total' => (int) $stats->gift_total,
|
||||
'congratulation_count' => (int) $stats->congratulation_count,
|
||||
'condolence_count' => (int) $stats->condolence_count,
|
||||
'congratulation_amount' => (int) $stats->congratulation_amount,
|
||||
'condolence_amount' => (int) $stats->condolence_amount,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 총금액 자동 계산
|
||||
*/
|
||||
private function calculateTotal(array $data): int
|
||||
{
|
||||
$cash = ($data['has_cash'] ?? false) ? (int) ($data['cash_amount'] ?? 0) : 0;
|
||||
$gift = ($data['has_gift'] ?? false) ? (int) ($data['gift_amount'] ?? 0) : 0;
|
||||
|
||||
return $cash + $gift;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user