MexAdminLogic.php 17 KB

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