MexAdminLogic.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. <?php
  2. namespace App\Module\Mex\Logic;
  3. use App\Module\Mex\Models\MexAdminOperation;
  4. use App\Module\Mex\Models\MexWarehouse;
  5. use App\Module\Mex\Models\MexTransaction;
  6. use App\Module\Mex\Models\MexPriceConfig;
  7. use App\Module\Mex\Enums\AdminOperationType;
  8. use App\Module\Mex\Enums\TransactionType;
  9. use App\Module\GameItems\Services\ItemService;
  10. use App\Module\Game\Enums\REWARD_SOURCE_TYPE;
  11. /**
  12. * 农贸市场管理员操作逻辑
  13. *
  14. * 处理管理员操作相关的核心业务逻辑
  15. */
  16. class MexAdminLogic
  17. {
  18. /**
  19. * 仓库账户ID
  20. */
  21. private const WAREHOUSE_USER_ID = 15;
  22. /**
  23. * 调控账户ID
  24. */
  25. private const CONTROL_USER_ID = 16;
  26. /**
  27. * 物品注入(增加市场供应)
  28. *
  29. * @param int $adminUserId 管理员用户ID
  30. * @param int $itemId 商品ID
  31. * @param int $quantity 数量
  32. * @param string $price 价格
  33. * @param string|null $remark 备注
  34. * @return array 操作结果
  35. */
  36. public static function injectItem(int $adminUserId, int $itemId, int $quantity, string $price, ?string $remark = null): array
  37. {
  38. if ($quantity <= 0) {
  39. return ['success' => false, 'message' => '注入数量必须大于0'];
  40. }
  41. if (bccomp($price, '0', 5) <= 0) {
  42. return ['success' => false, 'message' => '注入价格必须大于0'];
  43. }
  44. $totalAmount = bcmul($price, $quantity, 5);
  45. // 检查事务是否已开启
  46. \UCore\Db\Helper::check_tr();
  47. try {
  48. // 获取操作前的仓库数量
  49. $warehouse = MexWarehouse::firstOrCreate(
  50. ['item_id' => $itemId],
  51. [
  52. 'quantity' => 0,
  53. 'total_buy_amount' => '0.00000',
  54. 'total_sell_amount' => '0.00000',
  55. 'total_buy_quantity' => 0,
  56. 'total_sell_quantity' => 0,
  57. ]
  58. );
  59. $beforeQuantity = $warehouse->quantity;
  60. // 1. 给仓库账户添加真实物品(确保用户可以买到)
  61. $addItemResult = ItemService::addItem(self::WAREHOUSE_USER_ID, $itemId, $quantity, [
  62. 'reason' => 'mex_admin_inject',
  63. 'source_type' => REWARD_SOURCE_TYPE::ADMIN_GRANT->value,
  64. 'source_id' => $adminUserId,
  65. 'remark' => "管理员注入物品到农贸市场,管理员ID:{$adminUserId}"
  66. ]);
  67. if (!$addItemResult['success']) {
  68. throw new \Exception('给仓库账户添加物品失败:' . ($addItemResult['message'] ?? '未知错误'));
  69. }
  70. // 2. 更新仓库库存统计(注入操作相当于系统买入)
  71. $warehouse->quantity += $quantity;
  72. $warehouse->total_buy_quantity += $quantity;
  73. $warehouse->total_buy_amount = bcadd($warehouse->total_buy_amount, $totalAmount, 5);
  74. $warehouse->last_transaction_at = now();
  75. $warehouse->save();
  76. $afterQuantity = $warehouse->quantity;
  77. // 创建成交记录
  78. $transaction = MexTransaction::create([
  79. 'buy_order_id' => null,
  80. 'sell_order_id' => null,
  81. 'buyer_id' => self::WAREHOUSE_USER_ID, // 仓库账户作为买方
  82. 'seller_id' => self::CONTROL_USER_ID, // 调控账户作为卖方
  83. 'item_id' => $itemId,
  84. 'quantity' => $quantity,
  85. 'price' => $price,
  86. 'total_amount' => $totalAmount,
  87. 'transaction_type' => TransactionType::ADMIN_INJECT,
  88. 'is_admin_operation' => true,
  89. 'admin_user_id' => $adminUserId,
  90. ]);
  91. // 创建成交记录
  92. $transaction = MexTransaction::create([
  93. 'buy_order_id' => null,
  94. 'sell_order_id' => null,
  95. 'buyer_id' => self::WAREHOUSE_USER_ID, // 仓库账户作为买方
  96. 'seller_id' => self::CONTROL_USER_ID, // 调控账户作为卖方
  97. 'item_id' => $itemId,
  98. 'quantity' => $quantity,
  99. 'price' => $price,
  100. 'total_amount' => $totalAmount,
  101. 'transaction_type' => TransactionType::ADMIN_INJECT,
  102. 'is_admin_operation' => true,
  103. 'admin_user_id' => $adminUserId,
  104. ]);
  105. // 创建管理员操作记录
  106. $operation = MexAdminOperation::create([
  107. 'admin_user_id' => $adminUserId,
  108. 'operation_type' => AdminOperationType::INJECT,
  109. 'item_id' => $itemId,
  110. 'quantity' => $quantity,
  111. 'price' => $price,
  112. 'total_amount' => $totalAmount,
  113. 'before_warehouse_quantity' => $beforeQuantity,
  114. 'after_warehouse_quantity' => $afterQuantity,
  115. 'transaction_id' => $transaction->id,
  116. 'remark' => $remark,
  117. ]);
  118. return [
  119. 'success' => true,
  120. 'message' => '物品注入成功',
  121. 'operation_id' => $operation->id,
  122. 'transaction_id' => $transaction->id,
  123. 'before_quantity' => $beforeQuantity,
  124. 'after_quantity' => $afterQuantity,
  125. 'injected_quantity' => $quantity,
  126. ];
  127. } catch (\Exception $e) {
  128. // 重新抛出异常,不隐藏错误
  129. throw $e;
  130. }
  131. }
  132. /**
  133. * 物品回收(减少市场库存)
  134. *
  135. * @param int $adminUserId 管理员用户ID
  136. * @param int $itemId 商品ID
  137. * @param int $quantity 数量
  138. * @param string $price 价格
  139. * @param string|null $remark 备注
  140. * @return array 操作结果
  141. */
  142. public static function recycleItem(int $adminUserId, int $itemId, int $quantity, string $price, ?string $remark = null): array
  143. {
  144. if ($quantity <= 0) {
  145. return ['success' => false, 'message' => '回收数量必须大于0'];
  146. }
  147. if (bccomp($price, '0', 5) <= 0) {
  148. return ['success' => false, 'message' => '回收价格必须大于0'];
  149. }
  150. $totalAmount = bcmul($price, $quantity, 5);
  151. // 检查事务是否已开启
  152. \UCore\Db\Helper::check_tr();
  153. try {
  154. // 检查仓库库存
  155. $warehouse = MexWarehouse::where('item_id', $itemId)->first();
  156. if (!$warehouse || $warehouse->quantity < $quantity) {
  157. throw new \Exception('仓库库存不足,无法回收');
  158. }
  159. $beforeQuantity = $warehouse->quantity;
  160. // 1. 从仓库账户扣除真实物品
  161. // 物品仓库bug,仓库账号有问题,实际没有起作用,因此不清理仓库账号
  162. // $consumeItemResult = ItemService::consumeItem(self::WAREHOUSE_USER_ID, $itemId, null, $quantity, [
  163. // 'reason' => 'mex_admin_recycle',
  164. // 'source_id' => $adminUserId,
  165. // 'remark' => "管理员从农贸市场回收物品,管理员ID:{$adminUserId}"
  166. // ]);
  167. // if (!$consumeItemResult['success']) {
  168. // throw new \Exception('从仓库账户扣除物品失败:' . ($consumeItemResult['message'] ?? '未知错误'));
  169. // }
  170. // 2. 更新仓库库存统计(回收操作相当于系统卖出)
  171. $warehouse->quantity -= $quantity;
  172. $warehouse->total_sell_quantity += $quantity;
  173. $warehouse->total_sell_amount = bcadd($warehouse->total_sell_amount, $totalAmount, 5);
  174. $warehouse->last_transaction_at = now();
  175. $warehouse->save();
  176. $afterQuantity = $warehouse->quantity;
  177. // 创建成交记录
  178. $transaction = MexTransaction::create([
  179. 'buy_order_id' => null,
  180. 'sell_order_id' => null,
  181. 'buyer_id' => self::CONTROL_USER_ID, // 调控账户作为买方
  182. 'seller_id' => self::WAREHOUSE_USER_ID, // 仓库账户作为卖方
  183. 'item_id' => $itemId,
  184. 'quantity' => $quantity,
  185. 'price' => $price,
  186. 'total_amount' => $totalAmount,
  187. 'transaction_type' => TransactionType::ADMIN_RECYCLE,
  188. 'is_admin_operation' => true,
  189. 'admin_user_id' => $adminUserId,
  190. ]);
  191. // 创建管理员操作记录
  192. $operation = MexAdminOperation::create([
  193. 'admin_user_id' => $adminUserId,
  194. 'operation_type' => AdminOperationType::RECYCLE,
  195. 'item_id' => $itemId,
  196. 'quantity' => $quantity,
  197. 'price' => $price,
  198. 'total_amount' => $totalAmount,
  199. 'before_warehouse_quantity' => $beforeQuantity,
  200. 'after_warehouse_quantity' => $afterQuantity,
  201. 'transaction_id' => $transaction->id,
  202. 'remark' => $remark,
  203. ]);
  204. return [
  205. 'success' => true,
  206. 'message' => '物品回收成功',
  207. 'operation_id' => $operation->id,
  208. 'transaction_id' => $transaction->id,
  209. 'before_quantity' => $beforeQuantity,
  210. 'after_quantity' => $afterQuantity,
  211. 'recycled_quantity' => $quantity,
  212. ];
  213. } catch (\Exception $e) {
  214. // 重新抛出异常,不隐藏错误
  215. throw $e;
  216. }
  217. }
  218. /**
  219. * 获取管理员操作记录
  220. *
  221. * @param int $page 页码
  222. * @param int $pageSize 每页数量
  223. * @param int|null $adminUserId 管理员用户ID筛选
  224. * @param AdminOperationType|null $operationType 操作类型筛选
  225. * @return array 操作记录列表
  226. */
  227. public static function getAdminOperations(int $page = 1, int $pageSize = 20, ?int $adminUserId = null, ?AdminOperationType $operationType = null): array
  228. {
  229. $query = MexAdminOperation::query()->orderBy('created_at', 'desc');
  230. if ($adminUserId) {
  231. $query->where('admin_user_id', $adminUserId);
  232. }
  233. if ($operationType) {
  234. $query->where('operation_type', $operationType);
  235. }
  236. $operations = $query->paginate($pageSize, ['*'], 'page', $page);
  237. $result = [];
  238. foreach ($operations->items() as $operation) {
  239. $result[] = [
  240. 'id' => $operation->id,
  241. 'admin_user_id' => $operation->admin_user_id,
  242. 'operation_type' => $operation->operation_type->value,
  243. 'operation_type_desc' => $operation->operation_type->getDescription(),
  244. 'item_id' => $operation->item_id,
  245. 'quantity' => $operation->quantity,
  246. 'price' => $operation->price,
  247. 'total_amount' => $operation->total_amount,
  248. 'before_warehouse_quantity' => $operation->before_warehouse_quantity,
  249. 'after_warehouse_quantity' => $operation->after_warehouse_quantity,
  250. 'quantity_change' => $operation->after_warehouse_quantity - $operation->before_warehouse_quantity,
  251. 'transaction_id' => $operation->transaction_id,
  252. 'remark' => $operation->remark,
  253. 'created_at' => $operation->created_at,
  254. ];
  255. }
  256. return [
  257. 'operations' => $result,
  258. 'total' => $operations->total(),
  259. 'page' => $page,
  260. 'page_size' => $pageSize,
  261. ];
  262. }
  263. /**
  264. * 获取管理员操作统计
  265. *
  266. * @param int $days 统计天数
  267. * @return array 统计信息
  268. */
  269. public static function getAdminOperationStats(int $days = 7): array
  270. {
  271. $startDate = now()->subDays($days)->startOfDay();
  272. $stats = MexAdminOperation::where('created_at', '>=', $startDate)
  273. ->selectRaw('
  274. COUNT(*) as total_operations,
  275. COUNT(CASE WHEN operation_type = ? THEN 1 END) as inject_count,
  276. COUNT(CASE WHEN operation_type = ? THEN 1 END) as recycle_count,
  277. SUM(CASE WHEN operation_type = ? THEN quantity ELSE 0 END) as inject_quantity,
  278. SUM(CASE WHEN operation_type = ? THEN quantity ELSE 0 END) as recycle_quantity,
  279. SUM(CASE WHEN operation_type = ? THEN total_amount ELSE 0 END) as inject_amount,
  280. SUM(CASE WHEN operation_type = ? THEN total_amount ELSE 0 END) as recycle_amount,
  281. COUNT(DISTINCT admin_user_id) as active_admins,
  282. COUNT(DISTINCT item_id) as affected_items
  283. ', [
  284. AdminOperationType::INJECT->value,
  285. AdminOperationType::RECYCLE->value,
  286. AdminOperationType::INJECT->value,
  287. AdminOperationType::RECYCLE->value,
  288. AdminOperationType::INJECT->value,
  289. AdminOperationType::RECYCLE->value,
  290. ])
  291. ->first();
  292. return [
  293. 'days' => $days,
  294. 'total_operations' => $stats->total_operations ?? 0,
  295. 'inject_count' => $stats->inject_count ?? 0,
  296. 'recycle_count' => $stats->recycle_count ?? 0,
  297. 'inject_quantity' => $stats->inject_quantity ?? 0,
  298. 'recycle_quantity' => $stats->recycle_quantity ?? 0,
  299. 'inject_amount' => $stats->inject_amount ?? '0.00000',
  300. 'recycle_amount' => $stats->recycle_amount ?? '0.00000',
  301. 'net_quantity' => ($stats->inject_quantity ?? 0) - ($stats->recycle_quantity ?? 0),
  302. 'net_amount' => bcsub($stats->inject_amount ?? '0', $stats->recycle_amount ?? '0', 5),
  303. 'active_admins' => $stats->active_admins ?? 0,
  304. 'affected_items' => $stats->affected_items ?? 0,
  305. 'start_date' => $startDate,
  306. 'end_date' => now(),
  307. ];
  308. }
  309. /**
  310. * 初始化库存
  311. * 为所有已启用定价的商品创建库存记录(库存为0)
  312. *
  313. * @param int $adminUserId 管理员用户ID
  314. * @return array 初始化结果
  315. */
  316. public static function initializeWarehouse(int $adminUserId): array
  317. {
  318. throw new \Exception('功能禁用!已经开服。');
  319. // 检查事务是否已开启
  320. \UCore\Db\Helper::check_tr();
  321. try {
  322. // 1. 检查是否已有库存记录
  323. $existingWarehouseCount = MexWarehouse::count();
  324. if ($existingWarehouseCount > 0) {
  325. return [
  326. 'success' => false,
  327. 'message' => '库存表不为空,无法初始化。当前已有 ' . $existingWarehouseCount . ' 条库存记录。'
  328. ];
  329. }
  330. // 2. 获取所有已启用的定价配置
  331. $priceConfigs = MexPriceConfig::where('is_enabled', true)->get();
  332. if ($priceConfigs->isEmpty()) {
  333. return [
  334. 'success' => false,
  335. 'message' => '没有找到已启用的定价配置,无法初始化库存。'
  336. ];
  337. }
  338. $initializedItems = [];
  339. $now = now();
  340. // 3. 为每个已定价的商品创建库存记录
  341. foreach ($priceConfigs as $priceConfig) {
  342. $warehouse = MexWarehouse::create([
  343. 'item_id' => $priceConfig->item_id,
  344. 'quantity' => 0,
  345. 'total_buy_amount' => '0.00000',
  346. 'total_sell_amount' => '0.00000',
  347. 'total_buy_quantity' => 0,
  348. 'total_sell_quantity' => 0,
  349. 'last_transaction_at' => $now,
  350. ]);
  351. $initializedItems[] = [
  352. 'item_id' => $priceConfig->item_id,
  353. 'min_price' => $priceConfig->min_price,
  354. 'max_price' => $priceConfig->max_price,
  355. 'warehouse_id' => $warehouse->id,
  356. ];
  357. }
  358. // 4. 创建管理员操作记录
  359. $operation = MexAdminOperation::create([
  360. 'admin_user_id' => $adminUserId,
  361. 'operation_type' => AdminOperationType::INJECT, // 使用注入类型记录初始化操作
  362. 'item_id' => 0, // 特殊标记:0表示批量初始化
  363. 'quantity' => count($initializedItems),
  364. 'price' => '0.00000',
  365. 'total_amount' => '0.00000',
  366. 'before_warehouse_quantity' => 0,
  367. 'after_warehouse_quantity' => 0,
  368. 'transaction_id' => null,
  369. 'remark' => "初始化农贸市场库存,为 " . count($initializedItems) . " 个商品创建库存记录",
  370. ]);
  371. return [
  372. 'success' => true,
  373. 'message' => '库存初始化成功',
  374. 'operation_id' => $operation->id,
  375. 'initialized_count' => count($initializedItems),
  376. 'initialized_items' => $initializedItems,
  377. ];
  378. } catch (\Exception $e) {
  379. // 重新抛出异常,不隐藏错误
  380. throw $e;
  381. }
  382. }
  383. }