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,138 @@
<?php
namespace App\Models\Tenants;
use App\Traits\Auditable;
use App\Traits\BelongsToTenant;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class CondolenceExpense extends Model
{
use Auditable, BelongsToTenant, ModelTrait, SoftDeletes;
protected $table = 'condolence_expenses';
// 카테고리 상수
public const CATEGORY_CONGRATULATION = 'congratulation';
public const CATEGORY_CONDOLENCE = 'condolence';
public const CATEGORY_LABELS = [
self::CATEGORY_CONGRATULATION => '축의',
self::CATEGORY_CONDOLENCE => '부조',
];
// 지출방법 상수
public const CASH_METHOD_CASH = 'cash';
public const CASH_METHOD_TRANSFER = 'transfer';
public const CASH_METHOD_CARD = 'card';
public const CASH_METHOD_LABELS = [
self::CASH_METHOD_CASH => '현금',
self::CASH_METHOD_TRANSFER => '계좌이체',
self::CASH_METHOD_CARD => '카드',
];
protected $fillable = [
'tenant_id',
'event_date',
'expense_date',
'partner_name',
'description',
'category',
'has_cash',
'cash_method',
'cash_amount',
'has_gift',
'gift_type',
'gift_amount',
'total_amount',
'options',
'memo',
'created_by',
'updated_by',
'deleted_by',
];
protected $casts = [
'event_date' => 'date',
'expense_date' => 'date',
'has_cash' => 'boolean',
'has_gift' => 'boolean',
'cash_amount' => 'integer',
'gift_amount' => 'integer',
'total_amount' => 'integer',
'options' => 'array',
];
protected $appends = [
'category_label',
'cash_method_label',
];
/**
* 카테고리 라벨
*/
public function getCategoryLabelAttribute(): string
{
return self::CATEGORY_LABELS[$this->category] ?? $this->category;
}
/**
* 지출방법 라벨
*/
public function getCashMethodLabelAttribute(): ?string
{
if (! $this->cash_method) {
return null;
}
return self::CASH_METHOD_LABELS[$this->cash_method] ?? $this->cash_method;
}
/**
* 등록자
*/
public function creator(): BelongsTo
{
return $this->belongsTo(\App\Models\User::class, 'created_by');
}
/**
* 카테고리 필터 스코프
*/
public function scopeByCategory($query, string $category)
{
return $query->where('category', $category);
}
/**
* 연도 필터 스코프
*/
public function scopeInYear($query, int $year)
{
return $query->whereYear('event_date', $year);
}
/**
* options 헬퍼
*/
public function getOption(string $key, $default = null)
{
return data_get($this->options, $key, $default);
}
public function setOption(string $key, $value): self
{
$options = $this->options ?? [];
data_set($options, $key, $value);
$this->options = $options;
return $this;
}
}