setName('shopro:partner:install') ->setDescription('导入合伙股东表结构/配置,并升级后台菜单'); } protected function execute(Input $input, Output $output) { $prefix = config('database.prefix') ?: 'fa_'; $output->writeln('prefix=' . $prefix); $this->ensureAgentColumns($prefix, $output); $this->runSqlFile($prefix, $output); $this->applyBusinessConfig($output); $this->enableCommissionGoods($output); $this->backfillTasters($output); $this->backfillLastSelfOrder($output); $this->upgradeMenu($output); $this->clearConfigCache($output); $output->writeln('shopro:partner:install done'); return 0; } protected function ensureAgentColumns($prefix, Output $output) { $table = $prefix . 'shopro_commission_agent'; $columns = [ 'is_venue' => "tinyint(1) NOT NULL DEFAULT 0 COMMENT '酒道馆'", 'is_county' => "tinyint(1) NOT NULL DEFAULT 0 COMMENT '区县代理'", 'county_code' => "varchar(20) NOT NULL DEFAULT '' COMMENT '区县编码'", 'direct_order_seq' => "int(11) NOT NULL DEFAULT 0 COMMENT '直推合格单累计'", 'madow' => "decimal(20,8) NOT NULL DEFAULT 0.00000000 COMMENT '马豆'", 'voucher_total' => "decimal(12,2) NOT NULL DEFAULT 0.00 COMMENT '兑换券总额'", 'voucher_released' => "decimal(12,2) NOT NULL DEFAULT 0.00 COMMENT '兑换券已释放'", 'voucher_used' => "decimal(12,2) NOT NULL DEFAULT 0.00 COMMENT '兑换券已使用'", 'last_self_order_time' => "bigint(16) DEFAULT NULL COMMENT '本人最近合格订单时间'", ]; foreach ($columns as $name => $ddl) { if ($this->columnExists($table, $name)) { $output->writeln("skip column {$name}"); continue; } Db::execute("ALTER TABLE `{$table}` ADD COLUMN `{$name}` {$ddl}"); $output->writeln("add column {$name}"); } $indexes = [ 'idx_venue' => 'is_venue', 'idx_county' => 'is_county', 'idx_last_self_order' => 'last_self_order_time', ]; foreach ($indexes as $idx => $col) { if ($this->indexExists($table, $idx)) { continue; } try { Db::execute("ALTER TABLE `{$table}` ADD KEY `{$idx}` (`{$col}`)"); } catch (\Exception $e) { $output->writeln('index ' . $idx . ' ' . $e->getMessage()); } } } protected function runSqlFile($prefix, Output $output) { $file = (defined('ADDON_PATH') ? ADDON_PATH : ROOT_PATH . 'addons/') . 'shopro/upgrade/partner_v2.sql'; if (!is_file($file)) { $output->writeln('sql file missing'); return; } $sql = str_replace('__PREFIX__', $prefix, file_get_contents($file)); $sql = preg_replace('/^--.*$/m', '', $sql); $parts = array_filter(array_map('trim', explode(';', $sql))); foreach ($parts as $part) { if ($part === '' || stripos($part, 'ALTER TABLE') !== false && stripos($part, 'shopro_commission_agent') !== false) { continue; } if (stripos($part, 'SET @') === 0) { continue; } try { Db::execute($part); $output->writeln('ok: ' . mb_substr(preg_replace('/\s+/', ' ', $part), 0, 80)); } catch (\Exception $e) { $output->writeln('skip: ' . $e->getMessage()); } } } protected function upgradeMenu(Output $output) { try { $info = get_addon_info('shopro'); $class = get_addon_class('shopro'); $addon = new $class($info); if (method_exists($addon, 'upgrade')) { $addon->upgrade(); $output->writeln('menu upgraded'); } } catch (\Exception $e) { $output->writeln('menu upgrade fail: ' . $e->getMessage()); try { $shopro = new \ReflectionClass(\addons\shopro\Shopro::class); $method = $shopro->getMethod('getMenu'); $method->setAccessible(true); $menu = $method->invoke(null); Menu::upgrade('shopro', $menu['new']); $output->writeln('menu upgraded via reflection'); } catch (\Exception $ex) { $output->writeln('menu fail: ' . $ex->getMessage()); } } } protected function applyBusinessConfig(Output $output) { $force = [ 'shop.commission.partner_mode' => '1', 'shop.commission.partner_enable' => '1', 'shop.commission.agent_check' => '0', ]; foreach ($force as $code => $value) { $row = Db::name('shopro_config')->where('code', $code)->find(); if ($row) { Db::name('shopro_config')->where('code', $code)->update(['value' => $value]); } else { Db::name('shopro_config')->insert([ 'code' => $code, 'parent_code' => 'shop.commission', 'name' => $code, 'type' => 'boolean', 'value' => $value, 'weigh' => 80, ]); } $output->writeln("config {$code}={$value}"); } $become = Db::name('shopro_config')->where('code', 'shop.commission.become_agent')->find(); if ($become) { Db::name('shopro_config')->where('code', 'shop.commission.become_agent')->update([ 'value' => json_encode(['type' => 'user', 'value' => ''], JSON_UNESCAPED_UNICODE), ]); $output->writeln('config become_agent=user'); } $form = Db::name('shopro_config')->where('code', 'shop.commission.agent_form')->find(); if ($form) { $val = json_decode($form['value'], true); if (!is_array($val)) { $val = []; } $val['status'] = '0'; Db::name('shopro_config')->where('code', 'shop.commission.agent_form')->update([ 'value' => json_encode($val, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ]); $output->writeln('config agent_form.status=0'); } $level = Db::name('shopro_config')->where('code', 'shop.commission.level')->find(); if ($level && intval($level['value']) < 1) { Db::name('shopro_config')->where('code', 'shop.commission.level')->update(['value' => '1']); $output->writeln('config level=1'); } } protected function enableCommissionGoods(Output $output) { $goodsIds = Db::name('shopro_goods')->where('status', 'up')->column('id'); $count = 0; foreach ($goodsIds as $goodsId) { $row = Db::name('shopro_commission_goods')->where('goods_id', $goodsId)->find(); if ($row) { if (intval($row['status']) !== 1) { Db::name('shopro_commission_goods')->where('goods_id', $goodsId)->update(['status' => 1]); $count++; } continue; } Db::name('shopro_commission_goods')->insert([ 'goods_id' => $goodsId, 'self_rules' => 0, 'commission_rules' => '{}', 'status' => 1, 'commission_config' => null, 'commission_order_status' => 1, ]); $count++; } $output->writeln("commission goods enabled: {$count}/" . count($goodsIds)); } protected function backfillTasters(Output $output) { $userIds = Db::name('user')->column('id'); $now = time(); $count = 0; foreach ($userIds as $userId) { $exists = Db::name('shopro_commission_agent')->where('user_id', $userId)->find(); if ($exists) { continue; } Db::name('shopro_commission_agent')->insert([ 'user_id' => $userId, 'level' => 1, 'status' => 'normal', 'apply_num' => 1, 'become_time' => $now, 'createtime' => $now, 'updatetime' => $now, ]); $count++; } $output->writeln("backfill tasters: {$count}"); } protected function backfillLastSelfOrder(Output $output) { $rows = Db::name('shopro_order') ->where('status', 'in', ['paid', 'completed']) ->where('paid_time', '>', 0) ->field('user_id, max(paid_time) as last_paid') ->group('user_id') ->select(); $count = 0; foreach ($rows as $row) { $agent = Db::name('shopro_commission_agent')->where('user_id', $row['user_id'])->find(); if (!$agent || !empty($agent['last_self_order_time'])) { continue; } Db::name('shopro_commission_agent')->where('user_id', $row['user_id'])->update([ 'last_self_order_time' => intval($row['last_paid']), ]); $count++; } $output->writeln("backfill last_self_order_time: {$count}"); } protected function clearConfigCache(Output $output) { $codes = [ 'shop.commission', 'shop.commission.partner_mode', 'shop.commission.partner_enable', 'shop.commission.become_agent', 'shop.commission.agent_form', 'shop.commission.agent_check', 'shop.commission.level', ]; foreach ($codes as $code) { cache('config:' . $code, null); } $output->writeln('config cache cleared'); } protected function columnExists($table, $column) { $database = config('database.database'); $row = Db::query("SELECT COUNT(*) AS c FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?", [$database, $table, $column]); return intval($row[0]['c'] ?? 0) > 0; } protected function indexExists($table, $index) { $database = config('database.database'); $row = Db::query("SELECT COUNT(*) AS c FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND INDEX_NAME = ?", [$database, $table, $index]); return intval($row[0]['c'] ?? 0) > 0; } }