feat(API): 채권현황 동적월 지원 및 year=0 파라미터 버그 수정
- ReceivablesController: boolean 유효성 검사를 string|in:true,false,1,0으로 변경 - 쿼리 문자열의 "true" 문자열을 올바르게 처리 - 디버깅용 Log::info 추가 - ReceivablesService: 동적 월 기간 지원 - recent_year=true 시 최근 12개월 동적 계산 - 월별 레이블 동적 생성 (예: 25.02, 25.03...) - 이월잔액(carry_forward_balance) 계산 추가 - Client 모델: is_overdue, memo 필드 추가 - 마이그레이션: clients 테이블에 is_overdue 컬럼 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,11 +24,21 @@ public function index(Request $request): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$params = $request->validate([
|
||||
'year' => 'nullable|integer|min:2000|max:2100',
|
||||
'year' => 'nullable|integer|min:2000|max:2100', 'recent_year' => 'nullable|string|in:true,false,1,0',
|
||||
'search' => 'nullable|string|max:100',
|
||||
'has_receivable' => 'nullable|boolean',
|
||||
'has_receivable' => 'nullable|string|in:true,false,1,0',
|
||||
]);
|
||||
|
||||
// 문자열 boolean을 실제 boolean으로 변환
|
||||
if (isset($params['recent_year'])) {
|
||||
$params['recent_year'] = filter_var($params['recent_year'], FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
if (isset($params['has_receivable'])) {
|
||||
$params['has_receivable'] = filter_var($params['has_receivable'], FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
\Log::info('[Receivables] index params', $params);
|
||||
|
||||
return $this->service->index($params);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
@@ -41,9 +51,53 @@ public function summary(Request $request): JsonResponse
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$params = $request->validate([
|
||||
'year' => 'nullable|integer|min:2000|max:2100',
|
||||
'recent_year' => 'nullable|string|in:true,false,1,0',
|
||||
]);
|
||||
|
||||
// 문자열 boolean을 실제 boolean으로 변환
|
||||
if (isset($params['recent_year'])) {
|
||||
$params['recent_year'] = filter_var($params['recent_year'], FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
\Log::info('[Receivables] summary params', $params);
|
||||
|
||||
return $this->service->summary($params);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 연체 상태 일괄 업데이트
|
||||
*/
|
||||
public function updateOverdueStatus(Request $request): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$validated = $request->validate([
|
||||
'updates' => 'required|array|min:1',
|
||||
'updates.*.id' => 'required|integer|exists:clients,id',
|
||||
'updates.*.is_overdue' => 'required|boolean',
|
||||
]);
|
||||
|
||||
$updatedCount = $this->service->updateOverdueStatus($validated['updates']);
|
||||
|
||||
return ['updated_count' => $updatedCount];
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래처 메모 일괄 업데이트
|
||||
*/
|
||||
public function updateMemos(Request $request): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$validated = $request->validate([
|
||||
'memos' => 'required|array|min:1',
|
||||
'memos.*.id' => 'required|integer|exists:clients,id',
|
||||
'memos.*.memo' => 'nullable|string|max:1000',
|
||||
]);
|
||||
|
||||
$updatedCount = $this->service->updateMemos($validated['memos']);
|
||||
|
||||
return ['updated_count' => $updatedCount];
|
||||
}, __('message.updated'));
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ class Client extends Model
|
||||
'outstanding_balance',
|
||||
'credit_limit',
|
||||
'is_active',
|
||||
'is_overdue',
|
||||
'client_type',
|
||||
'manager_name',
|
||||
'manager_tel',
|
||||
@@ -46,6 +47,7 @@ class Client extends Model
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
'is_overdue' => 'boolean',
|
||||
'tax_agreement' => 'boolean',
|
||||
'tax_amount' => 'decimal:2',
|
||||
'outstanding_balance' => 'decimal:2',
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
use App\Models\Tenants\Bill;
|
||||
use App\Models\Tenants\Deposit;
|
||||
use App\Models\Tenants\Sale;
|
||||
use Illuminate\Support\Collection;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* 채권 현황 서비스
|
||||
* 거래처별 월별 매출, 입금, 어음, 미수금 현황 조회
|
||||
* - 동적 월 표시 지원 (최근 1년: 동적 12개월)
|
||||
* - 이월잔액 + 누적 미수금 계산
|
||||
*/
|
||||
class ReceivablesService extends Service
|
||||
{
|
||||
@@ -21,9 +23,17 @@ class ReceivablesService extends Service
|
||||
public function index(array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$recentYear = $params['recent_year'] ?? false;
|
||||
$year = $params['year'] ?? date('Y');
|
||||
$search = $params['search'] ?? null;
|
||||
|
||||
// 월 기간 생성 (동적 월 지원)
|
||||
$periods = $this->generateMonthPeriods($recentYear, $year);
|
||||
$monthLabels = array_map(fn ($p) => $p['label'], $periods);
|
||||
|
||||
// 이월잔액 기준일 (첫번째 월의 시작일 전날)
|
||||
$carryForwardDate = Carbon::parse($periods[0]['start'])->subDay()->format('Y-m-d');
|
||||
|
||||
// 거래처 목록 조회
|
||||
$clientsQuery = Client::where('tenant_id', $tenantId)
|
||||
->where('is_active', true);
|
||||
@@ -37,52 +47,55 @@ public function index(array $params): array
|
||||
$result = [];
|
||||
|
||||
foreach ($clients as $client) {
|
||||
// 월별 데이터 수집
|
||||
$salesByMonth = $this->getSalesByMonth($tenantId, $client->id, $year);
|
||||
$depositsByMonth = $this->getDepositsByMonth($tenantId, $client->id, $year);
|
||||
$billsByMonth = $this->getBillsByMonth($tenantId, $client->id, $year);
|
||||
// 이월잔액 계산 (기준일 이전까지의 누적 미수금)
|
||||
$carryForwardBalance = $this->getCarryForwardBalance($tenantId, $client->id, $carryForwardDate);
|
||||
|
||||
// 미수금 계산 (매출 - 입금 - 어음)
|
||||
$receivablesByMonth = $this->calculateReceivables($salesByMonth, $depositsByMonth, $billsByMonth);
|
||||
// 월별 데이터 수집 (년-월 키 기반)
|
||||
$salesByPeriod = $this->getSalesByPeriods($tenantId, $client->id, $periods);
|
||||
$depositsByPeriod = $this->getDepositsByPeriods($tenantId, $client->id, $periods);
|
||||
$billsByPeriod = $this->getBillsByPeriods($tenantId, $client->id, $periods);
|
||||
|
||||
// 카테고리별 데이터 생성
|
||||
// 누적 미수금 계산
|
||||
$receivablesByPeriod = $this->calculateCumulativeReceivables(
|
||||
$carryForwardBalance,
|
||||
$salesByPeriod,
|
||||
$depositsByPeriod,
|
||||
$billsByPeriod,
|
||||
count($periods)
|
||||
);
|
||||
|
||||
// 카테고리별 데이터 생성 (배열 형태)
|
||||
$categories = [
|
||||
[
|
||||
'category' => 'sales',
|
||||
'amounts' => $this->formatMonthlyAmounts($salesByMonth),
|
||||
'amounts' => $this->formatPeriodAmounts($salesByPeriod, count($periods)),
|
||||
],
|
||||
[
|
||||
'category' => 'deposit',
|
||||
'amounts' => $this->formatMonthlyAmounts($depositsByMonth),
|
||||
'amounts' => $this->formatPeriodAmounts($depositsByPeriod, count($periods)),
|
||||
],
|
||||
[
|
||||
'category' => 'bill',
|
||||
'amounts' => $this->formatMonthlyAmounts($billsByMonth),
|
||||
'amounts' => $this->formatPeriodAmounts($billsByPeriod, count($periods)),
|
||||
],
|
||||
[
|
||||
'category' => 'receivable',
|
||||
'amounts' => $this->formatMonthlyAmounts($receivablesByMonth),
|
||||
],
|
||||
[
|
||||
'category' => 'memo',
|
||||
'amounts' => $this->getEmptyMonthlyAmounts(),
|
||||
'amounts' => $this->formatReceivableAmounts($receivablesByPeriod),
|
||||
],
|
||||
];
|
||||
|
||||
// 미수금이 있는 월 확인 (연체 표시용)
|
||||
$overdueMonths = [];
|
||||
foreach ($receivablesByMonth as $month => $amount) {
|
||||
if ($amount > 0) {
|
||||
$overdueMonths[] = $month;
|
||||
}
|
||||
}
|
||||
// 연체 여부: 최종 미수금이 양수인 경우
|
||||
$finalReceivable = end($receivablesByPeriod);
|
||||
$isOverdue = $client->is_overdue ?? ($finalReceivable > 0);
|
||||
|
||||
$result[] = [
|
||||
'id' => (string) $client->id,
|
||||
'vendor_id' => $client->id,
|
||||
'vendor_name' => $client->name,
|
||||
'is_overdue' => count($overdueMonths) > 0,
|
||||
'overdue_months' => $overdueMonths,
|
||||
'is_overdue' => $isOverdue,
|
||||
'memo' => $client->memo ?? '',
|
||||
'carry_forward_balance' => $carryForwardBalance,
|
||||
'month_labels' => $monthLabels,
|
||||
'categories' => $categories,
|
||||
];
|
||||
}
|
||||
@@ -97,7 +110,11 @@ public function index(array $params): array
|
||||
$result = array_values($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
// 공통 월 레이블 추가 (프론트엔드에서 헤더로 사용)
|
||||
return [
|
||||
'month_labels' => $monthLabels,
|
||||
'items' => $result,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,62 +123,55 @@ public function index(array $params): array
|
||||
public function summary(array $params): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$recentYear = $params['recent_year'] ?? false;
|
||||
$year = $params['year'] ?? date('Y');
|
||||
|
||||
$startDate = "{$year}-01-01";
|
||||
$endDate = "{$year}-12-31";
|
||||
// 월 기간 생성
|
||||
$periods = $this->generateMonthPeriods($recentYear, $year);
|
||||
$startDate = $periods[0]['start'];
|
||||
$endDate = end($periods)['end'];
|
||||
|
||||
// 총 매출
|
||||
// 이월잔액 기준일
|
||||
$carryForwardDate = Carbon::parse($startDate)->subDay()->format('Y-m-d');
|
||||
|
||||
// 전체 이월잔액 (모든 거래처)
|
||||
$totalCarryForward = $this->getTotalCarryForwardBalance($tenantId, $carryForwardDate);
|
||||
|
||||
// 기간 내 총 매출
|
||||
$totalSales = Sale::where('tenant_id', $tenantId)
|
||||
->whereNotNull('client_id')
|
||||
->whereBetween('sale_date', [$startDate, $endDate])
|
||||
->sum('total_amount');
|
||||
|
||||
// 총 입금
|
||||
// 기간 내 총 입금
|
||||
$totalDeposits = Deposit::where('tenant_id', $tenantId)
|
||||
->whereNotNull('client_id')
|
||||
->whereBetween('deposit_date', [$startDate, $endDate])
|
||||
->sum('amount');
|
||||
|
||||
// 총 어음
|
||||
// 기간 내 총 어음
|
||||
$totalBills = Bill::where('tenant_id', $tenantId)
|
||||
->whereNotNull('client_id')
|
||||
->where('bill_type', 'received')
|
||||
->whereBetween('issue_date', [$startDate, $endDate])
|
||||
->sum('amount');
|
||||
|
||||
// 총 미수금
|
||||
$totalReceivables = $totalSales - $totalDeposits - $totalBills;
|
||||
if ($totalReceivables < 0) {
|
||||
$totalReceivables = 0;
|
||||
}
|
||||
// 총 미수금 (이월잔액 + 매출 - 입금 - 어음)
|
||||
$totalReceivables = $totalCarryForward + $totalSales - $totalDeposits - $totalBills;
|
||||
|
||||
// 거래처 수
|
||||
$vendorCount = Client::where('tenant_id', $tenantId)
|
||||
->where('is_active', true)
|
||||
->whereHas('orders')
|
||||
->count();
|
||||
|
||||
// 연체 거래처 수 (미수금이 있는 거래처)
|
||||
$overdueVendorCount = DB::table('sales')
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereNotNull('client_id')
|
||||
->whereBetween('sale_date', [$startDate, $endDate])
|
||||
->select('client_id')
|
||||
->groupBy('client_id')
|
||||
->havingRaw('SUM(total_amount) > (
|
||||
SELECT COALESCE(SUM(d.amount), 0) FROM deposits d
|
||||
WHERE d.tenant_id = ? AND d.client_id = sales.client_id
|
||||
AND d.deposit_date BETWEEN ? AND ?
|
||||
) + (
|
||||
SELECT COALESCE(SUM(b.amount), 0) FROM bills b
|
||||
WHERE b.tenant_id = ? AND b.client_id = sales.client_id
|
||||
AND b.bill_type = "received"
|
||||
AND b.issue_date BETWEEN ? AND ?
|
||||
)', [$tenantId, $startDate, $endDate, $tenantId, $startDate, $endDate])
|
||||
// 연체 거래처 수 (미수금이 양수인 거래처)
|
||||
$overdueVendorCount = Client::where('tenant_id', $tenantId)
|
||||
->where('is_active', true)
|
||||
->where('is_overdue', true)
|
||||
->count();
|
||||
|
||||
return [
|
||||
'total_carry_forward' => (float) $totalCarryForward,
|
||||
'total_sales' => (float) $totalSales,
|
||||
'total_deposits' => (float) $totalDeposits,
|
||||
'total_bills' => (float) $totalBills,
|
||||
@@ -172,130 +182,271 @@ public function summary(array $params): array
|
||||
}
|
||||
|
||||
/**
|
||||
* 월별 매출 조회
|
||||
* 월 기간 배열 생성
|
||||
* @return array [['start' => 'Y-m-d', 'end' => 'Y-m-d', 'label' => 'YY.MM', 'year' => Y, 'month' => M], ...]
|
||||
*/
|
||||
private function getSalesByMonth(int $tenantId, int $clientId, string $year): array
|
||||
private function generateMonthPeriods(bool $recentYear, string $year): array
|
||||
{
|
||||
$result = [];
|
||||
$periods = [];
|
||||
|
||||
$sales = Sale::where('tenant_id', $tenantId)
|
||||
->where('client_id', $clientId)
|
||||
->whereYear('sale_date', $year)
|
||||
->select(
|
||||
DB::raw('MONTH(sale_date) as month'),
|
||||
DB::raw('SUM(total_amount) as total')
|
||||
)
|
||||
->groupBy(DB::raw('MONTH(sale_date)'))
|
||||
->get();
|
||||
if ($recentYear) {
|
||||
// 최근 1년: 현재 월 기준으로 12개월 전부터
|
||||
$current = Carbon::now()->startOfMonth();
|
||||
$start = $current->copy()->subMonths(11);
|
||||
|
||||
foreach ($sales as $sale) {
|
||||
$result[(int) $sale->month] = (float) $sale->total;
|
||||
for ($i = 0; $i < 12; $i++) {
|
||||
$month = $start->copy()->addMonths($i);
|
||||
$periods[] = [
|
||||
'start' => $month->format('Y-m-01'),
|
||||
'end' => $month->endOfMonth()->format('Y-m-d'),
|
||||
'label' => $month->format('y.m'),
|
||||
'year' => (int) $month->format('Y'),
|
||||
'month' => (int) $month->format('n'),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
// 특정 연도: 1월~12월
|
||||
for ($month = 1; $month <= 12; $month++) {
|
||||
$date = Carbon::createFromDate($year, $month, 1);
|
||||
$periods[] = [
|
||||
'start' => $date->format('Y-m-01'),
|
||||
'end' => $date->endOfMonth()->format('Y-m-d'),
|
||||
'label' => "{$month}월",
|
||||
'year' => (int) $year,
|
||||
'month' => $month,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
return $periods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 월별 입금 조회
|
||||
* 이월잔액 계산 (기준일 이전까지의 누적 미수금)
|
||||
*/
|
||||
private function getDepositsByMonth(int $tenantId, int $clientId, string $year): array
|
||||
private function getCarryForwardBalance(int $tenantId, int $clientId, string $beforeDate): float
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$deposits = Deposit::where('tenant_id', $tenantId)
|
||||
// 기준일 이전 총 매출
|
||||
$totalSales = Sale::where('tenant_id', $tenantId)
|
||||
->where('client_id', $clientId)
|
||||
->whereYear('deposit_date', $year)
|
||||
->select(
|
||||
DB::raw('MONTH(deposit_date) as month'),
|
||||
DB::raw('SUM(amount) as total')
|
||||
)
|
||||
->groupBy(DB::raw('MONTH(deposit_date)'))
|
||||
->get();
|
||||
->where('sale_date', '<=', $beforeDate)
|
||||
->sum('total_amount');
|
||||
|
||||
foreach ($deposits as $deposit) {
|
||||
$result[(int) $deposit->month] = (float) $deposit->total;
|
||||
}
|
||||
// 기준일 이전 총 입금
|
||||
$totalDeposits = Deposit::where('tenant_id', $tenantId)
|
||||
->where('client_id', $clientId)
|
||||
->where('deposit_date', '<=', $beforeDate)
|
||||
->sum('amount');
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 월별 어음 조회
|
||||
*/
|
||||
private function getBillsByMonth(int $tenantId, int $clientId, string $year): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$bills = Bill::where('tenant_id', $tenantId)
|
||||
// 기준일 이전 총 어음
|
||||
$totalBills = Bill::where('tenant_id', $tenantId)
|
||||
->where('client_id', $clientId)
|
||||
->where('bill_type', 'received')
|
||||
->whereYear('issue_date', $year)
|
||||
->select(
|
||||
DB::raw('MONTH(issue_date) as month'),
|
||||
DB::raw('SUM(amount) as total')
|
||||
)
|
||||
->groupBy(DB::raw('MONTH(issue_date)'))
|
||||
->get();
|
||||
->where('issue_date', '<=', $beforeDate)
|
||||
->sum('amount');
|
||||
|
||||
foreach ($bills as $bill) {
|
||||
$result[(int) $bill->month] = (float) $bill->total;
|
||||
}
|
||||
|
||||
return $result;
|
||||
return (float) ($totalSales - $totalDeposits - $totalBills);
|
||||
}
|
||||
|
||||
/**
|
||||
* 미수금 계산
|
||||
* 전체 거래처 이월잔액 합계
|
||||
*/
|
||||
private function calculateReceivables(array $sales, array $deposits, array $bills): array
|
||||
private function getTotalCarryForwardBalance(int $tenantId, string $beforeDate): float
|
||||
{
|
||||
$totalSales = Sale::where('tenant_id', $tenantId)
|
||||
->whereNotNull('client_id')
|
||||
->where('sale_date', '<=', $beforeDate)
|
||||
->sum('total_amount');
|
||||
|
||||
$totalDeposits = Deposit::where('tenant_id', $tenantId)
|
||||
->whereNotNull('client_id')
|
||||
->where('deposit_date', '<=', $beforeDate)
|
||||
->sum('amount');
|
||||
|
||||
$totalBills = Bill::where('tenant_id', $tenantId)
|
||||
->whereNotNull('client_id')
|
||||
->where('bill_type', 'received')
|
||||
->where('issue_date', '<=', $beforeDate)
|
||||
->sum('amount');
|
||||
|
||||
return (float) ($totalSales - $totalDeposits - $totalBills);
|
||||
}
|
||||
|
||||
/**
|
||||
* 기간별 매출 조회
|
||||
*/
|
||||
private function getSalesByPeriods(int $tenantId, int $clientId, array $periods): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
for ($month = 1; $month <= 12; $month++) {
|
||||
$salesAmount = $sales[$month] ?? 0;
|
||||
$depositAmount = $deposits[$month] ?? 0;
|
||||
$billAmount = $bills[$month] ?? 0;
|
||||
foreach ($periods as $index => $period) {
|
||||
$total = Sale::where('tenant_id', $tenantId)
|
||||
->where('client_id', $clientId)
|
||||
->whereBetween('sale_date', [$period['start'], $period['end']])
|
||||
->sum('total_amount');
|
||||
|
||||
$receivable = $salesAmount - $depositAmount - $billAmount;
|
||||
$result[$month] = max(0, $receivable);
|
||||
$result[$index] = (float) $total;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 월별 금액을 프론트엔드 형식으로 변환
|
||||
* 기간별 입금 조회
|
||||
*/
|
||||
private function formatMonthlyAmounts(array $monthlyData): array
|
||||
private function getDepositsByPeriods(int $tenantId, int $clientId, array $periods): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($periods as $index => $period) {
|
||||
$total = Deposit::where('tenant_id', $tenantId)
|
||||
->where('client_id', $clientId)
|
||||
->whereBetween('deposit_date', [$period['start'], $period['end']])
|
||||
->sum('amount');
|
||||
|
||||
$result[$index] = (float) $total;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 기간별 어음 조회
|
||||
*/
|
||||
private function getBillsByPeriods(int $tenantId, int $clientId, array $periods): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($periods as $index => $period) {
|
||||
$total = Bill::where('tenant_id', $tenantId)
|
||||
->where('client_id', $clientId)
|
||||
->where('bill_type', 'received')
|
||||
->whereBetween('issue_date', [$period['start'], $period['end']])
|
||||
->sum('amount');
|
||||
|
||||
$result[$index] = (float) $total;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 누적 미수금 계산
|
||||
* 1월: 이월잔액 + 1월 매출 - 1월 입금 - 1월 어음
|
||||
* 2월: 1월 미수금 + 2월 매출 - 2월 입금 - 2월 어음
|
||||
* ...
|
||||
*/
|
||||
private function calculateCumulativeReceivables(
|
||||
float $carryForward,
|
||||
array $sales,
|
||||
array $deposits,
|
||||
array $bills,
|
||||
int $periodCount
|
||||
): array {
|
||||
$result = [];
|
||||
$cumulative = $carryForward;
|
||||
|
||||
for ($i = 0; $i < $periodCount; $i++) {
|
||||
$monthSales = $sales[$i] ?? 0;
|
||||
$monthDeposits = $deposits[$i] ?? 0;
|
||||
$monthBills = $bills[$i] ?? 0;
|
||||
|
||||
$cumulative = $cumulative + $monthSales - $monthDeposits - $monthBills;
|
||||
$result[$i] = $cumulative;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 기간별 금액을 프론트엔드 형식으로 변환 (매출, 입금, 어음용)
|
||||
*/
|
||||
private function formatPeriodAmounts(array $periodData, int $periodCount): array
|
||||
{
|
||||
$amounts = [];
|
||||
$total = 0;
|
||||
|
||||
for ($month = 1; $month <= 12; $month++) {
|
||||
$key = "month{$month}";
|
||||
$amount = $monthlyData[$month] ?? 0;
|
||||
$amounts[$key] = $amount;
|
||||
for ($i = 0; $i < $periodCount; $i++) {
|
||||
$amount = $periodData[$i] ?? 0;
|
||||
$amounts[] = $amount;
|
||||
$total += $amount;
|
||||
}
|
||||
|
||||
$amounts['total'] = $total;
|
||||
|
||||
return $amounts;
|
||||
return [
|
||||
'values' => $amounts,
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 빈 월별 금액 데이터
|
||||
* 미수금 금액을 프론트엔드 형식으로 변환 (누적이므로 total = 마지막 값)
|
||||
*/
|
||||
private function getEmptyMonthlyAmounts(): array
|
||||
private function formatReceivableAmounts(array $receivables): array
|
||||
{
|
||||
$amounts = [];
|
||||
$values = array_values($receivables);
|
||||
$total = ! empty($values) ? end($values) : 0;
|
||||
|
||||
for ($month = 1; $month <= 12; $month++) {
|
||||
$amounts["month{$month}"] = 0;
|
||||
return [
|
||||
'values' => $values,
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 연체 상태 일괄 업데이트
|
||||
*/
|
||||
public function updateOverdueStatus(array $updates): int
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$updatedCount = 0;
|
||||
|
||||
foreach ($updates as $update) {
|
||||
$clientId = (int) $update['id'];
|
||||
$isOverdue = (bool) $update['is_overdue'];
|
||||
|
||||
$affected = Client::where('tenant_id', $tenantId)
|
||||
->where('id', $clientId)
|
||||
->update(['is_overdue' => $isOverdue]);
|
||||
|
||||
$updatedCount += $affected;
|
||||
}
|
||||
|
||||
$amounts['total'] = 0;
|
||||
|
||||
return $amounts;
|
||||
return $updatedCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래처 메모 업데이트
|
||||
*/
|
||||
public function updateMemo(int $clientId, string $memo): bool
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$affected = Client::where('tenant_id', $tenantId)
|
||||
->where('id', $clientId)
|
||||
->update(['memo' => $memo]);
|
||||
|
||||
return $affected > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래처 메모 일괄 업데이트
|
||||
*/
|
||||
public function updateMemos(array $memos): int
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$updatedCount = 0;
|
||||
|
||||
foreach ($memos as $item) {
|
||||
$clientId = (int) $item['id'];
|
||||
$memo = $item['memo'] ?? '';
|
||||
|
||||
$affected = Client::where('tenant_id', $tenantId)
|
||||
->where('id', $clientId)
|
||||
->update(['memo' => $memo]);
|
||||
|
||||
$updatedCount += $affected;
|
||||
}
|
||||
|
||||
return $updatedCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('clients', function (Blueprint $table) {
|
||||
$table->boolean('is_overdue')
|
||||
->default(false)
|
||||
->after('is_active')
|
||||
->comment('연체 여부 (수동 설정)');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('clients', function (Blueprint $table) {
|
||||
$table->dropColumn('is_overdue');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -540,6 +540,7 @@
|
||||
Route::get('', [ReceivablesController::class, 'index'])->name('v1.receivables.index');
|
||||
Route::get('/summary', [ReceivablesController::class, 'summary'])->name('v1.receivables.summary');
|
||||
Route::put('/overdue-status', [ReceivablesController::class, 'updateOverdueStatus'])->name('v1.receivables.update-overdue-status');
|
||||
Route::put('/memos', [ReceivablesController::class, 'updateMemos'])->name('v1.receivables.update-memos');
|
||||
});
|
||||
|
||||
// Daily Report API (일일 보고서)
|
||||
|
||||
Reference in New Issue
Block a user