用户表:uid => {uid, spread_uid, member_level} */ private array $users = []; /** @var array[] 报单订单表 */ private array $orders = []; /** 升级门槛(默认值,对应 DEFAULT_DIRECT_REQUIRE / DEFAULT_UMBRELLA_REQUIRE) */ private array $directRequire = [1 => 3]; private array $umbrellaRequire = [2 => 30, 3 => 100, 4 => 1000]; /** 最低直推人数要求(云店及以上升级需 ≥3 直推) */ private int $minDirectSpreadCount = 3; // ---- 数据构建辅助 --------------------------------------------------- private function addUser(int $uid, int $spreadUid = 0, int $memberLevel = 0): void { $this->users[$uid] = [ 'uid' => $uid, 'spread_uid' => $spreadUid, 'member_level' => $memberLevel, ]; } /** * 为指定用户添加 N 笔已支付报单订单 */ private function addQueueOrders(int $uid, int $count): void { for ($i = 0; $i < $count; $i++) { $this->orders[] = [ 'uid' => $uid, 'is_queue_goods' => 1, 'paid' => 1, 'is_del' => 0, ]; } } // ---- 核心逻辑(镜像 MemberLevelServices) ---------------------------- /** * 检查并执行升级(复现 checkUpgrade 逻辑) * * @return int 升级后的等级 */ private function checkUpgrade(int $uid): int { if (!isset($this->users[$uid])) { return 0; } $currentLevel = (int)$this->users[$uid]['member_level']; $nextLevel = $currentLevel + 1; if ($nextLevel > 4) { return $currentLevel; } if ($this->checkLevelCondition($uid, $currentLevel, $nextLevel)) { $this->users[$uid]['member_level'] = $nextLevel; // 连续升级检查 return $this->checkUpgrade($uid); } return $currentLevel; } private function checkLevelCondition(int $uid, int $currentLevel, int $nextLevel): bool { if ($nextLevel === 1) { $require = $this->directRequire[1]; return $this->getDirectQueueOrderCount($uid) >= $require; } $umbrellaRequire = $this->umbrellaRequire[$nextLevel] ?? PHP_INT_MAX; if ($this->getUmbrellaQueueOrderCount($uid) < $umbrellaRequire) { return false; } return $this->getDirectSpreadCount($uid) >= $this->minDirectSpreadCount; } /** 直推报单订单数(仅统计直推1层) */ private function getDirectQueueOrderCount(int $uid): int { $directUids = $this->getDirectUids($uid); if (empty($directUids)) { return 0; } return count(array_filter( $this->orders, fn($o) => in_array($o['uid'], $directUids, true) && $o['is_queue_goods'] === 1 && $o['paid'] === 1 && $o['is_del'] === 0 )); } /** 直推人数 */ private function getDirectSpreadCount(int $uid): int { return count($this->getDirectUids($uid)); } /** 伞下总报单订单数(含业绩分离逻辑,DFS) */ private function getUmbrellaQueueOrderCount(int $uid, int $maxDepth = 8): int { return $this->recursiveUmbrellaCount($uid, $maxDepth); } private function recursiveUmbrellaCount(int $uid, int $remainDepth): int { if ($remainDepth <= 0) { return 0; } $directChildren = $this->getDirectUids($uid); if (empty($directChildren)) { return 0; } $total = 0; foreach ($directChildren as $childUid) { $childLevel = (int)($this->users[$childUid]['member_level'] ?? 0); // 业绩分离:直推下级已是云店(level≥2),跳过其团队 if ($childLevel >= 2) { continue; } // 统计该下级自身的报单订单 $total += count(array_filter( $this->orders, fn($o) => $o['uid'] === $childUid && $o['is_queue_goods'] === 1 && $o['paid'] === 1 && $o['is_del'] === 0 )); // 递归统计下级的伞下 $total += $this->recursiveUmbrellaCount($childUid, $remainDepth - 1); } return $total; } /** 获取直推子用户 uid 列表 */ private function getDirectUids(int $uid): array { return array_keys(array_filter( $this->users, fn($u) => (int)$u['spread_uid'] === $uid )); } protected function setUp(): void { $this->users = []; $this->orders = []; } // ----------------------------------------------------------------------- // 测试用例 // ----------------------------------------------------------------------- /** * @test * 普通→创客:直推3单满足条件 */ public function testUpgradeToLevel1With3DirectOrders(): void { $this->addUser(1); // 普通会员 $this->addUser(10, 1); // 直推下级A $this->addUser(11, 1); // 直推下级B $this->addUser(12, 1); // 直推下级C // 直推下级各下1单报单订单 $this->addQueueOrders(10, 1); $this->addQueueOrders(11, 1); $this->addQueueOrders(12, 1); $level = $this->checkUpgrade(1); $this->assertEquals(1, $level, '直推3单应升级到创客(level=1)'); } /** * @test * 直推2单不满足创客条件 */ public function testNotUpgradeWithOnly2DirectOrders(): void { $this->addUser(1); $this->addUser(10, 1); $this->addUser(11, 1); $this->addQueueOrders(10, 1); $this->addQueueOrders(11, 1); // 仅2单 $level = $this->checkUpgrade(1); $this->assertEquals(0, $level, '直推2单不满足创客条件,仍为普通会员'); } /** * @test * 创客→云店:伞下30单 + 3个直推人 */ public function testUpgradeToLevel2With30UmbrellaOrders(): void { // uid=1 已是创客(level=1),有3个直推 $this->addUser(1, 0, 1); $this->addUser(10, 1); $this->addUser(11, 1); $this->addUser(12, 1); // 伞下合计30单 $this->addQueueOrders(10, 10); $this->addQueueOrders(11, 10); $this->addQueueOrders(12, 10); $level = $this->checkUpgrade(1); $this->assertEquals(2, $level, '伞下30单+3直推 → 升级到云店(level=2)'); } /** * @test * 伞下29单不满足云店条件 */ public function testNotUpgradeLevel2WithOnly29Orders(): void { $this->addUser(1, 0, 1); $this->addUser(10, 1); $this->addUser(11, 1); $this->addUser(12, 1); $this->addQueueOrders(10, 10); $this->addQueueOrders(11, 10); $this->addQueueOrders(12, 9); // 总计29单 $level = $this->checkUpgrade(1); $this->assertEquals(1, $level, '伞下29单不满足云店条件'); } /** * @test * 仅2个直推人时,即使业绩达标也不升级云店 */ public function testNotUpgradeLevel2Without3DirectSpreads(): void { $this->addUser(1, 0, 1); $this->addUser(10, 1); // 仅2个直推 $this->addUser(11, 1); $this->addQueueOrders(10, 20); $this->addQueueOrders(11, 20); // 总计40单 >= 30,但直推人只有2个 $level = $this->checkUpgrade(1); $this->assertEquals(1, $level, '直推人数<3,即使业绩达标也不升级'); } /** * @test * 业绩分离:直推下级已是云店(level=2),其团队业绩不计入上级伞下 */ public function testUmbrellaPerformanceSeparation(): void { /* * 树结构: * uid=1 (创客 level=1) * ├─ uid=10 (云店 level=2) ← 业绩分离,其下单不计入uid=1 * │ └─ uid=100 (普通) → 下了20单(不计入uid=1) * ├─ uid=11 (普通 level=0) → 下了15单(计入uid=1) * └─ uid=12 (普通 level=0) → 下了15单(计入uid=1) */ $this->addUser(1, 0, 1); // 待检查升级 $this->addUser(10, 1, 2); // 已是云店,业绩分离 $this->addUser(100, 10, 0); // 10的下级 $this->addUser(11, 1, 0); $this->addUser(12, 1, 0); $this->addQueueOrders(100, 20); // 这20单不应计入uid=1 $this->addQueueOrders(11, 15); // 计入 $this->addQueueOrders(12, 15); // 计入 $umbrella = $this->getUmbrellaQueueOrderCount(1); $this->assertEquals(30, $umbrella, '业绩分离后,仅计入11+12的30单'); // uid=1 有3个直推(10/11/12),伞下=30,满足云店条件 $level = $this->checkUpgrade(1); $this->assertEquals(2, $level, '业绩分离+30单达标,升级到云店'); } /** * @test * 业绩分离:整个已分离的子树递归不计入 */ public function testUmbrellaPerformanceSeparationDeepTree(): void { /* * uid=1 (创客) 的伞下树: * uid=10 (云店 level=2) → 业绩分离,整个子树不计入 * └─ uid=101 → 下50单 * └─ uid=201 → 下50单 * uid=11 (普通) → 下10单(计入) * uid=12 (普通) → 下10单(计入) * uid=13 (普通) → 下10单(计入) */ $this->addUser(1, 0, 1); $this->addUser(10, 1, 2); // 云店,分离 $this->addUser(101, 10, 0); // 10的子 $this->addUser(201, 101, 0); // 101的子 $this->addUser(11, 1, 0); $this->addUser(12, 1, 0); $this->addUser(13, 1, 0); $this->addQueueOrders(101, 50); // 不计入uid=1 $this->addQueueOrders(201, 50); // 不计入uid=1 $this->addQueueOrders(11, 10); // 计入 $this->addQueueOrders(12, 10); // 计入 $this->addQueueOrders(13, 10); // 计入 $umbrella = $this->getUmbrellaQueueOrderCount(1); $this->assertEquals(30, $umbrella, '深层分离树中,仅计入未分离分支的30单'); } /** * @test * 云店→服务商:伞下100单 + 3个直推 */ public function testUpgradeToLevel3(): void { $this->addUser(1, 0, 2); // 云店 for ($i = 10; $i <= 13; $i++) { $this->addUser($i, 1, 0); } // 伞下合计100单 $this->addQueueOrders(10, 40); $this->addQueueOrders(11, 30); $this->addQueueOrders(12, 20); $this->addQueueOrders(13, 10); $level = $this->checkUpgrade(1); $this->assertEquals(3, $level, '伞下100单 → 升级到服务商(level=3)'); } /** * @test * 服务商→分公司:伞下1000单 + 3个直推 */ public function testUpgradeToLevel4(): void { $this->addUser(1, 0, 3); // 服务商 for ($i = 10; $i <= 13; $i++) { $this->addUser($i, 1, 0); } // 伞下合计1000单 $this->addQueueOrders(10, 400); $this->addQueueOrders(11, 300); $this->addQueueOrders(12, 200); $this->addQueueOrders(13, 100); $level = $this->checkUpgrade(1); $this->assertEquals(4, $level, '伞下1000单 → 升级到分公司(level=4)'); } /** * @test * 已是最高等级(level=4)时不再检查 */ public function testMaxLevelNoFurtherUpgrade(): void { $this->addUser(1, 0, 4); // 已是分公司 // 无论伞下多少单,等级不变 for ($i = 10; $i <= 14; $i++) { $this->addUser($i, 1, 0); $this->addQueueOrders($i, 1000); } $level = $this->checkUpgrade(1); $this->assertEquals(4, $level, '已是最高等级,不再升级'); } /** * @test * 连续升级:满足条件时一次 checkUpgrade 可连升多级 * 场景:普通会员同时满足创客条件, * 但不满足云店条件(伞下不足30)→ 只升到创客 */ public function testNoContinuousUpgradeWhenNextLevelNotMet(): void { $this->addUser(1, 0, 0); // 普通 $this->addUser(10, 1, 0); $this->addUser(11, 1, 0); $this->addUser(12, 1, 0); // 满足创客(直推3单),但伞下仅3单 < 30,不满足云店 $this->addQueueOrders(10, 1); $this->addQueueOrders(11, 1); $this->addQueueOrders(12, 1); $level = $this->checkUpgrade(1); $this->assertEquals(1, $level, '只连升到创客,云店条件不满足则停止'); } /** * @test * 直推单数统计仅计算直推1层,不递归 * 验证:直推的下级(孙子级)的订单不计入直推订单数 */ public function testDirectOrderCountIsShallowOnly(): void { $this->addUser(1, 0, 0); $this->addUser(10, 1, 0); // 直推 $this->addUser(100, 10, 0); // 孙子级(不是直推) // 孙子级下2单,直推层无订单 $this->addQueueOrders(100, 2); $directCount = $this->getDirectQueueOrderCount(1); $this->assertEquals(0, $directCount, '孙子级订单不计入直推订单数'); } /** * @test * 同一用户下多单,仅计算为1人的订单数(不影响升级统计正确性) */ public function testMultipleOrdersFromSameUser(): void { $this->addUser(1, 0, 0); $this->addUser(10, 1, 0); $this->addUser(11, 1, 0); $this->addUser(12, 1, 0); // 直推下级每人多单,直推订单数 = 订单总数(按订单统计,非按人统计) $this->addQueueOrders(10, 3); // 3单 $this->addQueueOrders(11, 0); // 0单 $this->addQueueOrders(12, 0); // 0单 $directCount = $this->getDirectQueueOrderCount(1); $this->assertEquals(3, $directCount, '直推订单数按订单计算,同一人多单分别统计'); // 总计3单 ≥ 3,满足创客条件 $level = $this->checkUpgrade(1); $this->assertEquals(1, $level, '直推3单(来自同一人)满足创客升级'); } }