CostCalculation.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  1. <?php
  2. namespace app\api\controller;
  3. use app\common\controller\Api;
  4. use think\Db;
  5. use think\Queue;
  6. use app\service\UnifiedCostCalculationService;
  7. use think\Request;
  8. class CostCalculation extends Api
  9. {
  10. protected $noNeedLogin = ['*'];
  11. protected $noNeedRight = ['*'];
  12. // // 楼层编组映射(优化版)
  13. // const FLOOR_GROUP_MAP = [
  14. // '1' => ['02、胶印机组', '03、卷凹机组', '06、单凹机组', '05、圆切机组', '04、圆烫机组', '10、模切机组', '09、烫金机组'], // 1楼编组
  15. // '2' => ['01、切纸机组', '11、检品机组', '07、丝印机组', '12、覆膜机组', '08、喷码机组'], // 2楼编组
  16. // ];
  17. //
  18. // // 科目名称常量(简化版)
  19. // const SUBJECT_WASTE_GAS = '废气处理(RTO)';
  20. // const SUBJECT_BOILER = '锅炉';
  21. // const SUBJECT_AIR_COMPRESSOR = '空压机';
  22. // const SUBJECT_HOT_WATER_BOILER = '热水锅炉';
  23. // const SUBJECT_VACUUM_BLOWER = '真空鼓风机';
  24. // const SUBJECT_CENTRAL_AIR_CONDITIONER = '中央空调';
  25. // const SUBJECT_TOTAL_TO_APPORTION = '待分摊总额';
  26. //
  27. // // 需要分配到卷凹机组的科目列表
  28. // const ROLL_COATER_ONLY_SUBJECTS = [
  29. // self::SUBJECT_BOILER,
  30. // self::SUBJECT_HOT_WATER_BOILER,
  31. // ];
  32. //
  33. // // 简化科目列表
  34. // const SIMPLIFIED_SUBJECTS = [
  35. // self::SUBJECT_WASTE_GAS,
  36. // self::SUBJECT_BOILER,
  37. // self::SUBJECT_AIR_COMPRESSOR,
  38. // self::SUBJECT_HOT_WATER_BOILER,
  39. // self::SUBJECT_VACUUM_BLOWER,
  40. // self::SUBJECT_CENTRAL_AIR_CONDITIONER,
  41. // self::SUBJECT_TOTAL_TO_APPORTION,
  42. // ];
  43. //
  44. // /**
  45. // * 根据设备编号获取楼层信息
  46. // */
  47. // protected function getFloorByMachineCode($machineCode)
  48. // {
  49. // static $machineFloorCache = [];
  50. //
  51. // if (isset($machineFloorCache[$machineCode])) {
  52. // return $machineFloorCache[$machineCode];
  53. // }
  54. //
  55. // // 查询设备编组
  56. // $group = Db::name('设备_基本资料')
  57. // ->where('sys_sbID', '<>','')
  58. // ->where('设备编号', $machineCode)
  59. // ->value('设备编组');
  60. //
  61. // if (!$group) {
  62. // $machineFloorCache[$machineCode] = null;
  63. // return null;
  64. // }
  65. //
  66. // // 根据编组判断楼层
  67. // foreach (self::FLOOR_GROUP_MAP as $floor => $groups) {
  68. // foreach ($groups as $groupName) {
  69. // if (strpos($group, $groupName) !== false) {
  70. // $machineFloorCache[$machineCode] = $floor;
  71. // return $floor;
  72. // }
  73. // }
  74. // }
  75. //
  76. // $machineFloorCache[$machineCode] = null;
  77. // return null;
  78. // }
  79. //
  80. // /**
  81. // * 根据楼层获取该楼层的所有机台
  82. // */
  83. // protected function getMachinesByFloor($floor, $month)
  84. // {
  85. // // 获取该楼层的所有编组
  86. // $groups = self::FLOOR_GROUP_MAP[$floor] ?? [];
  87. // if (empty($groups)) {
  88. // return [];
  89. // }
  90. //
  91. // try {
  92. // // 方法1:使用更简单的查询条件构建方式
  93. // $query = Db::name('成本v23_月度成本明细')
  94. // ->alias('c')
  95. // ->join('设备_基本资料 e', 'c.sczl_jtbh = e.设备编号', 'LEFT')
  96. // ->where('c.sys_ny', $month)
  97. // ->where('e.设备编号', '<>', '')
  98. // ->whereIn('e.设备编组', $groups);
  99. //
  100. //
  101. // $query->field([
  102. // 'c.sczl_jtbh' => '机台编号',
  103. // 'c.占用机时',
  104. // 'e.设备编组',
  105. // 'c.sczl_gdbh' => '工单编号',
  106. // 'c.sczl_yjno' => '印件号',
  107. // 'c.sczl_gxh' => '工序号',
  108. // 'c.Uniqid',
  109. // 'c.车间名称'
  110. // ]);
  111. //
  112. // $machines = $query->select();
  113. //
  114. // return $machines ?: [];
  115. //
  116. // } catch (\Exception $e) {
  117. // throw new \Exception('查询机台数据失败: ' . $e->getMessage());
  118. //
  119. // }
  120. // }
  121. //
  122. // /**
  123. // * 获取所有机台(不分楼层)
  124. // */
  125. // protected function getAllMachines($month)
  126. // {
  127. // return Db::name('成本v23_月度成本明细')
  128. // ->alias('c')
  129. // ->join('设备_基本资料 e', 'c.sczl_jtbh = e.设备编号')
  130. // ->where('c.sys_ny', $month)
  131. // ->where('e.设备编号', '<>', '')
  132. // ->field([
  133. // 'c.sczl_jtbh' => '机台编号',
  134. // 'c.占用机时',
  135. // 'e.设备编组',
  136. // 'c.sczl_gdbh' => '工单编号',
  137. // 'c.sczl_yjno' => '印件号',
  138. // 'c.sczl_gxh' => '工序号',
  139. // 'c.Uniqid',
  140. // 'c.车间名称'
  141. // ])
  142. // ->select();
  143. // }
  144. //
  145. // /**
  146. // * 获取卷凹机组的所有机台
  147. // */
  148. // protected function getRollCoaterMachines($month)
  149. // {
  150. // return Db::name('成本v23_月度成本明细')
  151. // ->alias('c')
  152. // ->join('设备_基本资料 e', 'c.sczl_jtbh = e.设备编号')
  153. // ->where('c.sys_ny', $month)
  154. // ->where('e.设备编号', '<>', '')
  155. // ->where('e.设备编组', 'like', '%03、卷凹机组%')
  156. // ->field([
  157. // 'c.sczl_jtbh' => '机台编号',
  158. // 'c.占用机时',
  159. // 'e.设备编组',
  160. // 'c.sczl_gdbh' => '工单编号',
  161. // 'c.sczl_yjno' => '印件号',
  162. // 'c.sczl_gxh' => '工序号',
  163. // 'c.Uniqid',
  164. // 'c.车间名称'
  165. // ])
  166. // ->select();
  167. // }
  168. //
  169. // /**
  170. // * 主计算方法
  171. // */
  172. // public function costCalculation()
  173. // {
  174. // if (!$this->request->isGet()) {
  175. // $this->error('请求错误');
  176. // }
  177. //
  178. // $param = $this->request->param();
  179. // if (empty($param['month'])) {
  180. // $this->error('请选择月份');
  181. // }
  182. //
  183. // $month = $param['month'];
  184. // $sysId = $param['sys_id'] ?? '';
  185. //
  186. //
  187. // // 计算分摊
  188. // $apportionmentResults = $this->calculateApportionment($month);
  189. //
  190. // // 格式化结果并保存
  191. // $formattedResults = $this->formatApportionmentResults($apportionmentResults, $month, $sysId);
  192. // $this->saveApportionmentCoefficients($formattedResults, $month);
  193. //
  194. // // 进行二次分配(到工单)
  195. // $this->allocateToWorkOrders($month);
  196. //
  197. // $this->success('成本分摊计算成功');
  198. // }
  199. //
  200. // /**
  201. // * 计算分摊(核心方法)
  202. // */
  203. // protected function calculateApportionment($month)
  204. // {
  205. // // 获取水电气分摊数据
  206. // $utilityData = $this->getUtilityData($month);
  207. //
  208. // $results = [];
  209. //
  210. // foreach ($utilityData as $item) {
  211. // $subject = $this->simplifySubjectName($item['科目名称']);
  212. // $amount = $this->calculateAmount($item);
  213. //
  214. // if ($subject === self::SUBJECT_TOTAL_TO_APPORTION) {
  215. // // 待分摊总额按楼层分摊
  216. // $floorResults = $this->allocateByFloor($amount, $month);
  217. //
  218. // foreach ($floorResults as $floor => $machineAllocations) {
  219. // foreach ($machineAllocations as $machineCode => $machineAmount) {
  220. // if (!isset($results[$machineCode])) {
  221. // $results[$machineCode] = [];
  222. // }
  223. // $results[$machineCode][$subject] = $machineAmount;
  224. // }
  225. // }
  226. // } elseif (in_array($subject, self::ROLL_COATER_ONLY_SUBJECTS)) {
  227. // // 锅炉和热水锅炉只分配到卷凹机组
  228. // $rollCoaterResults = $this->allocateToRollCoaterOnly($amount, $month);
  229. //
  230. // foreach ($rollCoaterResults as $machineCode => $machineAmount) {
  231. // if (!isset($results[$machineCode])) {
  232. // $results[$machineCode] = [];
  233. // }
  234. // if (!isset($results[$machineCode][$subject])) {
  235. // $results[$machineCode][$subject] = $machineAmount;
  236. // }
  237. // }
  238. // } else {
  239. // // 其他科目按所有机台分摊
  240. // $globalResults = $this->allocateGlobally($amount, $month);
  241. // foreach ($globalResults as $machineCode => $machineAmount) {
  242. // if (!isset($results[$machineCode])) {
  243. // $results[$machineCode] = [];
  244. // }
  245. // $results[$machineCode][$subject] = $machineAmount;
  246. // }
  247. // }
  248. // }
  249. //
  250. // return $results;
  251. // }
  252. //
  253. // /**
  254. // * 获取水电气数据
  255. // */
  256. // protected function getUtilityData($month)
  257. // {
  258. // return Db::name('成本_各月水电气')
  259. // ->where('Sys_ny', $month)
  260. // ->whereLike('费用类型', '%分摊%')
  261. // ->select();
  262. // }
  263. //
  264. // /**
  265. // * 简化科目名称
  266. // */
  267. // protected function simplifySubjectName($subjectName)
  268. // {
  269. // $simplifiedMap = [
  270. // '废气处理(RTO)' => self::SUBJECT_WASTE_GAS,
  271. // '锅炉' => self::SUBJECT_BOILER,
  272. // '热水锅炉' => self::SUBJECT_HOT_WATER_BOILER,
  273. // '空压机' => self::SUBJECT_AIR_COMPRESSOR,
  274. // '真空鼓风机' => self::SUBJECT_VACUUM_BLOWER,
  275. // '中央空调' => self::SUBJECT_CENTRAL_AIR_CONDITIONER,
  276. // '待分摊总额' => self::SUBJECT_TOTAL_TO_APPORTION,
  277. // ];
  278. //
  279. // foreach ($simplifiedMap as $keyword => $simplified) {
  280. // if (strpos($subjectName, $keyword) !== false) {
  281. // return $simplified;
  282. // }
  283. // }
  284. //
  285. // return $subjectName;
  286. // }
  287. //
  288. // /**
  289. // * 计算金额
  290. // */
  291. // protected function calculateAmount($item)
  292. // {
  293. // $electricity = ($this->getSafeNumericValue($item['耗电量']) * $this->getSafeNumericValue($item['单位电价']));
  294. // $gas = ($this->getSafeNumericValue($item['耗气量']) * $this->getSafeNumericValue($item['单位气价']));
  295. //
  296. // return round($electricity + $gas, 2);
  297. // }
  298. //
  299. // /**
  300. // * 按楼层分摊
  301. // */
  302. // protected function allocateByFloor($totalAmount, $month)
  303. // {
  304. // $results = [];
  305. //
  306. // // 计算每个楼层的总机时
  307. // $floorTotalHours = [];
  308. // foreach ([1, 2] as $floor) {
  309. // $machines = $this->getMachinesByFloor($floor, $month);
  310. // $totalHours = 0;
  311. // foreach ($machines as $machine) {
  312. // $totalHours += $machine['占用机时'];
  313. // }
  314. // $floorTotalHours[$floor] = $totalHours;
  315. // }
  316. //
  317. // $allFloorsTotal = array_sum($floorTotalHours);
  318. // if ($allFloorsTotal <= 0) {
  319. // return $results;
  320. // }
  321. //
  322. // // 按楼层比例分摊
  323. // foreach ($floorTotalHours as $floor => $hours) {
  324. // $floorAmount = round($totalAmount * ($hours / $allFloorsTotal), 2);
  325. //
  326. // // 在楼层内按机台分摊
  327. // $machines = $this->getMachinesByFloor($floor, $month);
  328. // $floorResults = $this->allocateWithinGroup($floorAmount, $machines);
  329. //
  330. // $results[$floor] = $floorResults;
  331. // }
  332. //
  333. // return $results;
  334. // }
  335. //
  336. // /**
  337. // * 全局分摊(不分楼层)
  338. // */
  339. // protected function allocateGlobally($totalAmount, $month)
  340. // {
  341. // $machines = $this->getAllMachines($month);
  342. // return $this->allocateWithinGroup($totalAmount, $machines);
  343. // }
  344. //
  345. // /**
  346. // * 只分摊到卷凹机组
  347. // */
  348. // protected function allocateToRollCoaterOnly($totalAmount, $month)
  349. // {
  350. // $machines = $this->getRollCoaterMachines($month);
  351. // return $this->allocateWithinGroup($totalAmount, $machines);
  352. // }
  353. //
  354. // /**
  355. // * 在组内按机台运行时间分摊
  356. // */
  357. // protected function allocateWithinGroup($totalAmount, $machines)
  358. // {
  359. // $results = [];
  360. //
  361. // if (empty($machines)) {
  362. // return $results;
  363. // }
  364. //
  365. // $totalHours = 0;
  366. // $machineHours = [];
  367. //
  368. // foreach ($machines as $machine) {
  369. // $hours = floatval($machine['占用机时']);
  370. // $machineCode = $machine['机台编号'];
  371. //
  372. // if ($hours > 0) {
  373. // $totalHours += $hours;
  374. // if (!isset($machineHours[$machineCode])) {
  375. // $machineHours[$machineCode] = $hours;
  376. // } else {
  377. // $machineHours[$machineCode] += $hours;
  378. // }
  379. // }
  380. // }
  381. //
  382. // if ($totalHours <= 0) {
  383. // return $results;
  384. // }
  385. //
  386. // foreach ($machineHours as $machineCode => $hours) {
  387. // $results[$machineCode] = round($totalAmount * ($hours / $totalHours), 2);
  388. // }
  389. //
  390. // return $results;
  391. // }
  392. //
  393. // /**
  394. // * 格式化分摊结果
  395. // */
  396. // protected function formatApportionmentResults($results, $month, $sysId)
  397. // {
  398. // $formatted = [];
  399. // $now = date('Y-m-d H:i:s');
  400. //
  401. // foreach ($results as $machineCode => $subjects) {
  402. // foreach ($subjects as $subject => $amount) {
  403. // $formatted[] = [
  404. // 'Sys_ny' => $month,
  405. // '科目名称' => $subject,
  406. // '设备编号' => $machineCode,
  407. // '分摊系数' => 1,
  408. // '分摊金额' => $amount,
  409. // 'Sys_id' => $sysId,
  410. // 'Sys_rq' => $now,
  411. // ];
  412. // }
  413. // }
  414. //
  415. // return $formatted;
  416. // }
  417. //
  418. // /**
  419. // * 保存分摊系数
  420. // */
  421. // protected function saveApportionmentCoefficients($data, $month)
  422. // {
  423. // // 删除旧数据
  424. // Db::name('成本_各月分摊系数')->where('Sys_ny', $month)->delete();
  425. //
  426. // if (!empty($data)) {
  427. // $sql = Db::name('成本_各月分摊系数')->fetchSql(true)->insertAll($data);
  428. // \db()->query($sql);
  429. // }
  430. // }
  431. //
  432. // /**
  433. // * 分摊到工单(二次分配)
  434. // */
  435. // protected function allocateToWorkOrders($month)
  436. // {
  437. // // 获取所有机台的分摊系数(每机时费用)
  438. // $coefficients = $this->getMachineCoefficients($month);
  439. //
  440. // // 获取所有机台的工单
  441. // $workOrders = $this->getWorkOrdersByMonth($month);
  442. //
  443. // $updates = [];
  444. // foreach ($workOrders as $order) {
  445. // $machineCode = $order['机台编号'];
  446. //
  447. // if (!isset($coefficients[$machineCode])) {
  448. // continue;
  449. // }
  450. //
  451. // $machineCoeffs = $coefficients[$machineCode];
  452. // $hours = floatval($order['占用机时']);
  453. //
  454. // if ($hours <= 0) {
  455. // continue;
  456. // }
  457. //
  458. // // 计算各科目分摊金额
  459. // $updateData = [
  460. // '直接水电' => round($hours * 0.69, 2), // 直接水电费
  461. // ];
  462. //
  463. // foreach ($machineCoeffs as $subject => $rate) {
  464. // $subjectKey = ($subject === '待分摊总额') ? '分摊水电' : $subject;
  465. // $updateData[$subjectKey] = round($hours * $rate, 2);
  466. // }
  467. //
  468. // // 构建更新条件
  469. // $where = [
  470. // 'sys_ny' => $month,
  471. // 'sczl_gdbh' => $order['工单编号'],
  472. // 'sczl_yjno' => $order['印件号'],
  473. // 'sczl_gxh' => $order['工序号'],
  474. // 'sczl_jtbh' => $machineCode,
  475. // ];
  476. //
  477. // $updates[] = [
  478. // 'where' => $where,
  479. // 'data' => $updateData
  480. // ];
  481. // }
  482. //
  483. // // 批量更新
  484. // $this->batchUpdateWorkOrders($updates, $month);
  485. // }
  486. //
  487. // /**
  488. // * 获取机台分摊系数(每机时费用)
  489. // */
  490. // protected function getMachineCoefficients($month)
  491. // {
  492. // // 查询分摊系数和机台运行时间
  493. // $data = Db::name('成本_各月分摊系数')
  494. // ->alias('c')
  495. // ->join('成本v23_月度成本明细 d', 'd.sys_ny = c.Sys_ny AND d.sczl_jtbh = c.设备编号')
  496. // ->where('c.Sys_ny', $month)
  497. // ->field([
  498. // 'c.设备编号',
  499. // 'c.科目名称',
  500. // 'c.分摊金额',
  501. // 'SUM(d.占用机时)' => 'total_hours'
  502. // ])
  503. // ->group('c.设备编号, c.科目名称')
  504. // ->select();
  505. //
  506. // $coefficients = [];
  507. //
  508. // foreach ($data as $item) {
  509. // $machineCode = $item['设备编号'];
  510. // $subject = $item['科目名称'];
  511. // $amount = floatval($item['分摊金额']);
  512. // $hours = floatval($item['total_hours']);
  513. //
  514. // if ($hours > 0) {
  515. // $rate = round($amount / $hours, 4);
  516. //
  517. // if (!isset($coefficients[$machineCode])) {
  518. // $coefficients[$machineCode] = [];
  519. // }
  520. //
  521. // $coefficients[$machineCode][$subject] = $rate;
  522. // }
  523. // }
  524. //
  525. // return $coefficients;
  526. // }
  527. //
  528. // /**
  529. // * 获取所有工单
  530. // */
  531. // protected function getWorkOrdersByMonth($month)
  532. // {
  533. // return Db::name('成本v23_月度成本明细')
  534. // ->where('sys_ny', $month)
  535. // ->field([
  536. // 'sczl_gdbh' => '工单编号',
  537. // 'sczl_yjno' => '印件号',
  538. // 'sczl_gxh' => '工序号',
  539. // 'sczl_jtbh' => '机台编号',
  540. // '占用机时'
  541. // ])
  542. // ->select();
  543. // }
  544. //
  545. // /**
  546. // * 批量更新工单数据
  547. // */
  548. // protected function batchUpdateWorkOrders($updates, $month)
  549. // {
  550. // $db = Db::name('成本v23_月度成本明细');
  551. //
  552. // foreach ($updates as $update) {
  553. // $sql = $db->where($update['where'])->fetchSql(true)->update($update['data']);
  554. // $db->query($sql);
  555. // }
  556. // }
  557. //
  558. // /**
  559. // * 安全数值获取
  560. // */
  561. // protected function getSafeNumericValue($value)
  562. // {
  563. // if ($value === null || $value === '' || $value === false) {
  564. // return 0;
  565. // }
  566. // return floatval($value);
  567. // }
  568. // app/controller/UnifiedCostController.php
  569. /**
  570. * 执行成本计算
  571. * @ApiMethod POST
  572. * @param string month 年月
  573. * @param string sys_id 系统ID
  574. */
  575. public function calculate()
  576. {
  577. if (Request::instance()->isPost() == false) {
  578. $this->error('非法请求');
  579. }
  580. $params = Request::instance()->param();
  581. if (!isset($params['month']) || empty($params['month'])) {
  582. $this->error('月份参数错误');
  583. }
  584. $month = trim($params['month']);
  585. $sysId = $params['sys_id'] ?? '';
  586. // 检查是否有正在执行的任务
  587. $runningTask = Db::name('queue_tasks')
  588. ->where('task_type', 'cost_calculation')
  589. ->where('task_data', 'like', '%"month":"' . $month . '"%')
  590. ->where('status', 'in', ['pending', 'processing'])
  591. ->find();
  592. if ($runningTask) {
  593. $this->success('该月份的成本计算任务已在执行中,请勿重复提交');
  594. }
  595. // 检查工资计算是否在执行中(可选)
  596. $salaryRunning = Db::name('queue_tasks')
  597. ->where('task_type', 'salary_calculation')
  598. ->where('task_data', 'like', '%"date":"' . $month . '"%')
  599. ->where('status', 'in', ['pending', 'processing'])
  600. ->find();
  601. if ($salaryRunning) {
  602. $this->error('该月份的工资计算正在执行中,建议等待工资计算完成后再进行成本计算');
  603. }
  604. // 准备任务数据
  605. $taskData = [
  606. 'month' => $month,
  607. 'sys_id' => $sysId,
  608. 'user_id' => session('user_id') ?? 0,
  609. 'user_name' => session('user_name') ?? '系统',
  610. 'request_time' => date('Y-m-d H:i:s')
  611. ];
  612. // 先创建任务记录
  613. $taskId = Db::name('queue_tasks')->insertGetId([
  614. 'task_type' => 'cost_calculation',
  615. 'task_data' => json_encode($taskData, JSON_UNESCAPED_UNICODE),
  616. 'status' => 'pending',
  617. 'queue_name' => 'cost_calculation',
  618. 'create_time' => date('Y-m-d H:i:s')
  619. ]);
  620. // 添加到任务数据中
  621. $taskData['task_id'] = $taskId;
  622. // 提交到成本计算队列
  623. $jobHandlerClassName = 'app\job\CostCalculationJob';
  624. $queueName = 'cost_calculation';
  625. $jobId = Queue::push($jobHandlerClassName, $taskData, $queueName);
  626. if ($jobId) {
  627. // 更新任务记录
  628. Db::name('queue_tasks')
  629. ->where('id', $taskId)
  630. ->update([
  631. 'job_id' => $jobId,
  632. 'update_time' => date('Y-m-d H:i:s')
  633. ]);
  634. $this->success('成本计算任务已提交到队列,请稍后查看结果', null, [
  635. 'task_id' => $taskId,
  636. 'job_id' => $jobId,
  637. 'month' => $month,
  638. 'queue_name' => $queueName
  639. ]);
  640. } else {
  641. // 更新任务状态为失败
  642. Db::name('queue_tasks')
  643. ->where('id', $taskId)
  644. ->update([
  645. 'status' => 'failed',
  646. 'error' => '任务提交到队列失败',
  647. 'update_time' => date('Y-m-d H:i:s')
  648. ]);
  649. $this->error('成本计算任务提交失败');
  650. }
  651. }
  652. /**
  653. * 查询成本计算状态
  654. * @ApiMethod GET
  655. * @param string month 年月
  656. */
  657. public function status()
  658. {
  659. $month = Request::instance()->param('month');
  660. if (empty($month)) {
  661. $this->error('月份参数错误');
  662. }
  663. // 查询任务记录
  664. $task = Db::name('queue_tasks')
  665. ->where('task_type', 'cost_calculation')
  666. ->where('task_data', 'like', '%"month":"' . $month . '"%')
  667. ->order('id', 'desc')
  668. ->find();
  669. if ($task) {
  670. $result = json_decode($task['result'] ?? '{}', true);
  671. $taskData = json_decode($task['task_data'] ?? '{}', true);
  672. $response = [
  673. 'exists' => true,
  674. 'task_id' => $task['id'],
  675. 'month' => $month,
  676. 'status' => $task['status'],
  677. 'queue_name' => $task['queue_name'],
  678. 'job_id' => $task['job_id'],
  679. 'start_time' => $task['start_time'],
  680. 'end_time' => $task['end_time'],
  681. 'retry_count' => $task['retry_count'],
  682. 'result' => $result,
  683. 'error' => $task['error'] ?? '',
  684. 'create_time' => $task['create_time'],
  685. 'user_info' => [
  686. 'user_id' => $taskData['user_id'] ?? 0,
  687. 'user_name' => $taskData['user_name'] ?? ''
  688. ]
  689. ];
  690. } else {
  691. $response = ['exists' => false, 'month' => $month];
  692. }
  693. $this->success('查询成功', null, $response);
  694. }
  695. /**
  696. * 获取成本计算任务列表
  697. * @ApiMethod GET
  698. */
  699. public function list()
  700. {
  701. $page = Request::instance()->param('page', 1);
  702. $limit = Request::instance()->param('limit', 20);
  703. $month = Request::instance()->param('month');
  704. $status = Request::instance()->param('status');
  705. $query = Db::name('queue_tasks')
  706. ->where('task_type', 'cost_calculation');
  707. if ($month) {
  708. $query->where('task_data', 'like', '%"month":"' . $month . '"%');
  709. }
  710. if ($status) {
  711. $query->where('status', $status);
  712. }
  713. $total = $query->count();
  714. $list = $query->order('id', 'desc')
  715. ->page($page, $limit)
  716. ->select();
  717. // 解析任务数据
  718. foreach ($list as &$item) {
  719. $taskData = json_decode($item['task_data'] ?? '{}', true);
  720. $item['month'] = $taskData['month'] ?? '';
  721. $item['user_name'] = $taskData['user_name'] ?? '';
  722. $item['request_time'] = $taskData['request_time'] ?? '';
  723. if (!empty($item['result'])) {
  724. $item['result_data'] = json_decode($item['result'], true);
  725. }
  726. }
  727. $this->success('查询成功', null, [
  728. 'list' => $list,
  729. 'total' => $total,
  730. 'page' => $page,
  731. 'pages' => ceil($total / $limit)
  732. ]);
  733. }
  734. /**
  735. * 手动重试失败的任务
  736. * @ApiMethod POST
  737. * @param int task_id 任务ID
  738. */
  739. public function retry()
  740. {
  741. $taskId = Request::instance()->param('task_id');
  742. if (empty($taskId)) {
  743. $this->error('任务ID不能为空');
  744. }
  745. $task = Db::name('queue_tasks')
  746. ->where('id', $taskId)
  747. ->where('task_type', 'cost_calculation')
  748. ->where('status', 'failed')
  749. ->find();
  750. if (!$task) {
  751. $this->error('任务不存在或无法重试');
  752. }
  753. // 解析原任务数据
  754. $taskData = json_decode($task['task_data'], true);
  755. $taskData['task_id'] = $taskId;
  756. $taskData['retry_time'] = date('Y-m-d H:i:s');
  757. // 更新原任务状态
  758. Db::name('queue_tasks')
  759. ->where('id', $taskId)
  760. ->update([
  761. 'status' => 'retrying',
  762. 'update_time' => date('Y-m-d H:i:s')
  763. ]);
  764. // 提交到队列
  765. $jobHandlerClassName = 'app\job\CostCalculationJob';
  766. $queueName = 'cost_calculation';
  767. $jobId = Queue::push($jobHandlerClassName, $taskData, $queueName);
  768. if ($jobId) {
  769. $this->success('任务已重新提交到队列', null, [
  770. 'task_id' => $taskId,
  771. 'new_job_id' => $jobId,
  772. 'queue_name' => $queueName
  773. ]);
  774. } else {
  775. $this->error('任务重试失败');
  776. }
  777. }
  778. }