fix: 거래처 연체/악성채권 저장 버그 수정

- ClientUpdateRequest, ClientStoreRequest에 is_overdue 필드 추가
  - FormRequest rules에 누락되어 프론트엔드 값이 필터링됨
- ClientService.update()에 bad_debt 토글 연동 로직 추가
  - bad_debt=true → BadDebt 레코드 생성 (status: collecting)
  - bad_debt=false → BadDebt 레코드 종료 (status: recovered)
- ClientService의 has_bad_debt 판단 로직 수정
  - 기존: sum(debt_amount) > 0
  - 변경: exists() - 금액과 무관하게 레코드 존재 여부로 판단

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-27 22:39:04 +09:00
parent 51b23adcfe
commit 3917ea3831
3 changed files with 67 additions and 7 deletions

View File

@@ -96,6 +96,7 @@ public function rules(): array
// 기타
'memo' => 'nullable|string',
'is_active' => 'nullable|boolean',
'is_overdue' => 'nullable|boolean',
];
}
}

View File

@@ -96,6 +96,7 @@ public function rules(): array
// 기타
'memo' => 'nullable|string',
'is_active' => 'nullable|boolean',
'is_overdue' => 'nullable|boolean',
];
}
}

View File

@@ -70,13 +70,24 @@ public function index(array $params)
->select('client_id', DB::raw('SUM(debt_amount) as total_bad_debt'))
->pluck('total_bad_debt', 'client_id');
// 활성 악성채권이 있는 거래처 ID 목록 (금액과 무관하게 레코드 존재 여부)
$clientsWithBadDebt = BadDebt::where('tenant_id', $tenantId)
->whereIn('client_id', $clientIds)
->whereIn('status', [BadDebt::STATUS_COLLECTING, BadDebt::STATUS_LEGAL_ACTION])
->where('is_active', true)
->whereNull('deleted_at')
->distinct()
->pluck('client_id')
->flip()
->toArray();
// 각 거래처에 미수금/악성채권 정보 추가
$paginator->getCollection()->transform(function ($client) use ($salesByClient, $depositsByClient, $badDebtsByClient) {
$paginator->getCollection()->transform(function ($client) use ($salesByClient, $depositsByClient, $badDebtsByClient, $clientsWithBadDebt) {
$totalSales = $salesByClient[$client->id] ?? 0;
$totalDeposits = $depositsByClient[$client->id] ?? 0;
$client->outstanding_amount = max(0, $totalSales - $totalDeposits);
$client->bad_debt_total = $badDebtsByClient[$client->id] ?? 0;
$client->has_bad_debt = ($badDebtsByClient[$client->id] ?? 0) > 0;
$client->has_bad_debt = isset($clientsWithBadDebt[$client->id]);
return $client;
});
@@ -108,15 +119,14 @@ public function show(int $id)
$client->outstanding_amount = max(0, $totalSales - $totalDeposits);
// 악성채권 정보
$badDebtTotal = BadDebt::where('tenant_id', $tenantId)
$activeBadDebtQuery = BadDebt::where('tenant_id', $tenantId)
->where('client_id', $id)
->whereIn('status', [BadDebt::STATUS_COLLECTING, BadDebt::STATUS_LEGAL_ACTION])
->where('is_active', true)
->whereNull('deleted_at')
->sum('debt_amount');
->whereNull('deleted_at');
$client->bad_debt_total = $badDebtTotal;
$client->has_bad_debt = $badDebtTotal > 0;
$client->bad_debt_total = (clone $activeBadDebtQuery)->sum('debt_amount');
$client->has_bad_debt = (clone $activeBadDebtQuery)->exists();
return $client;
}
@@ -173,6 +183,7 @@ private function generateClientCode(int $tenantId): string
public function update(int $id, array $data)
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
$client = Client::where('tenant_id', $tenantId)->find($id);
if (! $client) {
@@ -182,11 +193,58 @@ public function update(int $id, array $data)
// client_code 변경 불가 (프론트에서 보내도 무시)
unset($data['client_code']);
// bad_debt 토글 처리 (bad_debts 테이블과 연동)
$badDebtToggle = $data['bad_debt'] ?? null;
unset($data['bad_debt']); // Client 모델에는 bad_debt 컬럼이 없으므로 제거
$client->update($data);
// bad_debt 토글이 명시적으로 전달된 경우에만 처리
if ($badDebtToggle !== null) {
$this->syncBadDebtStatus($client, (bool) $badDebtToggle, $userId);
}
return $client->refresh();
}
/**
* 악성채권 상태 동기화
* 프론트엔드의 bad_debt 토글에 따라 bad_debts 테이블 연동
*/
private function syncBadDebtStatus(Client $client, bool $hasBadDebt, ?int $userId): void
{
$tenantId = $client->tenant_id;
// 현재 활성 악성채권 조회
$activeBadDebt = BadDebt::where('tenant_id', $tenantId)
->where('client_id', $client->id)
->where('is_active', true)
->whereIn('status', [BadDebt::STATUS_COLLECTING, BadDebt::STATUS_LEGAL_ACTION])
->first();
if ($hasBadDebt && ! $activeBadDebt) {
// 악성채권 활성화: 새 레코드 생성
BadDebt::create([
'tenant_id' => $tenantId,
'client_id' => $client->id,
'debt_amount' => $client->outstanding_balance ?? 0,
'status' => BadDebt::STATUS_COLLECTING,
'overdue_days' => 0,
'occurred_at' => now(),
'is_active' => true,
'created_by' => $userId,
]);
} elseif (! $hasBadDebt && $activeBadDebt) {
// 악성채권 비활성화: 기존 레코드 종료 처리
$activeBadDebt->update([
'is_active' => false,
'status' => BadDebt::STATUS_RECOVERED,
'closed_at' => now(),
'updated_by' => $userId,
]);
}
}
/** 삭제 */
public function destroy(int $id)
{