Files
sam-manage/app/Services/Sales/SalesDevelopmentApprovalService.php
김보곤 28f129393d feat:개발 진행중 → 승인대기로 이동 기능 추가
- revertToPending 서비스 메서드 추가
- revertToPending 컨트롤러 액션 추가
- /approvals/{id}/revert-pending 라우트 추가
- progress-list에 "승인대기로" 버튼 추가
- JavaScript revertToPending 함수 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 20:33:44 +09:00

242 lines
8.2 KiB
PHP

<?php
namespace App\Services\Sales;
use App\Models\Sales\SalesTenantManagement;
use App\Models\Tenants\Tenant;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
/**
* 개발 승인 관리 서비스
* 영업/매니저 진행률이 100% 완료된 고객의 개발 진행 상태를 관리
*/
class SalesDevelopmentApprovalService
{
/**
* 통계 조회
*/
public function getStats(): array
{
// 승인 대기 (영업 100% + 매니저 100% + hq_status = pending)
$pendingCount = SalesTenantManagement::query()
->where('sales_progress', 100)
->where('manager_progress', 100)
->where('hq_status', SalesTenantManagement::HQ_STATUS_PENDING)
->count();
// 개발 진행 중 (review ~ int_test)
$progressStatuses = [
SalesTenantManagement::HQ_STATUS_REVIEW,
SalesTenantManagement::HQ_STATUS_PLANNING,
SalesTenantManagement::HQ_STATUS_CODING,
SalesTenantManagement::HQ_STATUS_DEV_TEST,
SalesTenantManagement::HQ_STATUS_DEV_DONE,
SalesTenantManagement::HQ_STATUS_INT_TEST,
];
$inProgressCount = SalesTenantManagement::query()
->where('sales_progress', 100)
->where('manager_progress', 100)
->whereIn('hq_status', $progressStatuses)
->count();
// 오늘 완료 (handover 상태이고 오늘 업데이트된)
$todayCompletedCount = SalesTenantManagement::query()
->where('hq_status', SalesTenantManagement::HQ_STATUS_HANDOVER)
->whereDate('updated_at', today())
->count();
// 총 완료
$totalCompletedCount = SalesTenantManagement::query()
->where('hq_status', SalesTenantManagement::HQ_STATUS_HANDOVER)
->count();
return [
'pending' => $pendingCount,
'in_progress' => $inProgressCount,
'today_completed' => $todayCompletedCount,
'total_completed' => $totalCompletedCount,
];
}
/**
* 승인 대기 목록 조회
*/
public function getPendingApprovals(?string $search = null, int $perPage = 10): LengthAwarePaginator
{
$query = SalesTenantManagement::query()
->with(['tenant', 'salesPartner.user', 'manager'])
->where('sales_progress', 100)
->where('manager_progress', 100)
->where('hq_status', SalesTenantManagement::HQ_STATUS_PENDING);
// 검색
if ($search) {
$query->whereHas('tenant', function ($q) use ($search) {
$q->where('company_name', 'like', "%{$search}%")
->orWhere('business_number', 'like', "%{$search}%");
});
}
return $query->latest('updated_at')->paginate($perPage, ['*'], 'pending_page');
}
/**
* 개발 진행 중 목록 조회
*/
public function getInProgressItems(?string $search = null, int $perPage = 10): LengthAwarePaginator
{
$progressStatuses = [
SalesTenantManagement::HQ_STATUS_REVIEW,
SalesTenantManagement::HQ_STATUS_PLANNING,
SalesTenantManagement::HQ_STATUS_CODING,
SalesTenantManagement::HQ_STATUS_DEV_TEST,
SalesTenantManagement::HQ_STATUS_DEV_DONE,
SalesTenantManagement::HQ_STATUS_INT_TEST,
];
$query = SalesTenantManagement::query()
->with(['tenant', 'salesPartner.user', 'manager'])
->where('sales_progress', 100)
->where('manager_progress', 100)
->whereIn('hq_status', $progressStatuses);
// 검색
if ($search) {
$query->whereHas('tenant', function ($q) use ($search) {
$q->where('company_name', 'like', "%{$search}%")
->orWhere('business_number', 'like', "%{$search}%");
});
}
return $query->latest('updated_at')->paginate($perPage, ['*'], 'progress_page');
}
/**
* 완료 목록 조회
*/
public function getCompletedItems(?string $search = null, int $perPage = 10): LengthAwarePaginator
{
$query = SalesTenantManagement::query()
->with(['tenant', 'salesPartner.user', 'manager'])
->where('hq_status', SalesTenantManagement::HQ_STATUS_HANDOVER);
// 검색
if ($search) {
$query->whereHas('tenant', function ($q) use ($search) {
$q->where('company_name', 'like', "%{$search}%")
->orWhere('business_number', 'like', "%{$search}%");
});
}
return $query->latest('updated_at')->paginate($perPage, ['*'], 'completed_page');
}
/**
* 개발 승인 처리 (pending → review)
*/
public function approve(int $id): SalesTenantManagement
{
$management = SalesTenantManagement::findOrFail($id);
// 승인 조건 확인
if ($management->sales_progress < 100 || $management->manager_progress < 100) {
throw new \InvalidArgumentException('영업/매니저 진행률이 100%가 아닙니다.');
}
if ($management->hq_status !== SalesTenantManagement::HQ_STATUS_PENDING) {
throw new \InvalidArgumentException('이미 승인된 항목입니다.');
}
$management->update([
'hq_status' => SalesTenantManagement::HQ_STATUS_REVIEW,
]);
return $management->fresh();
}
/**
* 반려 처리
*/
public function reject(int $id, string $reason): SalesTenantManagement
{
$management = SalesTenantManagement::findOrFail($id);
if ($management->hq_status !== SalesTenantManagement::HQ_STATUS_PENDING) {
throw new \InvalidArgumentException('승인 대기 상태가 아닙니다.');
}
// notes 필드에 반려 사유 추가
$currentNotes = $management->notes ?? '';
$rejectionNote = '[반려 ' . now()->format('Y-m-d H:i') . '] ' . $reason;
$newNotes = $currentNotes ? $currentNotes . "\n" . $rejectionNote : $rejectionNote;
$management->update([
'notes' => $newNotes,
]);
return $management->fresh();
}
/**
* 본사 진행 상태 업데이트
*/
public function updateHqStatus(int $id, string $status): SalesTenantManagement
{
$management = SalesTenantManagement::findOrFail($id);
// 유효한 상태인지 확인
if (!array_key_exists($status, SalesTenantManagement::$hqStatusLabels)) {
throw new \InvalidArgumentException('유효하지 않은 상태입니다.');
}
// pending 상태에서는 review로만 변경 가능 (승인 처리)
if ($management->hq_status === SalesTenantManagement::HQ_STATUS_PENDING && $status !== SalesTenantManagement::HQ_STATUS_REVIEW) {
throw new \InvalidArgumentException('승인 대기 상태에서는 검토 상태로만 변경 가능합니다. 먼저 승인 처리를 해주세요.');
}
$management->update([
'hq_status' => $status,
]);
return $management->fresh();
}
/**
* 승인대기로 되돌리기 (진행중 → pending)
*/
public function revertToPending(int $id): SalesTenantManagement
{
$management = SalesTenantManagement::findOrFail($id);
// 이미 pending 상태인 경우
if ($management->hq_status === SalesTenantManagement::HQ_STATUS_PENDING) {
throw new \InvalidArgumentException('이미 승인대기 상태입니다.');
}
// handover(인계) 상태에서는 되돌리기 불가
if ($management->hq_status === SalesTenantManagement::HQ_STATUS_HANDOVER) {
throw new \InvalidArgumentException('인계 완료된 항목은 되돌릴 수 없습니다.');
}
$management->update([
'hq_status' => SalesTenantManagement::HQ_STATUS_PENDING,
]);
return $management->fresh();
}
/**
* 상세 정보 조회
*/
public function getDetail(int $id): SalesTenantManagement
{
return SalesTenantManagement::with([
'tenant',
'salesPartner.user',
'manager',
'contractProducts.product',
])->findOrFail($id);
}
}