BIService.php 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058
  1. <?php
  2. namespace App\Service;
  3. use App\Model\DailyDwOrder;
  4. use App\Model\DailyDwOrderDetails;
  5. use App\Model\DailyPwOrder;
  6. use App\Model\DailyPwOrderDetails;
  7. use App\Model\Employee;
  8. use App\Model\ExpenseClaims;
  9. use App\Model\ExpenseClaimsDetails;
  10. use App\Model\Fee;
  11. use App\Model\Item;
  12. use App\Model\ItemNode;
  13. use App\Model\ItemNodeMission;
  14. use App\Model\ItemNodeMissionContent;
  15. use App\Model\MonthlyDdOrder;
  16. use App\Model\MonthlyDdOrderDetails;
  17. use App\Model\MonthlyPsOrder;
  18. use App\Model\MonthlyPsOrderDetails;
  19. use App\Model\PLeaveOverOrder;
  20. use App\Model\PLeaveOverOrderDetails;
  21. use App\Model\WorkFlowInstances;
  22. use App\Model\WorkFlowInstancesNodes;
  23. use Carbon\Carbon;
  24. use Illuminate\Support\Facades\DB;
  25. class BIService extends Service
  26. {
  27. private function commonRule($data)
  28. {
  29. if (isset($data['time']) && !empty($data['time'])) {
  30. $start = $this->changeDateToDate($data['time'][0]);
  31. $end = $this->changeDateToDate($data['time'][1], true);
  32. $data['month_start'] = date("Y-m-d", $start);
  33. $data['month_end'] = date("Y-m-d", $end);
  34. }
  35. if (!isset($data['month_start'])) $month_start = date('Y-01-01', strtotime('-1 year'));
  36. else $month_start = date('Y-m-01', strtotime($data['month_start']));
  37. if (!isset($data['month_end'])) $month_end = date('Y-01-01', strtotime('+1 year', strtotime($month_start)));
  38. else {
  39. $start_year = date('Y', strtotime($month_start));
  40. $end_year = date('Y', strtotime($data['month_end']));
  41. if ($start_year != $end_year) return [false, "查询不得跨年!", ""];
  42. $month_end = date('Y-m-01', strtotime($data['month_end'] . ' +1 month'));
  43. }
  44. return [true, strtotime($month_start), strtotime($month_end)];
  45. }
  46. public function homePageData($data, $user)
  47. {
  48. // mark 存在需求冲突 如果补录的项目 比如 2015年 那么一开始展示的年份就是2015 和2026年6月10号需求冲突
  49. // 先注释原逻辑 换新
  50. // $model = Item::TopClear($user,$data);
  51. // $start_time = $model->where('del_time',0)
  52. // ->orderby('id', 'desc')->value("start_time");
  53. if(isset($data['year'])){
  54. $start_time = strtotime($data['year']. '-01-01');
  55. }else{
  56. $start_time = time();
  57. }
  58. $year = date('Y-01-01', $start_time);
  59. $data['month_start'] = $year;
  60. list($status, $month_start, $month_end) = $this->commonRule($data);
  61. if (!$status) return [false, $month_start];
  62. //当年项目总数
  63. $model = Item::TopClear($user,$data);
  64. $intersectCount = $model->where('del_time',0)
  65. ->where('start_time', '<=', $month_end) // 项目开始时间 <= 区间结束时间
  66. ->where('end_time', '>=', $month_start) // 项目结束时间 >= 区间开始时间
  67. ->count();
  68. //人员费用
  69. list($total_man, $salary_map) = $this->getEmployeeSalary($month_start, $month_end, $data, $user);
  70. //折旧费用
  71. list($total_zj, $zj_map) = $this->getDeviceSalary($month_start, $month_end, $data, $user);
  72. //费用报销
  73. list($total_other, $other_map, $return_fee) = $this->getFeeItemSalary($month_start, $month_end, $data, $user);
  74. //研发费用总额
  75. $total = bcadd(bcadd($total_man, $total_zj,3), $total_other,3);
  76. //研发费用结构
  77. $rd = [
  78. 0 => [
  79. 'title' => '人工费用',
  80. 'rate' => $total == 0 ? 0 : bcmul(bcdiv($total_man,$total,4),100,2),
  81. 'rate_unit' => '%',
  82. 'total' => $total_man,
  83. 'total_unit' => '¥',
  84. ],
  85. 1 => [
  86. 'title' => '折旧费用',
  87. 'rate' => $total == 0 ? 0 :bcmul(bcdiv($total_zj,$total,4),100,2),
  88. 'rate_unit' => '%',
  89. 'total' => $total_zj,
  90. 'total_unit' => '¥',
  91. ],
  92. 2 => [
  93. 'title' => '费用报销',
  94. 'rate' => $total == 0 ? 0 :bcmul(bcdiv($total_other,$total,4),100,2),
  95. 'rate_unit' => '%',
  96. 'total' => $total_other,
  97. 'total_unit' => '¥',
  98. ],
  99. ];
  100. //研发费用趋势
  101. $rd_trend = $this->makeTrend($month_start, $salary_map, $zj_map, $other_map);
  102. //研发费用报销占比
  103. $rd_rate = $return_fee;
  104. //加班 请假 总时长
  105. list($over_time, $leave_time) = $this->overLeave($month_start, $month_end, $data, $user);
  106. // 项目人员工时汇总
  107. $man_work = $this->manWork($month_start, $month_end, $data, $user);
  108. //加计扣除金额
  109. $statisticService = new StatisticService();
  110. $attendance = $statisticService->employeeAttendanceMonthStatistic(["year"=>$year, "s" => "/api/employeeAttendanceMonthStatistic"],$user);
  111. $discount = 0;
  112. foreach ($attendance[1]['list'] as $v){
  113. $discount += $v['jj_total_amount']*100;
  114. }
  115. return [true, [
  116. 'rd_total' => $total,
  117. 'rd_unit' => '¥',
  118. 'rd_title' => '研发费用总额', //-----
  119. 'discount_total' => round($discount/100,2),
  120. 'discount_unit' => '¥',
  121. 'discount_title' => '加计扣除金额', //------
  122. 'over_time' => $over_time, // 加班
  123. 'leave_time' => $leave_time, // 请假
  124. 'rd' => $rd, //研发费用结构
  125. 'rd_trend' => $rd_trend, //研发费用趋势
  126. 'rd_rate' => $rd_rate, // 研发费用报销占比
  127. 'man_work' => $man_work, //项目人员工时汇总
  128. 'year' => date('Y',strtotime($year)),
  129. 'item_num' => $intersectCount,
  130. 'start_year' => date('Y',time())
  131. ]];
  132. }
  133. private function getEmployeeSalary($month_start, $month_end, $data, $user)
  134. {
  135. $monthly_ps_order = MonthlyPsOrder::Clear($user, $data)
  136. ->where('del_time', 0)
  137. ->where("month", ">=", $month_start)
  138. ->where("month", "<", $month_end)
  139. ->pluck('month','id')
  140. ->toArray();
  141. $monthly_ps_order_ids = array_keys($monthly_ps_order);
  142. $month_employee_salary = MonthlyPsOrderDetails::whereIn('main_id', $monthly_ps_order_ids)
  143. ->select("base_salary","performance_salary","other","bonus", "main_id")
  144. ->get()
  145. ->toArray();
  146. $total = 0;
  147. $salary_map = [];
  148. foreach ($month_employee_salary as $value) {
  149. $currentAmount = bcadd($value['base_salary'], $value['performance_salary'],3);
  150. $currentAmount = bcadd($currentAmount, $value['other'],3);
  151. $currentAmount = bcadd($currentAmount, $value['bonus'],3);
  152. if(isset($monthly_ps_order[$value['main_id']])){
  153. $month = $monthly_ps_order[$value['main_id']];
  154. if(isset($salary_map[$month])){
  155. $salary_map[$month] = bcadd($salary_map[$month], $currentAmount,3);
  156. }else{
  157. $salary_map[$month] = $currentAmount;
  158. }
  159. }
  160. $total = bcadd($total, $currentAmount,3);
  161. }
  162. return [$total, $salary_map];
  163. }
  164. private function getDeviceSalary($month_start, $month_end, $data, $user)
  165. {
  166. $monthly_dd_order= MonthlyDdOrder::Clear($user, $data)
  167. ->where('del_time', 0)
  168. ->where("month", ">=", $month_start)
  169. ->where("month", "<", $month_end)
  170. ->pluck('month','id')
  171. ->toArray();
  172. $monthly_dd_order_ids = array_keys($monthly_dd_order);
  173. $month_device_salary = MonthlyDdOrderDetails::whereIn('main_id', $monthly_dd_order_ids)
  174. ->select("depreciation_amount as amount", "main_id")
  175. ->get()
  176. ->toArray();
  177. $total = 0;
  178. $amount_map = [];
  179. foreach ($month_device_salary as $value) {
  180. $currentAmount = (string)$value['amount'];
  181. if(isset($monthly_dd_order[$value['main_id']])){
  182. $month = $monthly_dd_order[$value['main_id']];
  183. if(isset($amount_map[$month])){
  184. $amount_map[$month] = bcadd($amount_map[$month], $currentAmount,3);
  185. }else{
  186. $amount_map[$month] = $currentAmount;
  187. }
  188. }
  189. $total = bcadd($total, $currentAmount,3);
  190. }
  191. return [$total, $amount_map];
  192. }
  193. private function getFeeItemSalary($month_start, $month_end, $data, $user)
  194. {
  195. $e_order = ExpenseClaims::Clear($user, $data)
  196. ->where('del_time', 0)
  197. ->where("month", ">=", $month_start)
  198. ->where("month", "<", $month_end)
  199. ->pluck('month','id')
  200. ->toArray();
  201. $e_order_ids = array_keys($e_order);
  202. $e_order_detail = ExpenseClaimsDetails::whereIn('expense_claims_id', $e_order_ids)
  203. ->select("amount", "expense_claims_id as main_id", "fee_id")
  204. ->get()
  205. ->toArray();
  206. $fee = Fee::TopClear($user, $data);
  207. $fee_map = $fee->whereIn('id', array_unique(array_column($e_order_detail,'fee_id')))->pluck('title','id')->toArray();
  208. $total = 0;
  209. $amount_map = [];
  210. $fee_amount_map = [];
  211. foreach ($e_order_detail as $value) {
  212. $currentAmount = (string)$value['amount'];
  213. if(isset($e_order[$value['main_id']])){
  214. $month = $e_order[$value['main_id']];
  215. if(isset($amount_map[$month])){
  216. $amount_map[$month] = bcadd($amount_map[$month], $currentAmount,3);
  217. }else{
  218. $amount_map[$month] = $currentAmount;
  219. }
  220. }
  221. $fee_tmp = $fee_map[$value['fee_id']] ?? "";
  222. if(! empty($fee_tmp)){
  223. if(isset($fee_amount_map[$fee_tmp])){
  224. $fee_amount_map[$fee_tmp] = bcadd($fee_amount_map[$fee_tmp], $currentAmount,3);
  225. }else{
  226. $fee_amount_map[$fee_tmp] = $currentAmount;
  227. }
  228. }
  229. $total = bcadd($total, $currentAmount,3);
  230. }
  231. $return_fee = [];
  232. foreach ($fee_amount_map as $key => $value){
  233. $amount = (string)$value;
  234. $return_fee[] = [
  235. "title" => $key,
  236. "total" => $amount,
  237. "total_unit" => "元",
  238. "rate" => bcmul(bcdiv($amount,$total,4),100,2),
  239. "rate_unit" => "%"
  240. ];
  241. }unset($fee_amount_map);
  242. return [$total, $amount_map, $return_fee];
  243. }
  244. private function makeTrend($month_start, $salary_map, $zj_map, $other_map)
  245. {
  246. $trend = [];
  247. for ($i = 0; $i < 12; $i++) {
  248. // 计算每个月第一天的时间戳作为 Key
  249. $timestamp = strtotime("+$i month", $month_start);
  250. $monthName = ($i + 1) . '月';
  251. $trend[$timestamp] = [
  252. 'month' => $monthName,
  253. // 'salary' => "0.000",
  254. // 'zj' => "0.000",
  255. // 'other' => "0.000",
  256. 'total' => "0.000",
  257. ];
  258. }
  259. // 2. 将传入的 Map 数据填充进去
  260. foreach ($trend as $timestamp => &$item) {
  261. // 使用 ?? "0" 容错,并强制转为 string 保证 bcadd 精度
  262. $s = $salary_map[$timestamp] ?? "0";
  263. $z = $zj_map[$timestamp] ?? "0";
  264. $o = $other_map[$timestamp] ?? "0";
  265. // $item['salary'] = bcadd($s, "0", 3);
  266. // $item['zj'] = bcadd($z, "0", 3);
  267. // $item['other'] = bcadd($o, "0", 3);
  268. // 计算该月总计
  269. $item['total'] = bcadd(bcadd($s, $z, 3), $o, 3);
  270. }
  271. return array_values($trend);
  272. }
  273. private function overLeave($month_start, $month_end, $data, $user)
  274. {
  275. $id = PLeaveOverOrder::Clear($user, $data)
  276. ->where('del_time', 0)
  277. ->where("order_time", ">=", $month_start)
  278. ->where("order_time", "<", $month_end)
  279. ->pluck('id')
  280. ->toArray();
  281. $p = PLeaveOverOrderDetails::whereIn('main_id', $id)
  282. ->select("type", "total_min")
  283. ->get()
  284. ->toArray();
  285. $total = $total_1 = 0;
  286. foreach ($p as $value) {
  287. $total_min = (string)$value['total_min'];
  288. if($value['type'] == PLeaveOverOrderDetails::TYPE_ONE){
  289. $total = bcadd($total, $total_min,2);
  290. }else{
  291. $total_1 = bcadd($total_1, $total_min,2);
  292. }
  293. }
  294. // 最终返回或输出前转换
  295. $total_hours = bcdiv($total, 60, 2);
  296. $total_1_hours = bcdiv($total_1, 60, 2);
  297. return [$total_1_hours, $total_hours];
  298. }
  299. private function manWork($month_start, $month_end, $data, $user)
  300. {
  301. $daily_pw = DailyPwOrder::Clear($user, $data)
  302. ->where('del_time', 0)
  303. ->where("order_time", ">=", $month_start)
  304. ->where("order_time", "<", $month_end)
  305. ->select('id','item_id')
  306. ->get()->toArray();
  307. $item_map = Item::whereIn('id',array_unique(array_column($daily_pw,'item_id')))
  308. ->pluck('title','id')
  309. ->toArray();
  310. $p = DailyPwOrderDetails::whereIn('main_id', array_column($daily_pw,'id'))
  311. ->select("item_id", "total_work_min")
  312. ->get()
  313. ->toArray();
  314. $total = 0;
  315. $map = [];
  316. foreach ($p as $value) {
  317. $total_min = (string)$value['total_work_min'];
  318. if(isset($map[$value['item_id']])){
  319. $map[$value['item_id']] = bcadd($map[$value['item_id']], $total_min,2);
  320. }else{
  321. $map[$value['item_id']] = $total_min;
  322. }
  323. $total = bcadd($total, $total_min,2);
  324. }
  325. $return = [];
  326. foreach ($map as $key => $value){
  327. $rate = bcmul(bcdiv($value, $total,4),100,2);
  328. $total_hours = bcdiv($value, 60, 2);
  329. $return[] = [
  330. 'title' => $item_map[$key] ?? "",
  331. 'total_work_hour' => $total_hours,
  332. 'total_work_hour_unit' => "小时",
  333. "rate" => $rate,
  334. "rate_unit" => "%",
  335. ];
  336. }
  337. return $return;
  338. }
  339. public function cockpit($data, $user)
  340. {
  341. // 1. 声明时区对象
  342. $utcZone = new \DateTimeZone('UTC');
  343. $localZone = new \DateTimeZone('Asia/Shanghai'); // 你的数据库/服务器本地时区(北京时间)
  344. if (!empty($data['time'][0]) && !empty($data['time'][1])) {
  345. // 【A. 满足 employeeSalarySummary 的 UTC 干净时间】
  346. // 提取前端 ISO 串中的纯日期,强制转为 UTC 视角的开始和结束
  347. $rawStartUtc = new \DateTime($data['time'][0], $utcZone);
  348. $rawEndUtc = new \DateTime($data['time'][1], $utcZone);
  349. $startDateTimeUtc = new \DateTime($rawStartUtc->format('Y-m-d') . ' 00:00:00', $utcZone);
  350. $endDateTimeUtc = new \DateTime($rawEndUtc->format('Y-m-d') . ' 23:59:59', $utcZone);
  351. // 【B. 满足 getManWork 的本地 00点和23点 时间戳】
  352. $startTimestampLocal = (new \DateTime($rawStartUtc->format('Y-m-d') . ' 00:00:00', $localZone))->getTimestamp();
  353. $endTimestampLocal = (new \DateTime($rawEndUtc->format('Y-m-d') . ' 23:59:59', $localZone))->getTimestamp();
  354. } else {
  355. // 【没传参时的默认兜底逻辑】
  356. // UTC 视角的当月起止
  357. $startDateTimeUtc = new \DateTime('first day of this month 00:00:00', $utcZone);
  358. $endDateTimeUtc = new \DateTime('last day of this month 23:59:59', $utcZone);
  359. // 本地视角的当月起止时间戳
  360. $startTimestampLocal = (new \DateTime('first day of this month 00:00:00', $localZone))->getTimestamp();
  361. $endTimestampLocal = (new \DateTime('last day of this month 23:59:59', $localZone))->getTimestamp();
  362. }
  363. $data['start_time'] = $startTimestampLocal;
  364. $data['end_time'] = $endTimestampLocal;
  365. $array = $this->getManWork($data, $user);
  366. // 当月设备工时信息
  367. $array2 = $this->getDeviceWork($data, $user);
  368. $array = array_merge_recursive($array, $array2);
  369. $array3 = $this->getDeviceOldWork($data, $user);
  370. $array = array_merge_recursive($array, $array3);
  371. $array3 = $this->getFeeOrder($data, $user);
  372. $array = array_merge_recursive($array, $array3);
  373. $passTime = [
  374. $startDateTimeUtc->format('Y-m-d'),
  375. $endDateTimeUtc->format('Y-m-d')
  376. ];
  377. list($status, $return) = (new StatisticService())->employeeSalarySummary(['time' => $passTime], $user);
  378. if(! $status) return [false, $return];
  379. $total = array_sum(array_column($return,'money'));
  380. $array['rd_total_money'] = $total;
  381. return [true, $array];
  382. }
  383. private function getManWork($data, $user){
  384. $model = DailyPwOrder::Clear($user, $data);
  385. $id = $model->where('del_time', 0)
  386. ->where('order_time', '>=', $data['start_time'])
  387. ->where('order_time', '<=', $data['end_time'])
  388. ->pluck('id')
  389. ->toArray();
  390. $details = DailyPwOrderDetails::where('del_time', 0)
  391. ->whereIn('main_id', $id) // 💡 避坑提示:原代码如果是数组 $id,这里建议用 whereIn
  392. ->select('employee_id', 'total_work_min')
  393. ->get()->toArray();
  394. // 1. 计算当月总工时(将二维数组中所有 'total_work_min' 字段求和)
  395. $totalWorkMin = array_sum(array_column($details, 'total_work_min'));
  396. // 2. 如果你需要把分钟数转换为“小时”,可以除以 60(保留两位小数,根据业务需要决定是否转换)
  397. $totalWorkHours = round($totalWorkMin / 60, 2);
  398. // 3. 计算参与人员总数(先提取所有员工ID,再通过 array_unique 去重,最后 count 计算数量)
  399. $employeeIds = array_column($details, 'employee_id');
  400. $uniqueEmployeeIds = array_unique($employeeIds);
  401. $totalEmployees = count($uniqueEmployeeIds);
  402. return [
  403. 'man_total_work_hours' => $totalWorkHours, // 当月总工时
  404. 'man_total_employees' => $totalEmployees, // 参与人员总数
  405. ];
  406. }
  407. private function getDeviceWork($data, $user){
  408. $model = DailyDwOrder::Clear($user, $data);
  409. $id = $model->where('del_time', 0)
  410. ->where('order_time', '>=', $data['start_time'])
  411. ->where('order_time', '<=', $data['end_time'])
  412. ->pluck('id')
  413. ->toArray();
  414. $details = DailyDwOrderDetails::where('del_time', 0)
  415. ->whereIn('main_id', $id) // 💡 避坑提示:原代码如果是数组 $id,这里建议用 whereIn
  416. ->select('device_id', 'total_work_min')
  417. ->get()->toArray();
  418. // 1. 计算当月总工时(将二维数组中所有 'total_work_min' 字段求和)
  419. $totalWorkMin = array_sum(array_column($details, 'total_work_min'));
  420. // 2. 如果你需要把分钟数转换为“小时”,可以除以 60(保留两位小数,根据业务需要决定是否转换)
  421. $totalWorkHours = round($totalWorkMin / 60, 2);
  422. $employeeIds = array_column($details, 'device_id');
  423. $uniqueEmployeeIds = array_unique($employeeIds);
  424. $totalEmployees = count($uniqueEmployeeIds);
  425. return [
  426. 'device_total_work_hours' => $totalWorkHours, // 当月总工时
  427. 'device_total_num' => $totalEmployees, // 参与人员总数
  428. ];
  429. }
  430. private function getDeviceOldWork($data, $user){
  431. $model = MonthlyDdOrder::Clear($user, $data);
  432. $id = $model->where('del_time', 0)
  433. ->where('month', '>=', $data['start_time'])
  434. ->where('month', '<=', $data['end_time'])
  435. ->pluck('id')
  436. ->toArray();
  437. $details = MonthlyDdOrderDetails::where('del_time', 0)
  438. ->whereIn('main_id', $id)
  439. ->select('device_id', 'depreciation_amount')
  440. ->get()->toArray();
  441. $totalWorkMin = array_sum(array_column($details, 'depreciation_amount'));
  442. return [
  443. 'device_total_money' => $totalWorkMin,
  444. ];
  445. }
  446. private function getFeeOrder($data, $user){
  447. $model = ExpenseClaims::Clear($user, $data);
  448. $id = $model->where('del_time', 0)
  449. ->where('month', '>=', $data['start_time'])
  450. ->where('month', '<=', $data['end_time'])
  451. ->pluck('id')
  452. ->toArray();
  453. $details = ExpenseClaimsDetails::where('del_time', 0)
  454. ->whereIn('expense_claims_id', $id)
  455. ->select('item_id', 'amount')
  456. ->get()->toArray();
  457. $totalWorkMin = array_sum(array_column($details, 'amount'));
  458. $employeeIds = array_column($details, 'item_id');
  459. $uniqueEmployeeIds = array_unique($employeeIds);
  460. $totalEmployees = count($uniqueEmployeeIds);
  461. return [
  462. 'fee_total_money' => $totalWorkMin,
  463. 'fee_total_num' => $totalEmployees,
  464. ];
  465. }
  466. public function homePageItemData($data, $user){
  467. $startOfMonth = Carbon::now()->startOfMonth()->timestamp;
  468. $endOfMonth = Carbon::now()->endOfMonth()->timestamp;
  469. $data['start_now_month'] = $startOfMonth;
  470. $data['end_now_month'] = $endOfMonth;
  471. // 进行中项目数量
  472. $item_count = $this->ItemAboutSearch($data, $user);
  473. // 进行中节点数量
  474. $item_node_count = $this->ItemNodeAboutSearch($data, $user);
  475. // 待审批数量(任务)
  476. $item_node_mission_wait_count = $this->ItemNodeMissionWaitAboutSearch($data, $user);
  477. // 超期任务数量 本月已完成任务数量 符合条件的当月任务
  478. list($num1, $num2, $mission) = $this->ItemNodeMissionAboutSearch($data, $user);
  479. // 🌟人效分析 人员工时对比(这里传入了当月筛选出的任务集)
  480. $man_about = $this->ItemNodeMissionAboutManSearch($data, $user, $mission);
  481. // 近六个月工时比较
  482. $last_six_about = $this->ItemNodeMissionAboutLastSixMonthSearch($data, $user);
  483. // 项目工时与人效排名
  484. $item_man_about = $this->ItemManSearch($data, $user);
  485. return [true, [
  486. 'item_count' => $item_count,
  487. 'item_node_count' => $item_node_count,
  488. 'item_node_mission_wait_count' => $item_node_mission_wait_count,
  489. 'cq_num' => $num1,
  490. 'wc_num' => $num2,
  491. 'rx' => $man_about['sub'],
  492. 'employee_work_list' => $man_about['employee_work_list'],
  493. 'last_six_month' => $last_six_about,
  494. 'item_man_abount' => $item_man_about
  495. ]];
  496. }
  497. private function ItemAboutSearch($data, $user){
  498. $model = Item::TopAndEmployeeClear($user, $data);
  499. return $model->where('del_time', 0)
  500. ->where('state', Item::TYPE_TWO)
  501. ->where('start_time', '<=', $data['end_now_month'])
  502. ->where('end_time', '>=', $data['start_now_month'])
  503. ->count();
  504. }
  505. private function ItemNodeAboutSearch($data, $user){
  506. $model = ItemNode::TopAndEmployeeClear($user, $data);
  507. return $model->where('del_time', 0)
  508. ->where('state', ItemNode::TYPE_TWO)
  509. ->where('start_time', '<=', $data['end_now_month'])
  510. ->where('end_time', '>=', $data['start_now_month'])
  511. // ->where('start_time', '<=', $data['start_now_month']) // 开始时间早于或等于当月结束
  512. // ->where('end_time', '>=', $data['end_now_month']) // 结束时间晚于或等于当月开始
  513. ->count();
  514. }
  515. private function ItemNodeMissionWaitAboutSearch($data, $user){
  516. // return WorkFlowInstances::where('del_time', 0)
  517. // ->where('document_type', 'item_node_mission')
  518. // ->where('crt_time', '>=', $data['start_now_month'])
  519. // ->where('crt_time', '<=', $data['end_now_month'])
  520. // ->where('status', WorkFlowInstances::status_two)
  521. // ->where('top_depart_id', $user['top_depart_id'])
  522. // ->count();
  523. return WorkFlowInstancesNodes::from('workflow_instance_nodes as nodes') // 给主表起个别名
  524. ->join('workflow_instances as instances', 'nodes.instance_id', '=', 'instances.id') // 直接连表,注意:如果你的外键不是 instance_id,请在这里修改
  525. ->where('nodes.del_time', 0)
  526. ->where('nodes.top_depart_id', $user['top_depart_id'])
  527. ->where('nodes.status', 1)
  528. ->whereRaw('JSON_CONTAINS(nodes.assignees, JSON_OBJECT("id", ?))', [$user['id']])
  529. ->whereRaw('NOT JSON_CONTAINS(nodes.assignees, JSON_OBJECT("id", ?, "is_pass", 1))', [$user['id']])
  530. ->where('instances.del_time', 0)
  531. ->where('instances.document_type', 'item_node_mission')
  532. ->where('instances.crt_time', '>=', $data['start_now_month'])
  533. ->where('instances.crt_time', '<=', $data['end_now_month'])
  534. ->where('instances.status', 'pending')
  535. ->count();
  536. }
  537. private function ItemNodeMissionAboutSearch($data, $user){
  538. $model = ItemNodeMission::TopAndEmployeeClear($user,$data);
  539. // 🌟 核心确保:不仅卡死当月区间,同时 select 出分摊所必需的 'start_time' 和 'end_time'
  540. $list = $model->where('del_time',0)
  541. ->where('start_time', '<=', $data['end_now_month'])
  542. ->where('end_time', '>=', $data['start_now_month'])
  543. ->select('state', 'end_time', 'id', 'due_work_hour', 'start_time')
  544. ->get()->toArray();
  545. $overdueCount = 0; // 已超期数量
  546. $completedCount = 0; // 已完成数量
  547. $now = time(); // 获取当前时间戳
  548. foreach ($list as $item) {
  549. if ($item['state'] == ItemNodeMission::TYPE_THREE) {
  550. $completedCount++;
  551. }
  552. if ($item['state'] == ItemNodeMission::TYPE_MINUS_TWO || $item['end_time'] < $now) {
  553. $overdueCount++;
  554. }
  555. }
  556. return [
  557. $overdueCount,
  558. $completedCount,
  559. $list
  560. ];
  561. }
  562. private function ItemNodeMissionAboutManSearch($data, $user, $mission){
  563. $mission_id = array_column($mission, 'id');
  564. // 🌟 严格限制:只查询当前月份内、且属于这批任务的实际工时单据 (防止把历史月份工时带入当前人员统计)
  565. $day_work_list = ItemNodeMissionContent::where('del_time',0)
  566. ->whereIn('item_node_mission_id', $mission_id)
  567. ->where('order_time', '>=', $data['start_now_month'])
  568. ->where('order_time', '<=', $data['end_now_month'])
  569. ->select('data_id as employee_id', 'total_work_min', 'item_id', 'item_node_mission_id')
  570. ->get()->toArray();
  571. $emp_map = Employee::whereIn('id', array_unique(array_column($day_work_list,'employee_id')))
  572. ->pluck('title','id')
  573. ->toArray();
  574. $total_work_minute = 0;
  575. $employee_keys = [];
  576. // 规整当前筛选月份的时间戳边界
  577. $start_timestamp = is_numeric($data['start_now_month']) ? (int)$data['start_now_month'] : strtotime($data['start_now_month']);
  578. $end_timestamp = is_numeric($data['end_now_month']) ? (int)$data['end_now_month'] : strtotime($data['end_now_month']);
  579. // 1. 预先计算好每个任务在【查询当月】的生命周期交集与分摊预计工时
  580. $yj_map = [];
  581. foreach ($mission as $m) {
  582. $m_id = $m['id'];
  583. // 🌟 强力兼容:不论数据库存的是 Timestamp 还是 Y-m-d 字符串,一律规整为标准时间戳计算天数
  584. $m_start = is_numeric($m['start_time']) ? (int)$m['start_time'] : strtotime($m['start_time']);
  585. $m_end = is_numeric($m['end_time']) ? (int)$m['end_time'] : strtotime($m['end_time']);
  586. if ($m_start && $m_end && $m_start <= $end_timestamp && $m_end >= $start_timestamp) {
  587. // 计算任务总天数
  588. $total_days = ceil(($m_end - $m_start) / 86400);
  589. if ($total_days <= 0) $total_days = 1;
  590. // 计算当月重叠区间
  591. $overlap_start = max($m_start, $start_timestamp);
  592. $overlap_end = min($m_end, $end_timestamp);
  593. $overlap_days = ceil(($overlap_end - $overlap_start) / 86400);
  594. if ($overlap_days < 0) $overlap_days = 0;
  595. // 算出该任务在当月应该分摊到的工时
  596. $yj_map[$m_id] = ($m['due_work_hour'] / $total_days) * $overlap_days;
  597. } else {
  598. $yj_map[$m_id] = 0;
  599. }
  600. }
  601. $employee_array = [];
  602. // 2. 第一步:纯粹统计当月实际工时,并将任务 ID 归并到人头上
  603. foreach ($day_work_list as $work) {
  604. $emp_id = $work['employee_id'];
  605. if (empty($emp_id)) continue;
  606. $total_work_minute += $work['total_work_min'];
  607. $employee_keys[$emp_id] = true;
  608. if (isset($employee_array[$emp_id])) {
  609. $employee_array[$emp_id]['actual_work'] += $work['total_work_min'];
  610. if (!in_array($work['item_node_mission_id'], $employee_array[$emp_id]['mission_id'])) {
  611. $employee_array[$emp_id]['mission_id'][] = $work['item_node_mission_id'];
  612. }
  613. } else {
  614. $employee_array[$emp_id] = [
  615. 'employee_id' => $emp_id,
  616. 'employee_title' => $emp_map[$emp_id] ?? '',
  617. 'actual_work' => $work['total_work_min'],
  618. 'mission_id' => [$work['item_node_mission_id']],
  619. 'yj_work' => 0, // 先占位
  620. ];
  621. }
  622. }
  623. // 3. 第二步:按人头独立参与的任务集合,精准分配分摊预计工时,同时记录已被触发的有效任务
  624. $valid_mission_ids = [];
  625. foreach ($employee_array as $emp_id => $emp_info) {
  626. $emp_yj_work = 0;
  627. foreach ($emp_info['mission_id'] as $m_id) {
  628. $emp_yj_work += $yj_map[$m_id] ?? 0;
  629. $valid_mission_ids[$m_id] = true; // 记录当月实际有人填报的任务
  630. }
  631. $employee_array[$emp_id]['actual_work'] = round($emp_info['actual_work'] / 60, 2);
  632. $employee_array[$emp_id]['yj_work'] = round($emp_yj_work, 2);
  633. }
  634. // 根据实际工时降序
  635. usort($employee_array, function($a, $b) {
  636. return $b['actual_work'] <=> $a['actual_work'];
  637. });
  638. $total_work_hours = round($total_work_minute / 60, 2);
  639. $employee_count = count($employee_keys);
  640. // 🌟 核心对齐:总预计工时【只计算当前出现在有效人员列表里的分摊工时总和】,确保和大盘数据指标完全对齐闭环
  641. $total_yj_work_hours = 0;
  642. foreach (array_keys($valid_mission_ids) as $m_id) {
  643. $total_yj_work_hours += $yj_map[$m_id] ?? 0;
  644. }
  645. $num = '0.00';
  646. $num_yj = '0.00';
  647. $num_rate = '0.00';
  648. $num_rate_2 = '0.00';
  649. $num_rate_3 = '0.00';
  650. $num_rate_4 = '0.00';
  651. $num_rate_5 = '0.00';
  652. if ($employee_count > 0) {
  653. $num = bcdiv($total_work_hours, $employee_count, 2);
  654. $num_yj = bcdiv($total_yj_work_hours, $employee_count, 2);
  655. }
  656. if (bccomp($num_yj, '0.00', 2) > 0) {
  657. $num_rate = bcmul(bcdiv($num, $num_yj, 4), 100, 2);
  658. }
  659. if ($total_yj_work_hours > 0 && $employee_count > 0) {
  660. $num_rate_2 = bcmul(bcdiv($total_work_hours, bcmul($total_yj_work_hours, $employee_count, 4), 4), 100, 2);
  661. }
  662. if ($total_yj_work_hours > 0) {
  663. $num_rate_3 = bcmul(bcdiv($total_work_hours, $total_yj_work_hours, 4), 100, 2);
  664. }
  665. // 成本人效比计算保持原有逻辑
  666. $total = Item::where('del_time', 0)->whereIn('id', array_unique(array_column($day_work_list, 'item_id')))->sum('budget');
  667. $monthly_ps_order = MonthlyPsOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $data['start_now_month'])
  668. ->where("month", "<", $data['end_now_month'])->pluck('id')->toArray();
  669. $month_employee_salary = MonthlyPsOrderDetails::whereIn('main_id', $monthly_ps_order)->select("base_salary", "performance_salary", "other", "bonus", "main_id")->get()->toArray();
  670. $man_total = 0;
  671. foreach ($month_employee_salary as $value) {
  672. $currentAmount = bcadd($value['base_salary'], $value['performance_salary'], 3);
  673. $currentAmount = bcadd($currentAmount, $value['other'], 3);
  674. $currentAmount = bcadd($currentAmount, $value['bonus'], 3);
  675. $man_total = bcadd($man_total, $currentAmount, 3);
  676. }
  677. if (bccomp($man_total, '0.000', 3) > 0) {
  678. $num_rate_4 = bcmul(bcdiv($total, $man_total, 4), 100, 2);
  679. $num_rate_5 = bcmul(bcdiv(bcdiv($total, $man_total, 4), 4, 4), 100, 2);
  680. }
  681. $man_final = round((float)$num_rate_3 * 0.6 + (float)$num_rate_4 * 0.4, 2);
  682. return [
  683. 'employee_work_list' => $employee_array,
  684. 'sub' => [
  685. 'average_actual_hours' => $num,
  686. 'average_du_hours' => $num_yj,
  687. 'average_actual_hours_rate' => $num_rate,
  688. 'load_rate' => $num_rate_2,
  689. 'achievement_rate' => $num_rate_3,
  690. 'man_rate' => $num_rate_4,
  691. 'man_rate_2' => $num_rate_5,
  692. 'man_final' => $man_final
  693. ]
  694. ];
  695. }
  696. private function ItemNodeMissionAboutLastSixMonthSearch($data, $user)
  697. {
  698. // 1. 先计算出近 6 个月的大总区间(最远月份的月初 到 当月的月末)
  699. $oldestMonthStart = date('Y-m-01 00:00:00', strtotime("-5 month"));
  700. $currentMonthEnd = date('Y-m-t 23:59:59'); // 当月月末
  701. $all_start_timestamp = strtotime($oldestMonthStart);
  702. $all_end_timestamp = strtotime($currentMonthEnd);
  703. // 2. 一次性捞出近 6 个月内所有可能相交的任务
  704. $mission_model = ItemNodeMission::TopAndEmployeeClear($user, $data);
  705. $all_missions = $mission_model->where('del_time', 0)
  706. ->where('start_time', '<=', $all_end_timestamp)
  707. ->where('end_time', '>=', $all_start_timestamp)
  708. ->select('id', 'due_work_hour', 'start_time', 'end_time')
  709. ->get()
  710. ->toArray();
  711. $all_mission_ids = array_column($all_missions, 'id');
  712. // 3. 一次性查出这些任务所有的实际工时记录(把 crt_time 换成你表里的 order_time)
  713. $all_work_contents = [];
  714. if (!empty($all_mission_ids)) {
  715. $all_work_contents = ItemNodeMissionContent::where('del_time', 0)
  716. ->whereIn('item_node_mission_id', $all_mission_ids)
  717. ->select('item_node_mission_id', 'total_work_min', 'order_time') // 👈 查出单据日期
  718. ->get()
  719. ->toArray();
  720. }
  721. $result = [];
  722. // 4. 以时间为驱动循环 6 次
  723. for ($i = 5; $i >= 0; $i--) {
  724. $monthStart = date('Y-m-01 00:00:00', strtotime("-$i month"));
  725. $monthEnd = date('Y-m-t 23:59:59', strtotime("-$i month"));
  726. $start_timestamp = strtotime($monthStart);
  727. $end_timestamp = strtotime($monthEnd);
  728. $month_name = date('Y-m', $start_timestamp);
  729. $month_due_hours = 0;
  730. $month_actual_mins = 0;
  731. // 5. 计算预计工时:按任务生命周期交集 + 天数均摊
  732. if (!empty($all_missions)) {
  733. foreach ($all_missions as $mission) {
  734. // 判断当前任务是否与当前月份有交集
  735. if ($mission['start_time'] <= $end_timestamp && $mission['end_time'] >= $start_timestamp) {
  736. // A. 计算任务总生命周期的总天数
  737. $total_days = ceil(($mission['end_time'] - $mission['start_time']) / 86400);
  738. if ($total_days <= 0) $total_days = 1;
  739. // B. 计算当前月份与该任务的【重叠区间】
  740. $overlap_start = max($mission['start_time'], $start_timestamp);
  741. $overlap_end = min($mission['end_time'], $end_timestamp);
  742. // C. 计算这个月里,该任务占用了多少天
  743. $overlap_days = ceil(($overlap_end - $overlap_start) / 86400);
  744. if ($overlap_days < 0) $overlap_days = 0;
  745. // D. 按比例分摊预计工时,并累加到当前月份
  746. $share_due_hour = ($mission['due_work_hour'] / $total_days) * $overlap_days;
  747. $month_due_hours += $share_due_hour;
  748. }
  749. }
  750. }
  751. // 6. ✨ 计算实际工时:严格根据单据日期 (order_time) 划归到具体的自然月
  752. if (!empty($all_work_contents)) {
  753. foreach ($all_work_contents as $work) {
  754. // 只有当工时记录的单据日期在当前月份区间内,才计入该月
  755. if ($work['order_time'] >= $start_timestamp && $work['order_time'] <= $end_timestamp) {
  756. $month_actual_mins += $work['total_work_min'];
  757. }
  758. }
  759. }
  760. // 7. 组装当前月份的数据结果
  761. $result[] = [
  762. 'month' => $month_name,
  763. 'total_due_hours' => round($month_due_hours, 2),
  764. 'total_actual_hours' => round($month_actual_mins / 60, 2)
  765. ];
  766. }
  767. return $result;
  768. }
  769. private function ItemManSearch($data, $user)
  770. {
  771. // 1. 获取当月进行中的项目
  772. $model = Item::TopAndEmployeeClear($user, $data);
  773. $item_list = $model->where('del_time', 0)
  774. ->whereIn('state', [Item::TYPE_TWO, Item::TYPE_THREE])
  775. ->where('start_time', '<=', $data['end_now_month'])
  776. ->where('end_time', '>=', $data['start_now_month'])
  777. ->select('id', 'title', 'budget') // 👈 增加选出项目总金额 budget 用于计算产值/成本
  778. ->get()->toArray();
  779. if (empty($item_list)) {
  780. return [];
  781. }
  782. $item_ids = array_column($item_list, 'id');
  783. $start_timestamp = (int)$data['start_now_month'];
  784. $end_timestamp = (int)$data['end_now_month'];
  785. // ==========================================
  786. // 🛠️ 核心步骤一:计算本月全员总实际工时与总工资
  787. // ==========================================
  788. // A. 本月全员总实际工时 (分钟)
  789. $all_employee_actual_mins = ItemNodeMissionContent::where('del_time', 0)
  790. ->where('order_time', '>=', $start_timestamp)
  791. ->where('order_time', '<=', $end_timestamp)
  792. ->sum('total_work_min');
  793. $all_employee_actual_hours = round($all_employee_actual_mins / 60, 2);
  794. // B. 本月全员总工资额
  795. $monthly_ps_order = MonthlyPsOrder::Clear($user, $data)->where('del_time', 0)
  796. ->where("month", ">=", $data['start_now_month'])
  797. ->where("month", "<", $data['end_now_month'])
  798. ->pluck('id')->toArray();
  799. $month_employee_salary = MonthlyPsOrderDetails::whereIn('main_id', $monthly_ps_order)
  800. ->select("base_salary", "performance_salary", "other", "bonus")
  801. ->get()->toArray();
  802. $total_salary = 0;
  803. foreach ($month_employee_salary as $value) {
  804. $currentAmount = bcadd($value['base_salary'], $value['performance_salary'], 3);
  805. $currentAmount = bcadd($currentAmount, $value['other'], 3);
  806. $currentAmount = bcadd($currentAmount, $value['bonus'], 3);
  807. $total_salary = bcadd($total_salary, $currentAmount, 3);
  808. }
  809. // ==========================================
  810. // 🛠️ 核心步骤二:获取项目下当月的任务并均摊预计工时
  811. // ==========================================
  812. // 获取这批项目下的所有任务
  813. $missions = ItemNodeMission::where('del_time', 0)
  814. ->whereIn('item_id', $item_ids)
  815. ->where('start_time', '<=', $end_timestamp)
  816. ->where('end_time', '>=', $start_timestamp)
  817. ->select('id', 'item_id', 'due_work_hour', 'start_time', 'end_time')
  818. ->get()->toArray();
  819. // 按项目归总分摊后的预计工时
  820. $item_yj_work_map = [];
  821. foreach ($missions as $m) {
  822. $proj_id = $m['item_id'];
  823. $m_start = is_numeric($m['start_time']) ? (int)$m['start_time'] : strtotime($m['start_time']);
  824. $m_end = is_numeric($m['end_time']) ? (int)$m['end_time'] : strtotime($m['end_time']);
  825. if ($m_start && $m_end && $m_start <= $end_timestamp && $m_end >= $start_timestamp) {
  826. // 任务总寿命天数
  827. $total_days = ceil(($m_end - $m_start) / 86400);
  828. if ($total_days <= 0) $total_days = 1;
  829. // 当月重叠天数
  830. $overlap_start = max($m_start, $start_timestamp);
  831. $overlap_end = min($m_end, $end_timestamp);
  832. $overlap_days = ceil(($overlap_end - $overlap_start) / 86400);
  833. if ($overlap_days < 0) $overlap_days = 0;
  834. // 分摊到当月的预计工时
  835. $share_yj_hour = ($m['due_work_hour'] / $total_days) * $overlap_days;
  836. if (!isset($item_yj_work_map[$proj_id])) {
  837. $item_yj_work_map[$proj_id] = 0;
  838. }
  839. $item_yj_work_map[$proj_id] += $share_yj_hour;
  840. }
  841. }
  842. // ==========================================
  843. // 🛠️ 核心步骤三:获取项目下当月的实际工时
  844. // ==========================================
  845. $work_contents = ItemNodeMissionContent::where('del_time', 0)
  846. ->whereIn('item_id', $item_ids)
  847. ->where('order_time', '>=', $start_timestamp)
  848. ->where('order_time', '<=', $end_timestamp)
  849. ->select('item_id', 'total_work_min')
  850. ->get()->toArray();
  851. // 按项目归总实际工时(分钟转换为小时)
  852. $item_actual_work_map = [];
  853. foreach ($work_contents as $work) {
  854. $proj_id = $work['item_id'];
  855. if (!isset($item_actual_work_map[$proj_id])) {
  856. $item_actual_work_map[$proj_id] = 0;
  857. }
  858. $item_actual_work_map[$proj_id] += $work['total_work_min'];
  859. }
  860. // ==========================================
  861. // 🛠️ 核心步骤四:组装数据并计算各项人效指标
  862. // ==========================================
  863. foreach ($item_list as &$item) {
  864. $id = $item['id'];
  865. // 1. 预计工时与实际工时
  866. $yj_work = isset($item_yj_work_map[$id]) ? round($item_yj_work_map[$id], 2) : 0.00;
  867. $actual_work = isset($item_actual_work_map[$id]) ? round($item_actual_work_map[$id] / 60, 2) : 0.00;
  868. $item['yj_work'] = $yj_work;
  869. $item['actual_work'] = $actual_work;
  870. // 2. 计算工时达成率 = 实际 ÷ 预计 × 100%
  871. $item['achievement_rate'] = '0.00';
  872. if ($yj_work > 0) {
  873. $item['achievement_rate'] = bcmul(bcdiv($actual_work, $yj_work, 4), 100, 2);
  874. }
  875. // 3. 计算项目人力成本 = 项目实际工时 × (本月工资总额 ÷ 本月全员实际工时)
  876. $item_cost = '0.00';
  877. if ($all_employee_actual_hours > 0) {
  878. // 平均时薪 = 工资总额 ÷ 全员实际工时
  879. $hourly_wage = bcdiv($total_salary, $all_employee_actual_hours, 4);
  880. // 项目人力成本 = 项目实际工时 × 平均时薪
  881. $item_cost = bcmul($actual_work, $hourly_wage, 2);
  882. }
  883. $item['item_cost'] = $item_cost;
  884. // 4. 计算产值/成本 = 项目总金额 ÷ 项目人力成本
  885. $item['production_cost_rate'] = '0.00';
  886. if (bccomp($item_cost, '0.00', 2) > 0) {
  887. $item['production_cost_rate'] = bcdiv($item['budget'], $item_cost, 2);
  888. }
  889. }
  890. return $item_list;
  891. }
  892. }