Files
sam-sales/salesmanagement/api/get_performance.php

316 lines
14 KiB
PHP

<?php
header("Content-Type: application/json; charset=utf-8");
require_once(__DIR__ . "/../../lib/mydb.php");
session_start();
if (!isset($_SESSION['sales_user'])) {
try {
$pdo_init = db_connect();
$stmt = $pdo_init->prepare("SELECT * FROM sales_member WHERE member_id = 'sales' LIMIT 1");
$stmt->execute();
$testUser = $stmt->fetch(PDO::FETCH_ASSOC);
if ($testUser) {
$currentUser = $testUser;
$userId = $currentUser['id'];
$userRole = $currentUser['role'];
} else {
echo json_encode(['success' => false, 'error' => '로그인이 필요합니다.']);
exit;
}
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => '로그인이 필요합니다.']);
exit;
}
} else {
$currentUser = $_SESSION['sales_user'];
$userId = $currentUser['id'];
$userRole = $currentUser['role'];
}
// 기간 필터링 (기본값: 당월)
$startDate = $_GET['startDate'] ?? date('Y-m-01');
$endDate = $_GET['endDate'] ?? date('Y-m-t');
try {
$pdo = db_connect();
// 1. 조직도 트리 생성 함수 (재귀)
function buildOrgTree($pdo, $parentId, $depth, $startDate, $endDate, $targetUserId) {
// 이 멤버의 정보 가져오기
$stmt = $pdo->prepare("SELECT id, name, role FROM sales_member WHERE id = ? AND is_active = 1");
$stmt->execute([$parentId]);
$member = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$member) return null;
// 이 멤버의 직접 실적 가져오기
// 1) 영업 실적 (manager_id = 본인): 영업 수당 대상 (상위 오버라이딩 O)
// 2) 매니저 수익 (sales_manager_id = 본인): 매니저 수당 대상 (상위 오버라이딩 X)
$sql = "
/* [Type: sales] 본인이 영업한 건 (가입비/계약금) -> 영업 수당 20%, 상위 5% */
SELECT 'sales' as type, p.id, t.tenant_name as customer, p.contract_date as contractDate, p.contract_amount as amount, 0 as fixed_commission, p.join_approved, p.payment_approved
FROM sales_tenant_products p
JOIN sales_tenants t ON p.tenant_id = t.id
WHERE t.manager_id = ?
AND p.contract_date BETWEEN ? AND ?
UNION ALL
/* [Type: manager] 본인이 매니징한 건 (구독료 수당) -> 매니저 수당 100%, 상위 오버라이딩 없음 */
SELECT 'manager' as type, p.id, t.tenant_name as customer, p.contract_date as contractDate, p.contract_amount as amount, p.commission_amount as fixed_commission, p.join_approved, p.payment_approved
FROM sales_tenant_products p
JOIN sales_tenants t ON p.tenant_id = t.id
WHERE t.sales_manager_id = ?
AND p.contract_date BETWEEN ? AND ?
UNION ALL
/* [Type: sales] 기타 영업 기록 (기존 호환) */
SELECT 'sales' as type, id, customer_name as customer, contract_date as contractDate, amount, 0 as fixed_commission, 1 as join_approved, 1 as payment_approved
FROM sales_record
WHERE member_id = ? AND status = 'completed' AND contract_date BETWEEN ? AND ?
";
// 파라미터 바인딩 순서 주의: manager_id, dates, sales_manager_id, dates, member_id, dates
$stmt = $pdo->prepare($sql);
$stmt->execute([$parentId, $startDate, $endDate, $parentId, $startDate, $endDate, $parentId, $startDate, $endDate]);
$directContracts = $stmt->fetchAll(PDO::FETCH_ASSOC);
$directSales = 0; // 매출 합계 (영업 매출만 집계할지, 전체 매출 집계할지? 보통 매출은 다 보여줌)
// 수당 계산 (건별 계산 후 합산)
$myDirectCommission = 0; // targetUserId가 이 노드(parentId)를 통해 받는 수당
foreach ($directContracts as &$c) {
$itemComm = 0;
if ($c['payment_approved'] == 1) {
if ($c['type'] === 'manager') {
// 매니저 수당: 본인(depth=0)인 경우에만 가져감 (오버라이딩 불가)
if ($parentId == $targetUserId) {
$itemComm = $c['fixed_commission'];
} else {
$itemComm = 0;
}
} else {
// 영업 수당: 본인 20%, 상위 5%, 상위2 3%
if ($parentId == $targetUserId) $itemComm = $c['amount'] * 0.20;
else if ($depth == 1) $itemComm = $c['amount'] * 0.05;
else if ($depth == 2) $itemComm = $c['amount'] * 0.03;
}
}
$c['commission'] = $itemComm; // 화면 표시용 (이 건으로 targetUserId가 번 돈)
$myDirectCommission += $itemComm;
// 매출 집계는 본인이 수행한 것만 (영업이든 매니징이든 매출액 자체는 표시)
$directSales += $c['amount'];
}
unset($c);
// 하위 멤버들 가져오기
$stmt = $pdo->prepare("SELECT id FROM sales_member WHERE parent_id = ? AND is_active = 1");
$stmt->execute([$parentId]);
$childrenIds = $stmt->fetchAll(PDO::FETCH_COLUMN);
$children = [];
$totalSales = $directSales;
$totalContractCount = count($directContracts);
$subtreeCommission = $myDirectCommission; // 내 직속 수당 + 하위로부터 올라온 오버라이딩 수당 합계
foreach ($childrenIds as $childId) {
$childNode = buildOrgTree($pdo, $childId, $depth + 1, $startDate, $endDate, $targetUserId);
if ($childNode) {
$children[] = $childNode;
$totalSales += $childNode['totalSales'];
$totalContractCount += $childNode['contractCount'];
$subtreeCommission += $childNode['commission']; // 하위 재귀 호출 결과(오버라이딩 된 값들) 합산
}
}
return [
'id' => 'node_' . $member['id'],
'real_id' => $member['id'],
'name' => $member['name'],
'role' => $member['role'],
'depth' => $depth,
'isDirect' => ($parentId == $targetUserId),
'directSales' => $directSales,
'totalSales' => $totalSales,
'contractCount' => $totalContractCount,
'directCommission' => $myDirectCommission, // 이 노드 자체에서 발생한 targetUser의 수입
'commission' => $subtreeCommission, // 이 노드 및 하위 전체에서 발생한 targetUser의 총 수입
'contracts' => $directContracts,
'children' => $children
];
}
// 대상 사용자 결정
$rootUserId = $userId;
if ($userRole === 'operator' && isset($_GET['target_id'])) {
$rootUserId = $_GET['target_id'];
}
// 트리 구축 (본인 노드)
$rootNode = buildOrgTree($pdo, $rootUserId, 0, $startDate, $endDate, $rootUserId);
// 프론트엔드 형식에 맞게 내 직접 판매 노드를 children의 첫번째로 삽입
if ($rootNode) {
$directNode = [
'id' => 'root-direct',
'name' => '내 직접 실적 (영업+매니징)',
'depth' => 0,
'role' => '본인',
'isDirect' => true,
'totalSales' => $rootNode['directSales'],
'contractCount' => count($rootNode['contracts']),
'commission' => $rootNode['directCommission'],
'contracts' => $rootNode['contracts'],
'children' => []
];
$actualChildren = $rootNode['children'];
$rootNode['children'] = array_merge([$directNode], $actualChildren);
$rootNode['isDirect'] = false;
}
// 전체 누적 실적 계산 (재귀) - 로직 동일하게 수정
function calculateTotalStats($pdo, $parentId, $targetUserId, $depth) {
$sql = "
SELECT 'sales' as type, p.contract_amount as amount, 0 as fixed_commission, p.join_approved, p.payment_approved
FROM sales_tenant_products p JOIN sales_tenants t ON p.tenant_id = t.id WHERE t.manager_id = ?
UNION ALL
SELECT 'manager' as type, p.contract_amount as amount, p.commission_amount as fixed_commission, p.join_approved, p.payment_approved
FROM sales_tenant_products p JOIN sales_tenants t ON p.tenant_id = t.id WHERE t.sales_manager_id = ?
UNION ALL
SELECT 'sales' as type, amount, 0 as fixed_commission, 1 as join_approved, 1 as payment_approved FROM sales_record WHERE member_id = ? AND status = 'completed'
";
$stmt = $pdo->prepare($sql);
$stmt->execute([$parentId, $parentId, $parentId]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$pendingJoin = 0;
$pendingPayment = 0;
foreach ($rows as $r) {
$directSales += $r['amount'];
if ($r['join_approved'] == 0) $pendingJoin++;
if ($r['join_approved'] == 1 && $r['payment_approved'] == 0) $pendingPayment++;
if ($r['payment_approved'] == 1) {
if ($r['type'] === 'manager') {
if ($parentId == $targetUserId) $commission += $r['fixed_commission'];
} else {
if ($parentId == $targetUserId) $commission += $r['amount'] * 0.20;
else if ($depth == 1) $commission += $r['amount'] * 0.05;
else if ($depth == 2) $commission += $r['amount'] * 0.03;
}
}
}
$stats = [
'totalSales' => $directSales,
'totalCommission' => $commission,
'totalCount' => $count,
'pendingJoin' => $pendingJoin,
'pendingPayment' => $pendingPayment
];
$stmt = $pdo->prepare("SELECT id FROM sales_member WHERE parent_id = ? AND is_active = 1");
$stmt->execute([$parentId]);
$children = $stmt->fetchAll(PDO::FETCH_COLUMN);
foreach ($children as $childId) {
if ($depth < 2) {
// 수당 합산은 2단계까지만 (sales 기준)
// manager 수당은 어차피 depth > 0 이면 0이므로 합산해도 상관없음
$childStats = calculateTotalStats($pdo, $childId, $targetUserId, $depth + 1);
$stats['totalSales'] += $childStats['totalSales'];
$stats['totalCommission'] += $childStats['totalCommission'];
$stats['totalCount'] += $childStats['totalCount'];
$stats['pendingJoin'] += $childStats['pendingJoin'];
$stats['pendingPayment'] += $childStats['pendingPayment'];
} else {
// 3단계 이하는 매출만 합산 (오버라이딩 X)
$childStats = calculateTotalStats($pdo, $childId, $targetUserId, $depth + 1);
$stats['totalSales'] += $childStats['totalSales'];
$stats['totalCount'] += $childStats['totalCount'];
$stats['pendingJoin'] += $childStats['pendingJoin'];
$stats['pendingPayment'] += $childStats['pendingPayment'];
}
}
return $stats;
}
if ($userRole === 'operator' && !isset($_GET['target_id'])) {
// 운영자가 특정 대상을 지정하지 않은 경우 전체 시스템 통계 반환
$stmt = $pdo->query("
SELECT
COALESCE(SUM(contract_amount), 0) as total_sales,
COALESCE(SUM(payout_amount), 0) as total_comm, -- 정산 지급액 합계
COUNT(*) as total_count,
SUM(CASE WHEN join_approved = 0 THEN 1 ELSE 0 END) as pending_join,
SUM(CASE WHEN join_approved = 1 AND payment_approved = 0 THEN 1 ELSE 0 END) as pending_payment
FROM sales_tenant_products
");
$tpData = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->query("
SELECT
COALESCE(SUM(amount), 0) as total_sales,
COALESCE(SUM(amount) * 0.25, 0) as total_comm, -- 영업수당(20%) + 관리수당(5%)
COUNT(*) as total_count
FROM sales_record
WHERE status = 'completed'
");
$srData = $stmt->fetch(PDO::FETCH_ASSOC);
$totalStats = [
'totalSales' => $tpData['total_sales'] + $srData['total_sales'],
'totalCommission' => $tpData['total_comm'] + $srData['total_comm'],
'totalCount' => $tpData['total_count'] + $srData['total_count'],
'pendingJoin' => (int)($tpData['pending_join'] ?? 0),
'pendingPayment' => (int)($tpData['pending_payment'] ?? 0)
];
} else {
$totalStats = calculateTotalStats($pdo, $rootUserId, $rootUserId, 0);
}
// 역할별 수당 합계 계산 (현재 기간 트리 기반)
function summarizeCommissions($node, &$summary) {
if ($node['isDirect']) {
$summary['direct'] += $node['commission'];
} else if ($node['depth'] == 1) {
$summary['manager'] += $node['commission'];
} else if ($node['depth'] == 2) {
$summary['educator'] += $node['commission'];
}
foreach ($node['children'] as $child) {
summarizeCommissions($child, $summary);
}
}
$commissionSummary = ['direct' => 0, 'manager' => 0, 'educator' => 0];
if ($rootNode) {
summarizeCommissions($rootNode, $commissionSummary);
}
echo json_encode([
'success' => true,
'total_stats' => $totalStats,
'period_stats' => [
'startDate' => $startDate,
'endDate' => $endDate,
'commission_summary' => $commissionSummary,
'total_period_commission' => array_sum($commissionSummary)
],
'org_tree' => $rootNode
], JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}