Files
sam-api/app/Services/BadDebtService.php
hskwon c0af888bed feat: Phase 6.1 악성채권 추심관리 API 구현
- 테이블 3개: bad_debts, bad_debt_documents, bad_debt_memos
- 모델 3개: BadDebt, BadDebtDocument, BadDebtMemo
- BadDebtService: CRUD, 요약 통계, 서류/메모 관리
- API 엔드포인트 11개 (목록, 등록, 상세, 수정, 삭제, 토글, 서류/메모 CRUD)
- Swagger 문서 작성 완료
2025-12-19 15:57:04 +09:00

308 lines
9.0 KiB
PHP

<?php
namespace App\Services;
use App\Models\BadDebts\BadDebt;
use App\Models\BadDebts\BadDebtDocument;
use App\Models\BadDebts\BadDebtMemo;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
class BadDebtService extends Service
{
/**
* 악성채권 목록 조회
*/
public function index(array $params): LengthAwarePaginator
{
$tenantId = $this->tenantId();
$query = BadDebt::query()
->where('tenant_id', $tenantId)
->with(['client:id,name,client_code', 'assignedUser:id,name']);
// 거래처 필터
if (! empty($params['client_id'])) {
$query->where('client_id', $params['client_id']);
}
// 상태 필터
if (! empty($params['status'])) {
$query->where('status', $params['status']);
}
// 활성화 필터
if (isset($params['is_active'])) {
$query->where('is_active', $params['is_active']);
}
// 검색어 필터
if (! empty($params['search'])) {
$search = $params['search'];
$query->where(function ($q) use ($search) {
$q->whereHas('client', function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('client_code', 'like', "%{$search}%");
});
});
}
// 정렬
$sortBy = $params['sort_by'] ?? 'created_at';
$sortDir = $params['sort_dir'] ?? 'desc';
$query->orderBy($sortBy, $sortDir);
// 페이지네이션
$perPage = $params['per_page'] ?? 20;
return $query->paginate($perPage);
}
/**
* 악성채권 요약 통계
*/
public function summary(array $params = []): array
{
$tenantId = $this->tenantId();
$query = BadDebt::query()
->where('tenant_id', $tenantId);
// 거래처 필터
if (! empty($params['client_id'])) {
$query->where('client_id', $params['client_id']);
}
// 전체 합계
$totalAmount = (clone $query)->sum('debt_amount');
// 상태별 합계
$collectingAmount = (clone $query)->collecting()->sum('debt_amount');
$legalActionAmount = (clone $query)->legalAction()->sum('debt_amount');
$recoveredAmount = (clone $query)->recovered()->sum('debt_amount');
$badDebtAmount = (clone $query)->badDebt()->sum('debt_amount');
return [
'total_amount' => (float) $totalAmount,
'collecting_amount' => (float) $collectingAmount,
'legal_action_amount' => (float) $legalActionAmount,
'recovered_amount' => (float) $recoveredAmount,
'bad_debt_amount' => (float) $badDebtAmount,
];
}
/**
* 악성채권 상세 조회
*/
public function show(int $id): BadDebt
{
$tenantId = $this->tenantId();
return BadDebt::query()
->where('tenant_id', $tenantId)
->with([
'client',
'assignedUser:id,name',
'creator:id,name',
'documents.file',
'memos.creator:id,name',
])
->findOrFail($id);
}
/**
* 악성채권 등록
*/
public function store(array $data): BadDebt
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
return DB::transaction(function () use ($data, $tenantId, $userId) {
$badDebt = new BadDebt;
$badDebt->tenant_id = $tenantId;
$badDebt->client_id = $data['client_id'];
$badDebt->debt_amount = $data['debt_amount'];
$badDebt->status = $data['status'] ?? BadDebt::STATUS_COLLECTING;
$badDebt->overdue_days = $data['overdue_days'] ?? 0;
$badDebt->assigned_user_id = $data['assigned_user_id'] ?? null;
$badDebt->occurred_at = $data['occurred_at'] ?? null;
$badDebt->closed_at = $data['closed_at'] ?? null;
$badDebt->is_active = $data['is_active'] ?? true;
$badDebt->options = $data['options'] ?? null;
$badDebt->created_by = $userId;
$badDebt->updated_by = $userId;
$badDebt->save();
return $badDebt->load(['client:id,name,client_code', 'assignedUser:id,name']);
});
}
/**
* 악성채권 수정
*/
public function update(int $id, array $data): BadDebt
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
return DB::transaction(function () use ($id, $data, $tenantId, $userId) {
$badDebt = BadDebt::query()
->where('tenant_id', $tenantId)
->findOrFail($id);
if (isset($data['client_id'])) {
$badDebt->client_id = $data['client_id'];
}
if (isset($data['debt_amount'])) {
$badDebt->debt_amount = $data['debt_amount'];
}
if (isset($data['status'])) {
$badDebt->status = $data['status'];
}
if (isset($data['overdue_days'])) {
$badDebt->overdue_days = $data['overdue_days'];
}
if (array_key_exists('assigned_user_id', $data)) {
$badDebt->assigned_user_id = $data['assigned_user_id'];
}
if (array_key_exists('occurred_at', $data)) {
$badDebt->occurred_at = $data['occurred_at'];
}
if (array_key_exists('closed_at', $data)) {
$badDebt->closed_at = $data['closed_at'];
}
if (isset($data['is_active'])) {
$badDebt->is_active = $data['is_active'];
}
if (array_key_exists('options', $data)) {
$badDebt->options = $data['options'];
}
$badDebt->updated_by = $userId;
$badDebt->save();
return $badDebt->fresh(['client:id,name,client_code', 'assignedUser:id,name']);
});
}
/**
* 악성채권 삭제
*/
public function destroy(int $id): bool
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
return DB::transaction(function () use ($id, $tenantId, $userId) {
$badDebt = BadDebt::query()
->where('tenant_id', $tenantId)
->findOrFail($id);
$badDebt->deleted_by = $userId;
$badDebt->save();
$badDebt->delete();
return true;
});
}
/**
* 설정 토글 (is_active)
*/
public function toggle(int $id): BadDebt
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
return DB::transaction(function () use ($id, $tenantId, $userId) {
$badDebt = BadDebt::query()
->where('tenant_id', $tenantId)
->findOrFail($id);
$badDebt->is_active = ! $badDebt->is_active;
$badDebt->updated_by = $userId;
$badDebt->save();
return $badDebt->fresh(['client:id,name,client_code', 'assignedUser:id,name']);
});
}
/**
* 서류 첨부
*/
public function addDocument(int $id, array $data): BadDebtDocument
{
$tenantId = $this->tenantId();
$badDebt = BadDebt::query()
->where('tenant_id', $tenantId)
->findOrFail($id);
$document = new BadDebtDocument;
$document->bad_debt_id = $badDebt->id;
$document->document_type = $data['document_type'];
$document->file_id = $data['file_id'];
$document->save();
return $document->load('file');
}
/**
* 서류 삭제
*/
public function removeDocument(int $id, int $documentId): bool
{
$tenantId = $this->tenantId();
$badDebt = BadDebt::query()
->where('tenant_id', $tenantId)
->findOrFail($id);
$document = BadDebtDocument::query()
->where('bad_debt_id', $badDebt->id)
->findOrFail($documentId);
return $document->delete();
}
/**
* 메모 추가
*/
public function addMemo(int $id, array $data): BadDebtMemo
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
$badDebt = BadDebt::query()
->where('tenant_id', $tenantId)
->findOrFail($id);
$memo = new BadDebtMemo;
$memo->bad_debt_id = $badDebt->id;
$memo->content = $data['content'];
$memo->created_by = $userId;
$memo->save();
return $memo->load('creator:id,name');
}
/**
* 메모 삭제
*/
public function removeMemo(int $id, int $memoId): bool
{
$tenantId = $this->tenantId();
$badDebt = BadDebt::query()
->where('tenant_id', $tenantId)
->findOrFail($id);
$memo = BadDebtMemo::query()
->where('bad_debt_id', $badDebt->id)
->findOrFail($memoId);
return $memo->delete();
}
}