StatisticService.php 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114
  1. <?php
  2. namespace App\Service;
  3. use App\Model\AuxiliaryAccount;
  4. use App\Model\AuxiliaryAccountDetails;
  5. use App\Model\CalendarDetails;
  6. use App\Model\DailyDwOrderDetails;
  7. use App\Model\DailyPwOrderDetails;
  8. use App\Model\Device;
  9. use App\Model\Employee;
  10. use App\Model\ExpenseClaims;
  11. use App\Model\ExpenseClaimsDetails;
  12. use App\Model\Fee;
  13. use App\Model\Item;
  14. use App\Model\ItemDetails;
  15. use App\Model\MonthlyDdOrder;
  16. use App\Model\MonthlyDdOrderDetails;
  17. use App\Model\MonthlyPsOrder;
  18. use App\Model\MonthlyPsOrderDetails;
  19. use App\Model\MonthlyPwOrderDetails;
  20. use App\Model\RuleSet;
  21. use App\Model\RuleSetDetails;
  22. use Carbon\Carbon;
  23. use Illuminate\Support\Facades\DB;
  24. class StatisticService extends Service
  25. {
  26. public function employeeDayHourStatistic($data, $user)
  27. {
  28. //传参月份、项目编码、项目名称 不允许跨年查询月份
  29. //项目编码、项目名称、人员名称、工时、日期
  30. list($status, $month_start, $month_end) = $this->commonRule($data);
  31. if (!$status) return [false, $month_start];
  32. $month_employee = DailyPwOrderDetails::Clear($user, $data);
  33. $month_employee_list = $month_employee->where("order_time", ">=", $month_start)->where("order_time", "<", $month_end)
  34. ->where('del_time', 0)
  35. ->select(
  36. "item_id",
  37. "employee_id",
  38. // 将时间戳转为 Y-m-d 格式并起别名
  39. DB::raw("FROM_UNIXTIME(order_time, '%Y-%m-%d') as order_date"),
  40. // 聚合求和
  41. DB::raw("SUM(total_work_min) as total_work")
  42. )
  43. ->groupBy(DB::raw("FROM_UNIXTIME(order_time, '%Y-%m-%d')"), "item_id", "employee_id")
  44. ->orderby("order_date", "asc")->get()->toArray();
  45. // $month_employee_list = $this->limit($month_employee_list, ['*'], $data);
  46. $dataCollection = collect($month_employee_list);
  47. $item_ids = $dataCollection->pluck('item_id')->unique()->values()->all();
  48. $employee_ids = $dataCollection->pluck('employee_id')->unique()->values()->all();
  49. $employee = Employee::Clear($user, $data);
  50. $employee_key_list = $employee->wherein('id', $employee_ids)->pluck("title", "id")->toArray();
  51. $item = Item::Clear($user, $data);
  52. $item_title_key_list = $item->wherein('id', $item_ids)->pluck("title", "id")->toArray();
  53. $item_code_key_list = $item->wherein('id', $item_ids)->pluck("code", "id")->toArray();
  54. $month_employee_list = collect($month_employee_list)->transform(function ($item) use ($employee_key_list, $item_title_key_list, $item_code_key_list) {
  55. $item['employee_name'] = $employee_key_list[$item['employee_id']] ?? "未知员工({$item['employee_id']})";
  56. $item['item_title'] = $item_title_key_list[$item['item_id']] ?? "未知项目({$item['item_id']})";
  57. $item['item_code'] = $item_code_key_list[$item['item_id']] ?? "未知项目({$item['item_id']})";
  58. $item['total_work_hours'] = round($item['total_work'] / 60, 2);
  59. return $item;
  60. })->all();
  61. return [true, $month_employee_list];
  62. }
  63. public function employeeMonthSalaryStatistic($data, $user)
  64. {
  65. //项目编码、项目名称、天数、工资、日期
  66. list($status, $month_start, $month_end) = $this->commonRule($data);
  67. if (!$status) return [false, $month_start];
  68. //确认所有项目、人员、人员工时
  69. $month_employee = DailyPwOrderDetails::Clear($user, $data);
  70. $month_employee_list = $month_employee->where("order_time", ">=", $month_start)->where("order_time", "<", $month_end)
  71. ->where('del_time', 0)
  72. ->select(
  73. "item_id",
  74. "employee_id",
  75. // 将时间戳转为 Y-m-d 格式并起别名
  76. DB::raw("FROM_UNIXTIME(order_time, '%Y-%m') as order_month"),
  77. // 聚合求和
  78. DB::raw("SUM(total_work_min) as total_work")
  79. )
  80. ->groupBy("order_month", "item_id", "employee_id")->get()->toArray();
  81. //查询所有人员工资
  82. $monthly_ps_order_ids = MonthlyPsOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)
  83. ->pluck('id')->toArray();
  84. $monthly_ps_order_key_list = MonthlyPsOrder::Clear($user, $data)
  85. ->where('del_time', 0)
  86. ->where("month", ">=", $month_start)
  87. ->where("month", "<", $month_end)
  88. ->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
  89. ->pluck('month_str', 'id')
  90. ->toArray();
  91. $month_employee_salary = MonthlyPsOrderDetails::wherein('main_id', $monthly_ps_order_ids)
  92. ->select("employee_id", "salary", "main_id")
  93. ->get()->toArray();
  94. //查询所有项目人员的工时比例
  95. //汇总每个人每个月工资
  96. $salary_map = [];
  97. foreach ($month_employee_salary as $val) {
  98. $month = $monthly_ps_order_key_list[$val['main_id']] ?? '';
  99. if ($month) {
  100. $salary_map[$val['employee_id'] . '_' . $month] = $val['salary'];
  101. }
  102. }
  103. // var_dump($salary_map);die;
  104. // 2. 计算每个员工在每个月的全月总工时
  105. $employee_monthly_total_min = [];
  106. foreach ($month_employee_list as $row) {
  107. $key = $row['employee_id'] . '_' . $row['order_month'];
  108. if (!isset($employee_monthly_total_min[$key])) {
  109. $employee_monthly_total_min[$key] = 0;
  110. }
  111. $employee_monthly_total_min[$key] += $row['total_work'];
  112. }
  113. // 3. 计算分摊天数与工资
  114. $item_month_list = [];
  115. foreach ($month_employee_list as $item) {
  116. $key = $item['employee_id'] . '_' . $item['order_month'];
  117. $item_key = $item['order_month'] . '_' . $item['item_id'];
  118. if (!isset($item_month_list[$item_key])) {
  119. $item_month_list[$item_key] = [
  120. "month" => $item['order_month'],
  121. "allocated_salary" => 0,
  122. "work_minutes" => 0,
  123. "item_id" => $item['item_id'],
  124. ];
  125. }
  126. $total_salary = $salary_map[$key] ?? 0;
  127. $total_min = $employee_monthly_total_min[$key] ?? 0;
  128. // B. 计算工资分摊:(项目工时 / 月总工时) * 月工资
  129. if ($total_min > 0) {
  130. $ratio = $item['total_work'] / $total_min;
  131. $allocated_salary = round($ratio * $total_salary, 2);
  132. } else {
  133. $allocated_salary = 0;
  134. }
  135. $item_month_list[$item_key]['allocated_salary'] += $allocated_salary;
  136. $item_month_list[$item_key]['work_minutes'] += $item['total_work'];
  137. }
  138. foreach ($item_month_list as $k => $v) {
  139. $item_month_list[$k]['days'] = round($v['work_minutes'] / 8 / 60);
  140. unset($item_month_list[$k]['work_minutes']);
  141. }
  142. $item_month_list = collect($item_month_list)->sortBy('month')->values()->all();
  143. $items = collect($item_month_list)->pluck('item_id')->unique()->values()->all();
  144. $item = Item::Clear($user, $data);
  145. $item_title_key_list = $item->wherein('id', $items)->pluck("title", "id")->toArray();
  146. $item_code_key_list = $item->wherein('id', $items)->pluck("code", "id")->toArray();
  147. $item_month_list = collect($item_month_list)->transform(function ($item) use ($item_title_key_list, $item_code_key_list) {
  148. $item['item_title'] = $item_title_key_list[$item['item_id']] ?? "未知项目({$item['item_id']})";
  149. $item['item_code'] = $item_code_key_list[$item['item_id']] ?? "未知项目({$item['item_id']})";
  150. return $item;
  151. })->all();
  152. return [true, $item_month_list];
  153. }
  154. public function itemDaySalaryStatistic($data, $user)
  155. {
  156. //项目编码、项目名称、人员名称、研发工时、研发工资、当月总工时、总计工资、年月
  157. list($status, $month_start, $month_end) = $this->commonRule($data);
  158. if (!$status) return [false, $month_start];
  159. //确认所有项目、人员、人员工时
  160. $month_employee = DailyPwOrderDetails::Clear($user, $data);
  161. $month_employee_list = $month_employee->where("order_time", ">=", $month_start)->where("order_time", "<", $month_end)
  162. ->where('del_time', 0)
  163. ->select(
  164. "item_id",
  165. "employee_id",
  166. // 将时间戳转为 Y-m-d 格式并起别名
  167. DB::raw("FROM_UNIXTIME(order_time, '%Y-%m') as order_month"),
  168. // 聚合求和
  169. DB::raw("SUM(total_work_min) as total_work")
  170. )
  171. ->groupBy(DB::raw("FROM_UNIXTIME(order_time, '%Y-%m')"), "item_id", "employee_id")->get()->toArray();
  172. //查询所有人员工资
  173. $monthly_ps_order_ids = MonthlyPsOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)->pluck('id')->toArray();
  174. $monthly_ps_order_key_list = MonthlyPsOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)
  175. ->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
  176. ->pluck('month_str', 'id')->toArray();
  177. $month_employee_salary = MonthlyPsOrderDetails::wherein('main_id', $monthly_ps_order_ids)
  178. ->select("employee_id", "salary", "main_id")
  179. ->get()->toArray();
  180. //查询所有项目人员的工时比例
  181. //汇总每个人每个月工资
  182. $salary_map = [];
  183. foreach ($month_employee_salary as $val) {
  184. $month = $monthly_ps_order_key_list[$val['main_id']] ?? '';
  185. if ($month) {
  186. $salary_map[$val['employee_id'] . '_' . $month] = $val['salary'];
  187. }
  188. }
  189. // 2. 计算每个员工在每个月的全月总工时
  190. $employee_monthly_total_min = [];
  191. foreach ($month_employee_list as $row) {
  192. $key = $row['employee_id'] . '_' . $row['order_month'];
  193. if (!isset($employee_monthly_total_min[$key])) {
  194. $employee_monthly_total_min[$key] = 0;
  195. }
  196. $employee_monthly_total_min[$key] += $row['total_work'];
  197. }
  198. // 3. 计算分摊天数与工资
  199. $item_month_list = [];
  200. foreach ($month_employee_list as $item) {
  201. $key = $item['employee_id'] . '_' . $item['order_month'];
  202. $employee_key = $item['order_month'] . '_' . $item['item_id'] . '_' . $item['employee_id'];
  203. $total_salary = $salary_map[$key] ?? 0;
  204. $total_min = $employee_monthly_total_min[$key] ?? 0;
  205. if (!isset($item_month_list[$employee_key])) {
  206. $item_month_list[$employee_key] = [
  207. "month" => $item['order_month'],
  208. "allocated_salary" => 0,
  209. "work_minutes" => 0,
  210. "total_min" => $total_min,
  211. "total_salary" => $total_salary,
  212. "item_id" => $item['item_id'],
  213. "employee_id" => $item['employee_id'],
  214. ];
  215. }
  216. // B. 计算工资分摊:(项目工时 / 月总工时) * 月工资
  217. if ($total_min > 0) {
  218. $ratio = $item['total_work'] / $total_min;
  219. $allocated_salary = round($ratio * $total_salary, 2);
  220. } else {
  221. $allocated_salary = 0;
  222. }
  223. $item_month_list[$employee_key]['allocated_salary'] += $allocated_salary;
  224. $item_month_list[$employee_key]['work_minutes'] += $item['total_work'];
  225. }
  226. foreach ($item_month_list as $k => $v) {
  227. $item_month_list[$k]['work_hours'] = round($v['work_minutes'] / 60,2);
  228. $item_month_list[$k]['total_hours'] = round($v['total_min'] / 60);
  229. unset($item_month_list[$k]['work_minutes']);
  230. }
  231. $item_month_list = collect($item_month_list)->sortBy('month')->values()->all();
  232. $items = collect($item_month_list)->pluck('item_id')->unique()->values()->all();
  233. $item = Item::Clear($user, $data);
  234. $item_title_key_list = $item->wherein('id', $items)->pluck("title", "id")->toArray();
  235. $item_code_key_list = $item->wherein('id', $items)->pluck("code", "id")->toArray();
  236. $employee_ids = collect($item_month_list)->pluck('employee_id')->unique()->values()->all();
  237. $employee = Employee::Clear($user, $data);
  238. $employee_key_list = $employee->wherein('id', $employee_ids)->pluck("title", "id")->toArray();
  239. $item_month_list = collect($item_month_list)->transform(function ($item) use ($item_title_key_list, $item_code_key_list, $employee_key_list) {
  240. $item['item_title'] = $item_title_key_list[$item['item_id']] ?? "未知项目({$item['item_id']})";
  241. $item['item_code'] = $item_code_key_list[$item['item_id']] ?? "未知项目({$item['item_id']})";
  242. $item['employee_title'] = $employee_key_list[$item['employee_id']] ?? "未知人员({$item['employee_id']})";
  243. return $item;
  244. })->all();
  245. return [true, $item_month_list];
  246. }
  247. public function itemDeviceMonthStatistic($data, $user)
  248. {
  249. //项目编码、项目名称、设备名称、项目工时、研发工时占比、设备原值、设备折旧额、本项目帐面归集的折旧额、确定的本项目折旧额、加计调整金额、当月工时、日期
  250. list($status, $month_start, $month_end) = $this->commonRule($data);
  251. if (!$status) return [false, $month_start];
  252. //确认所有项目、设备、设备工时
  253. $month_device = DailyDwOrderDetails::Clear($user, $data);
  254. $month_device_list = $month_device->where("order_time", ">=", $month_start)->where("order_time", "<", $month_end)
  255. ->where('del_time', 0)
  256. ->select(
  257. "item_id",
  258. "device_id",
  259. // 将时间戳转为 Y-m-d 格式并起别名
  260. DB::raw("FROM_UNIXTIME(order_time, '%Y-%m') as order_month"),
  261. // 聚合求和
  262. DB::raw("SUM(total_work_min) as total_work")
  263. )
  264. ->groupBy(DB::raw("FROM_UNIXTIME(order_time, '%Y-%m')"), "item_id", "device_id")->get()->toArray();
  265. //查询所有人员工资
  266. $monthly_dd_order_ids = MonthlyDdOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)
  267. ->pluck('id')->toArray();
  268. $monthly_dd_order_key_list = MonthlyDdOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)
  269. ->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
  270. ->pluck("month_str", 'id')->toArray();
  271. $month_device_salary = MonthlyDdOrderDetails::wherein('main_id', $monthly_dd_order_ids)
  272. ->select("device_id", "depreciation_amount", "main_id")
  273. ->get()->toArray();
  274. //查询所有项目人员的工时比例
  275. //汇总每个人每个月工资
  276. $depreciatio_map = [];
  277. foreach ($month_device_salary as $val) {
  278. $month = $monthly_dd_order_key_list[$val['main_id']] ?? '';
  279. if ($month) {
  280. $depreciatio_map[$val['device_id'] . '_' . $month] = $val['depreciation_amount'];
  281. }
  282. }
  283. // 2. 计算每个员工在每个月的全月总工时
  284. $device_monthly_total_min = [];
  285. foreach ($month_device_list as $row) {
  286. $key = $row['device_id'] . '_' . $row['order_month'];
  287. if (!isset($device_monthly_total_min[$key])) {
  288. $device_monthly_total_min[$key] = 0;
  289. }
  290. $device_monthly_total_min[$key] += $row['total_work'];
  291. }
  292. // 3. 计算分摊天数与工资
  293. $item_month_list = [];
  294. foreach ($month_device_list as $item) {
  295. $key = $item['device_id'] . '_' . $item['order_month'];
  296. $device_key = $item['order_month'] . '_' . $item['item_id'] . '_' . $item['device_id'];
  297. $total_depreciatio = $depreciatio_map[$key] ?? 0;
  298. $total_min = $device_monthly_total_min[$key] ?? 0;
  299. if (!isset($item_month_list[$device_key])) {
  300. $item_month_list[$device_key] = [
  301. "month" => $item['order_month'],
  302. "allocated_depreciatio" => 0,
  303. "work_minutes" => 0,
  304. "total_min" => $total_min,
  305. "item_id" => $item['item_id'],
  306. "device_id" => $item['device_id'],
  307. "total_depreciatio" => $total_depreciatio,
  308. ];
  309. }
  310. // B. 计算工资分摊:(项目工时 / 月总工时) * 月工资
  311. if ($total_min > 0) {
  312. $ratio = round($item['total_work'] / $total_min, 3);
  313. $allocated_salary = round($ratio * $total_depreciatio, 2);
  314. } else {
  315. $ratio = 0;
  316. $allocated_salary = 0;
  317. }
  318. $item_month_list[$device_key]['allocated_depreciatio'] += $allocated_salary;
  319. $item_month_list[$device_key]['work_minutes'] += $item['total_work'];
  320. $item_month_list[$device_key]['ratio'] = $ratio;
  321. }
  322. foreach ($item_month_list as $k => $v) {
  323. $item_month_list[$k]['total_hours'] = round($v['total_min'] / 60, 1);
  324. $item_month_list[$k]['hours'] = round($v['work_minutes'] / 60, 1);
  325. unset($item_month_list[$k]['work_minutes']);
  326. }
  327. $item_month_list = collect($item_month_list)->sortBy('month')->values()->all();
  328. $items = collect($item_month_list)->pluck('item_id')->unique()->values()->all();
  329. $item = Item::Clear($user, $data);
  330. $item_title_key_list = $item->wherein('id', $items)->pluck("title", "id")->toArray();
  331. $item_code_key_list = $item->wherein('id', $items)->pluck("code", "id")->toArray();
  332. $device_ids = collect($item_month_list)->pluck('device_id')->unique()->values()->all();
  333. $device = Device::Clear($user, $data);
  334. $device_key_list = $device->wherein('id', $device_ids)->pluck("title", "id")->toArray();
  335. $device_original_value_key_list = $device->wherein('id', $device_ids)->pluck("original_value", "id")->toArray();
  336. $item_month_list = collect($item_month_list)->transform(function ($item) use ($item_title_key_list, $item_code_key_list, $device_key_list, $device_original_value_key_list) {
  337. $item['item_title'] = $item_title_key_list[$item['item_id']] ?? "未知项目({$item['item_id']})";
  338. $item['item_code'] = $item_code_key_list[$item['item_id']] ?? "未知项目({$item['item_id']})";
  339. $item['device_title'] = $device_key_list[$item['device_id']] ?? "未知人员({$item['device_id']})";
  340. $item['device_original'] = $device_original_value_key_list[$item['device_id']] ?? "未知人员({$item['device_id']})";
  341. return $item;
  342. })->all();
  343. return [true, $item_month_list];
  344. }
  345. private function commonRule($data)
  346. {
  347. if(! empty($data['year'])){
  348. $return = $this->getYearRangeInfo($data['year']);
  349. if(is_null($return)) return [false, '年度格式错误'];
  350. list($data['month_start'], $data['month_end']) = $return;
  351. }else{
  352. if(isset($data['time']) && ! empty($data['time'])){
  353. $start = $this->changeDateToDate($data['time'][0]);
  354. $end = $this->changeDateToDate($data['time'][1], true);
  355. $data['month_start'] = date("Y-m-d",$start);
  356. $data['month_end'] = date("Y-m-d",$end);
  357. }
  358. }
  359. if (!isset($data['month_start'])) $month_start = date('Y-01-01');
  360. else $month_start = date('Y-m-01', strtotime($data['month_start']));
  361. if (!isset($data['month_end'])) $month_end = date('Y-01-01', strtotime('+1 year', strtotime($month_start)));
  362. else {
  363. $start_year = date('Y', strtotime($month_start));
  364. $end_year = date('Y', strtotime($data['month_end']));
  365. if ($start_year != $end_year) return [false, "查询不得跨年!", ""];
  366. $month_end = date('Y-m-01', strtotime($data['month_end'] . ' +1 month'));
  367. }
  368. return [true, strtotime($month_start), strtotime($month_end)];
  369. }
  370. /**
  371. * 根据前端 ISO 时间字符串获取该年份的起止日期和时间戳
  372. * 适配:2019-12-31T16:00:00.000Z 这种带时区的数据
  373. *
  374. * @param string $isoStr 前端传来的时间字符串
  375. * @return array|null
  376. */
  377. public function getYearRangeInfo($isoStr)
  378. {
  379. if (empty($isoStr)) return null;
  380. try {
  381. // 1. 解析 ISO 8601 字符串
  382. $date = new \DateTime($isoStr);
  383. // 2. 强制转为中国时区(PRC),处理 16:00:00Z 这种 UTC 偏移
  384. $date->setTimezone(new \DateTimeZone('PRC'));
  385. // 3. 提取年份
  386. $year = $date->format('Y');
  387. // 4. 构造日期字符串
  388. $startDate = $year . "-01-01";
  389. $endDate = $year . "-12-31";
  390. return [
  391. 'start_date' => $startDate,
  392. 'end_date' => $endDate,
  393. ];
  394. } catch (\Exception $e) {
  395. return null;
  396. }
  397. }
  398. public function employeeAttendanceMonthStatistic($data, $user)
  399. {
  400. //项目编码、项目名称、项目状态、支出类型、允许加计扣除金额合计、人员人工费用、折旧费用、其他费用、前N项小计、其他相关费用合计、委内费用、委外费用、
  401. list($status, $month_start, $month_end) = $this->commonRule($data);
  402. if (!$status) {
  403. return [false, $month_start];
  404. }
  405. //第一步确定项目
  406. $item = Item::Clear($user, $data);
  407. $item_list = $item->where('del_time', 0)
  408. ->where(function ($query) use ($month_start, $month_end) {
  409. $query->where('start_time', '<=', $month_start)
  410. ->orWhere('end_time', '>=', $month_end);
  411. })->select("code", "title", "start_time", "end_time", "id", "state")->orderby("start_time","asc")->get()->toArray();
  412. $item_key_list = [];
  413. foreach ($item_list as $v) {
  414. $item_key_list[$v['id']] = $v;
  415. }
  416. //第二步确定人员费用
  417. $item_employee_list = $this->getEmployeeItemSalary($month_start, $month_end, $data, $user);
  418. //第三步确定折旧费用
  419. $item_device_list = $this->getDeviceItemSalary($month_start, $month_end, $data, $user);
  420. //第四步其他费用
  421. list($item_fee_list,$fee_type_list) = $this->getFeeItemSalary($month_start, $month_end, $data, $user);
  422. //组合所有数据
  423. $return = [];
  424. foreach ($item_key_list as $v){
  425. //其他费用是个数组
  426. $item_value = [
  427. "code" => $v['code'],
  428. "title" => $v['title'],
  429. "state" => $v['state'] == 3 ? "完结" : "进行中",
  430. "employee_salary" => $item_employee_list[$v['id']]['salary']??0,
  431. "device_depreciation" => $item_device_list[$v['id']]['depreciation']??0,
  432. "expense_type" => "费用化支出",
  433. "fee_list" => collect($item_fee_list[$v['id']] ?? [])->values()->all(),
  434. ];
  435. $return[] = $item_value;
  436. }
  437. return [true,["list"=>$return,"fee_type_list"=>$fee_type_list]];
  438. }
  439. private function getEmployeeItemSalary($month_start, $month_end, $data, $user)
  440. {
  441. $month_employee = DailyPwOrderDetails::Clear($user, $data);
  442. $month_employee_list = $month_employee->where("order_time", ">=", $month_start)->where("order_time", "<", $month_end)
  443. ->where('del_time', 0)
  444. ->select(
  445. "item_id",
  446. "employee_id",
  447. // 将时间戳转为 Y-m-d 格式并起别名
  448. DB::raw("FROM_UNIXTIME(order_time, '%Y-%m') as order_month"),
  449. // 聚合求和
  450. DB::raw("SUM(total_work_min) as total_work")
  451. )
  452. ->groupBy("order_month", "item_id", "employee_id")->get()->toArray();
  453. //查询所有人员工资
  454. $monthly_ps_order_ids = MonthlyPsOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)
  455. ->pluck('id')->toArray();
  456. $monthly_ps_order_key_list = MonthlyPsOrder::Clear($user, $data)
  457. ->where('del_time', 0)
  458. ->where("month", ">=", $month_start)
  459. ->where("month", "<", $month_end)
  460. ->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
  461. ->pluck('month_str', 'id')
  462. ->toArray();
  463. $month_employee_salary = MonthlyPsOrderDetails::wherein('main_id', $monthly_ps_order_ids)
  464. ->select("employee_id", "salary", "main_id")
  465. ->get()->toArray();
  466. //查询所有项目人员的工时比例
  467. //汇总每个人每个月工资
  468. $salary_map = [];
  469. foreach ($month_employee_salary as $val) {
  470. $month = $monthly_ps_order_key_list[$val['main_id']] ?? '';
  471. if ($month) {
  472. $salary_map[$val['employee_id'] . '_' . $month] = $val['salary'];
  473. }
  474. }
  475. // 2. 计算每个员工在每个月的全月总工时
  476. $employee_monthly_total_min = [];
  477. foreach ($month_employee_list as $row) {
  478. $key = $row['employee_id'] . '_' . $row['order_month'];
  479. if (!isset($employee_monthly_total_min[$key])) {
  480. $employee_monthly_total_min[$key] = 0;
  481. }
  482. $employee_monthly_total_min[$key] += $row['total_work'];
  483. }
  484. // 2. 计算分摊天数与工资
  485. $item_list = [];
  486. foreach ($month_employee_list as $item) {
  487. $key = $item['employee_id'] . '_' . $item['order_month'];
  488. $item_key = $item['item_id'];
  489. if (!isset($item_list[$item_key])) {
  490. $item_list[$item_key] = [
  491. "salary" => 0,
  492. ];
  493. }
  494. $total_salary = $salary_map[$key] ?? 0;
  495. $total_min = $employee_monthly_total_min[$key] ?? 0;
  496. // B. 计算工资分摊:(项目工时 / 月总工时) * 月工资
  497. if ($total_min > 0) {
  498. $ratio = $item['total_work'] / $total_min;
  499. $allocated_salary = round($ratio * $total_salary, 2);
  500. } else {
  501. $allocated_salary = 0;
  502. }
  503. $item_list[$item_key]['salary'] += $allocated_salary;
  504. }
  505. return $item_list;
  506. }
  507. private function getDeviceItemSalary($month_start, $month_end, $data, $user)
  508. {
  509. //确认所有项目、设备、设备工时
  510. $month_device = DailyDwOrderDetails::Clear($user, $data);
  511. $month_device_list = $month_device->where("order_time", ">=", $month_start)->where("order_time", "<", $month_end)
  512. ->where('del_time', 0)
  513. ->select(
  514. "item_id",
  515. "device_id",
  516. // 将时间戳转为 Y-m-d 格式并起别名
  517. DB::raw("FROM_UNIXTIME(order_time, '%Y-%m') as order_month"),
  518. // 聚合求和
  519. DB::raw("SUM(total_work_min) as total_work")
  520. )
  521. ->groupBy(DB::raw("FROM_UNIXTIME(order_time, '%Y-%m')"), "item_id", "device_id")->get()->toArray();
  522. //查询所有人员工资
  523. $monthly_dd_order_ids = MonthlyDdOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)
  524. ->pluck('id')->toArray();
  525. $monthly_dd_order_key_list = MonthlyDdOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)
  526. ->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
  527. ->pluck("month_str", 'id')->toArray();
  528. $month_device_salary = MonthlyDdOrderDetails::wherein('main_id', $monthly_dd_order_ids)
  529. ->select("device_id", "depreciation_amount", "main_id")
  530. ->get()->toArray();
  531. //查询所有项目人员的工时比例
  532. //汇总每个人每个月工资
  533. $depreciatio_map = [];
  534. foreach ($month_device_salary as $val) {
  535. $month = $monthly_dd_order_key_list[$val['main_id']] ?? '';
  536. if ($month) {
  537. $depreciatio_map[$val['device_id'] . '_' . $month] = $val['depreciation_amount'];
  538. }
  539. }
  540. // 2. 计算每个员工在每个月的全月总工时
  541. $device_monthly_total_min = [];
  542. foreach ($month_device_list as $row) {
  543. $key = $row['device_id'] . '_' . $row['order_month'];
  544. if (!isset($device_monthly_total_min[$key])) {
  545. $device_monthly_total_min[$key] = 0;
  546. }
  547. $device_monthly_total_min[$key] += $row['total_work'];
  548. }
  549. // 3. 计算分摊天数与工资
  550. $item_list = [];
  551. foreach ($month_device_list as $item) {
  552. $key = $item['device_id'] . '_' . $item['order_month'];
  553. $device_key = $item['item_id'];
  554. $total_depreciatio = $depreciatio_map[$key] ?? 0;
  555. $total_min = $device_monthly_total_min[$key] ?? 0;
  556. if (!isset($item_list[$device_key])) {
  557. $item_list[$device_key] = [
  558. "depreciation" => 0,
  559. ];
  560. }
  561. // B. 计算工资分摊:(项目工时 / 月总工时) * 月工资
  562. if ($total_min > 0) {
  563. $ratio = round($item['total_work'] / $total_min, 3);
  564. $allocated_salary = round($ratio * $total_depreciatio, 2);
  565. } else {
  566. $allocated_salary = 0;
  567. }
  568. $item_list[$device_key]['depreciation'] += $allocated_salary;
  569. }
  570. return $item_list;
  571. }
  572. private function getFeeItemSalary($month_start, $month_end, $data, $user)
  573. {
  574. //确认所有项目、费用
  575. $expense = ExpenseClaimsDetails::Clear($user, $data);
  576. $expense_list = $expense->where("claim_date", ">=", $month_start)->where("claim_date", "<", $month_end)
  577. ->where('del_time', 0)
  578. ->select(
  579. "fee_id",
  580. "amount",
  581. "item_id",
  582. "entrust_type"
  583. )->get()->toArray();
  584. //需要根据分类去汇总
  585. $fee = Fee::Clear($user, $data);
  586. $fee = $fee->where('del_time', 0)->orderBy("sort", 'desc')->get()->toArray();
  587. return $this->groupListByRoot($expense_list, $fee);
  588. }
  589. /**
  590. * 将报销明细按照一级费用和项目分类进行分组
  591. * * @param array $list 报销明细列表 (含 fee_id, amount 等)
  592. * @param array $fee_type_list 费用类型树 (含 id, parent_id, title)
  593. * @return array
  594. */
  595. public function groupListByRoot(array $list, array $fee_type_list)
  596. {
  597. // 1. 建立 ID 索引,方便快速查找
  598. $idMap = array_column($fee_type_list, null, 'id');
  599. // 2. 预处理映射表:让所有子 ID 直接指向它的最顶层“祖宗” ID
  600. $childToRoot = [];
  601. foreach ($fee_type_list as $type) {
  602. $current = $type;
  603. // 向上追溯直到 parent_id 为 0,即找到一级分类
  604. while ($current['parent_id'] != 0) {
  605. $current = $idMap[$current['parent_id']];
  606. }
  607. $childToRoot[$type['id']] = [
  608. 'id' => $current['id'],
  609. 'title' => $current['title'],
  610. 'sort' => $current['sort']
  611. ];
  612. }
  613. // 3. 遍历明细数据进行分组
  614. $item_key_list = [];
  615. $type_list = [];
  616. foreach ($list as $item) {
  617. $feeId = $item['fee_id'];
  618. // 获取该费用对应的一级分类信息
  619. if (!isset($childToRoot[$feeId])) continue;
  620. $rootId = $childToRoot[$feeId]['id'];
  621. $title = $childToRoot[$feeId]['title'];
  622. $sort = $childToRoot[$feeId]['sort'];
  623. $key = $item['item_id'];
  624. if (!isset($item_key_list[$key][$rootId])) {
  625. $item_key_list[$key][$rootId] = [
  626. 'id' => $rootId,
  627. 'total_amount' => 0,
  628. 'entrust1_amount' => 0, //委内
  629. 'entrust2_amount' => 0, //委外
  630. ];
  631. }
  632. if($item['entrust_type'] == 1){
  633. $item_key_list[$key][$rootId]['entrust1_amount'] += $item['amount'];
  634. }elseif ($item['entrust_type'] == 2){
  635. $item_key_list[$key][$rootId]['entrust2_amount'] += $item['amount'];
  636. }
  637. $item_key_list[$key][$rootId]['total_amount'] += $item['amount'];
  638. //这边需要拿到头部所有的一级费用类型
  639. if(!isset($type_list[$rootId])){
  640. $type_list[$rootId] = [
  641. 'sort' => $sort,
  642. 'id' => $rootId,
  643. 'title' => $title,
  644. ];
  645. }
  646. }
  647. // 使用 values() 丢弃原始键名,重新从 0 开始建立索引
  648. $type_list = collect($type_list)->sortBy('sort')->values()->all();
  649. // 4. 重置数组索引并返回
  650. return [$item_key_list,$type_list];
  651. }
  652. public function auxiliaryStatistic($data,$user){
  653. list($status, $month_start, $month_end) = $this->commonRule($data);
  654. if (!$status) return [false, $month_start];
  655. //项目编码、项目名称、项目状态、凭证日期、凭证种类、凭证号数、凭证摘要、会计凭证归集金额、N个一级费用类型、委内、委外
  656. //确认所有项目
  657. $item = Item::Clear($user, $data);
  658. $item_list = $item->where('del_time', 0)
  659. ->where(function ($query) use ($month_start, $month_end) {
  660. $query->where('start_time', '<=', $month_start)
  661. ->orWhere('end_time', '>', $month_end);
  662. })->select("code", "title", "start_time", "end_time", "id", "state")->orderby("start_time","asc")->get()->toArray();
  663. $item_key_list = [];
  664. foreach ($item_list as $v) {
  665. $item_key_list[$v['id']] = $v;
  666. }
  667. //获取该区间内所有项目人工费、折旧费
  668. $item_salary = $this->auxiliaryEmployee($user,$data,$month_start,$month_end);
  669. $device_depreciation = $this->auxiliaryDevice($user,$data,$month_start,$month_end);
  670. $fee = Fee::Clear($user, $data);
  671. $fee = $fee->where('del_time', 0)->orderBy("sort", 'desc')->get()->toArray();
  672. $auxiliary = AuxiliaryAccountDetails::Clear($user, $data);
  673. $auxiliary_list = $auxiliary->where("voucher_date", ">=", $month_start)->where("voucher_date", "<", $month_end)
  674. ->where('del_time', 0)
  675. ->select(
  676. "item_id",
  677. "voucher_date",
  678. "voucher_type",
  679. "voucher_no",
  680. "voucher_remark",
  681. "voucher_amount",
  682. "aggregation_amount",
  683. "entrust_type",
  684. "entrust1_amount",
  685. "entrust2_amount",
  686. "entrust2_amount",
  687. "total_amount",
  688. "fee_id",
  689. "type"
  690. )->get()->toArray();
  691. list($fee_amount,$fee_type_list) = $this->auxiliaryGroupListByRoot($auxiliary_list,$fee);
  692. //找到项目和设备的费用然后进行比例计算
  693. $return = [];
  694. foreach ($fee_amount as $v){
  695. //人工费用
  696. if($v['type'] == 1||$v['type'] == 2){
  697. foreach ($item_key_list as $vv){
  698. $detail = [
  699. "code" => $vv['code'],
  700. "title" => $vv['title'],
  701. "state" => $vv['state'] == 3 ? "进行中":"已完成" ,
  702. "voucher_date" => date("Y-m-d",$v['voucher_date']) ,
  703. "voucher_type" => $v['voucher_type'] ,
  704. "voucher_no" => $v['voucher_no'] ,
  705. "voucher_remark" => $v['voucher_remark'] ,
  706. "voucher_amount" => $v['voucher_amount'] ,
  707. "aggregation_amount" => $v['aggregation_amount'] ,
  708. "total_amount" => $v['type'] == 1 ? $item_salary[$vv['id']."_".date("Y-m",$v['voucher_date'])]['allocated_salary'] : ($device_depreciation[$vv['id']."_".date("Y-m",$v['voucher_date'])]['allocated_depreciation']??0) ,
  709. "fee_id" => $v['fee_id'],
  710. "entrust1_amount" => $v['entrust1_amount'],
  711. "entrust2_amount" => $v['entrust2_amount'],
  712. "type" => $v['type'],
  713. ];
  714. $return[] = $detail;
  715. }
  716. }else{
  717. if(!isset($item_key_list[$v['item_id']])) continue;
  718. $item_value = $item_key_list[$v['item_id']];
  719. $detail = [
  720. "code" => $item_value['code'],
  721. "title" => $item_value['title'],
  722. "state" => $item_value['state'] == 3 ? "进行中":"已完成" ,
  723. "voucher_date" => date("Y-m-d",$v['voucher_date']) ,
  724. "voucher_type" => $v['voucher_type'] ,
  725. "voucher_no" => $v['voucher_no'] ,
  726. "voucher_remark" => $v['voucher_remark'] ,
  727. "voucher_amount" => $v['voucher_amount'] ,
  728. "aggregation_amount" => $v['aggregation_amount'] ,
  729. "total_amount" => $v['total_amount'] ,
  730. "fee_id" => $v['fee_id'],
  731. "entrust1_amount" => $v['entrust1_amount'],
  732. "entrust2_amount" => $v['entrust2_amount'],
  733. "type" => $v['type'],
  734. ];
  735. $return[] = $detail;
  736. }
  737. }
  738. return [true,[
  739. 'fee_type_list' => $fee_type_list,
  740. 'list' => $return,
  741. ]];
  742. }
  743. /**
  744. * 将报销明细按照一级费用和项目分类进行分组
  745. * * @param array $list 报销明细列表 (含 fee_id, amount 等)
  746. * @param array $fee_type_list 费用类型树 (含 id, parent_id, title)
  747. * @return array
  748. */
  749. public function auxiliaryGroupListByRoot(array $list, array $fee_type_list)
  750. {
  751. // 1. 建立 ID 索引,方便快速查找
  752. $idMap = array_column($fee_type_list, null, 'id');
  753. // 2. 预处理映射表:让所有子 ID 直接指向它的最顶层“祖宗” ID
  754. $childToRoot = [];
  755. foreach ($fee_type_list as $type) {
  756. $current = $type;
  757. // 向上追溯直到 parent_id 为 0,即找到一级分类
  758. while ($current['parent_id'] != 0) {
  759. $current = $idMap[$current['parent_id']];
  760. }
  761. $childToRoot[$type['id']] = [
  762. 'id' => $current['id'],
  763. 'title' => $current['title'],
  764. 'sort' => $current['sort']
  765. ];
  766. }
  767. // 3. 遍历明细数据进行分组
  768. $type_list = [];
  769. foreach ($list as $k=>$item) {
  770. if($item['item_id'] == 0||$item['fee_id'] == 0) {
  771. continue;
  772. }
  773. $feeId = $item['fee_id'];
  774. // 获取该费用对应的一级分类信息
  775. if (!isset($childToRoot[$feeId])) continue;
  776. $rootId = $childToRoot[$feeId]['id'];
  777. $title = $childToRoot[$feeId]['title'];
  778. $sort = $childToRoot[$feeId]['sort'];
  779. $item['fee_id'] = $rootId;
  780. $list[$k] = $item;
  781. //这边需要拿到头部所有的一级费用类型
  782. if(!isset($type_list[$rootId])){
  783. $type_list[$rootId] = [
  784. 'sort' => $sort,
  785. 'id' => $rootId,
  786. 'title' => $title,
  787. ];
  788. }
  789. }
  790. // 使用 values() 丢弃原始键名,重新从 0 开始建立索引
  791. $type_list = collect($type_list)->sortBy('sort')->values()->all();
  792. // 4. 重置数组索引并返回
  793. return [$list,$type_list];
  794. }
  795. public function auxiliaryEmployee($user,$data,$month_start,$month_end){
  796. //确认所有项目、人员、人员工时
  797. $month_employee = DailyPwOrderDetails::Clear($user, $data);
  798. $month_employee_list = $month_employee->where("order_time", ">=", $month_start)->where("order_time", "<", $month_end)
  799. ->where('del_time', 0)
  800. ->select(
  801. "item_id",
  802. "employee_id",
  803. // 将时间戳转为 Y-m-d 格式并起别名
  804. DB::raw("FROM_UNIXTIME(order_time, '%Y-%m') as order_month"),
  805. // 聚合求和
  806. DB::raw("SUM(total_work_min) as total_work")
  807. )
  808. ->groupBy("order_month", "item_id", "employee_id")->get()->toArray();
  809. //查询所有人员工资
  810. $monthly_ps_order_ids = MonthlyPsOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)
  811. ->pluck('id')->toArray();
  812. $monthly_ps_order_key_list = MonthlyPsOrder::Clear($user, $data)
  813. ->where('del_time', 0)
  814. ->where("month", ">=", $month_start)
  815. ->where("month", "<", $month_end)
  816. ->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
  817. ->pluck('month_str', 'id')
  818. ->toArray();
  819. $month_employee_salary = MonthlyPsOrderDetails::wherein('main_id', $monthly_ps_order_ids)
  820. ->select("employee_id", "salary", "main_id")
  821. ->get()->toArray();
  822. //查询所有项目人员的工时比例
  823. //汇总每个人每个月工资
  824. $salary_map = [];
  825. foreach ($month_employee_salary as $val) {
  826. $month = $monthly_ps_order_key_list[$val['main_id']] ?? '';
  827. if ($month) {
  828. $salary_map[$val['employee_id'] . '_' . $month] = $val['salary'];
  829. }
  830. }
  831. // var_dump($salary_map);die;
  832. // 2. 计算每个员工在每个月的全月总工时
  833. $employee_monthly_total_min = [];
  834. foreach ($month_employee_list as $row) {
  835. $key = $row['employee_id'] . '_' . $row['order_month'];
  836. if (!isset($employee_monthly_total_min[$key])) {
  837. $employee_monthly_total_min[$key] = 0;
  838. }
  839. $employee_monthly_total_min[$key] += $row['total_work'];
  840. }
  841. // 3. 计算分摊天数与工资
  842. $item_year_list = [];
  843. foreach ($month_employee_list as $item) {
  844. $key = $item['employee_id'] . '_' . $item['order_month'];
  845. $item_key = $item['item_id'] . '_' . $item['order_month'];
  846. if (!isset($item_year_list[$item_key])) {
  847. $item_year_list[$item_key] = [
  848. "allocated_salary" => 0,
  849. "item_id" => $item['item_id'],
  850. ];
  851. }
  852. $total_salary = $salary_map[$key] ?? 0;
  853. $total_min = $employee_monthly_total_min[$key] ?? 0;
  854. // B. 计算工资分摊:(项目工时 / 月总工时) * 月工资
  855. if ($total_min > 0) {
  856. $ratio = $item['total_work'] / $total_min;
  857. $allocated_salary = round($ratio * $total_salary, 2);
  858. } else {
  859. $allocated_salary = 0;
  860. }
  861. $item_year_list[$item_key]['allocated_salary'] += $allocated_salary;
  862. }
  863. return $item_year_list;
  864. }
  865. public function auxiliaryDevice($user,$data,$month_start,$month_end){
  866. $month_device = DailyDwOrderDetails::Clear($user, $data);
  867. $month_device_list = $month_device->where("order_time", ">=", $month_start)->where("order_time", "<", $month_end)
  868. ->where('del_time', 0)
  869. ->select(
  870. "item_id",
  871. "device_id",
  872. // 将时间戳转为 Y-m-d 格式并起别名
  873. DB::raw("FROM_UNIXTIME(order_time, '%Y-%m') as order_month"),
  874. // 聚合求和
  875. DB::raw("SUM(total_work_min) as total_work")
  876. )
  877. ->groupBy(DB::raw("FROM_UNIXTIME(order_time, '%Y-%m')"), "item_id", "device_id")->get()->toArray();
  878. //查询所有人员工资
  879. $monthly_dd_order_ids = MonthlyDdOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)
  880. ->pluck('id')->toArray();
  881. $monthly_dd_order_key_list = MonthlyDdOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)
  882. ->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
  883. ->pluck("month_str", 'id')->toArray();
  884. $month_device_salary = MonthlyDdOrderDetails::wherein('main_id', $monthly_dd_order_ids)
  885. ->select("device_id", "depreciation_amount", "main_id")
  886. ->get()->toArray();
  887. //查询所有项目人员的工时比例
  888. //汇总每个人每个月工资
  889. $depreciatio_map = [];
  890. foreach ($month_device_salary as $val) {
  891. $month = $monthly_dd_order_key_list[$val['main_id']] ?? '';
  892. if ($month) {
  893. $depreciatio_map[$val['device_id'] . '_' . $month] = $val['depreciation_amount'];
  894. }
  895. }
  896. // 2. 计算每个员工在每个月的全月总工时
  897. $device_monthly_total_min = [];
  898. foreach ($month_device_list as $row) {
  899. $key = $row['device_id'] . '_' . $row['order_month'];
  900. if (!isset($device_monthly_total_min[$key])) {
  901. $device_monthly_total_min[$key] = 0;
  902. }
  903. $device_monthly_total_min[$key] += $row['total_work'];
  904. }
  905. // 3. 计算分摊天数与工资
  906. $item_year_list = [];
  907. foreach ($month_device_list as $item) {
  908. $key = $item['device_id'] . '_' . $row['order_month'] ;
  909. $device_key = $item['item_id'] . '_' . $item['order_month'];
  910. $total_depreciatio = $depreciatio_map[$key] ?? 0;
  911. $total_min = $device_monthly_total_min[$key] ?? 0;
  912. if (!isset($item_year_list[$device_key])) {
  913. $item_year_list[$device_key] = [
  914. "allocated_depreciation" => 0,
  915. ];
  916. }
  917. // B. 计算工资分摊:(项目工时 / 月总工时) * 月工资
  918. if ($total_min > 0) {
  919. $ratio = round($item['total_work'] / $total_min, 3);
  920. $allocated_salary = round($ratio * $total_depreciatio, 2);
  921. } else {
  922. $allocated_salary = 0;
  923. }
  924. $item_year_list[$device_key]['allocated_depreciation'] += $allocated_salary;
  925. }
  926. return $item_year_list;
  927. }
  928. public function itemEmployeeSalaryStatistic($data,$user){
  929. list($status, $month_start, $month_end) = $this->commonRule($data);
  930. if (!$status) return [false, $month_start];
  931. //项目编码、项目名称、姓名、人员类别、应出勤工时、研发出勤工时、研发工时占比、归集工资总额、归集社保金额、归集公积金、研发工资总额、研发社保金额、研发公积金
  932. //获取人员工资项目相关分组数据
  933. $month_employee = DailyPwOrderDetails::Clear($user, $data);
  934. $month_employee_list = $month_employee->where("order_time", ">=", $month_start)->where("order_time", "<", $month_end)
  935. ->where('del_time', 0)
  936. ->select(
  937. "item_id",
  938. "employee_id",
  939. // 将时间戳转为 Y-m-d 格式并起别名
  940. DB::raw("FROM_UNIXTIME(order_time, '%Y-%m') as order_month"),
  941. // 聚合求和
  942. DB::raw("SUM(total_work_min) as total_work")
  943. )
  944. ->groupBy("order_month", "item_id", "employee_id")->get()->toArray();
  945. //查询所有人员工资
  946. $monthly_ps_order_ids = MonthlyPsOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)
  947. ->pluck('id')->toArray();
  948. $monthly_ps_order_key_list = MonthlyPsOrder::Clear($user, $data)
  949. ->where('del_time', 0)
  950. ->where("month", ">=", $month_start)
  951. ->where("month", "<", $month_end)
  952. ->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
  953. ->pluck('month_str', 'id')
  954. ->toArray();
  955. $month_employee_salary = MonthlyPsOrderDetails::wherein('main_id', $monthly_ps_order_ids)
  956. ->select("employee_id", "salary", "main_id","social_insurance","public_housing_fund")
  957. ->get()->toArray();
  958. //查询所有项目人员的工时比例
  959. //汇总每个人每个月工资
  960. $salary_map = [];
  961. foreach ($month_employee_salary as $val) {
  962. $month = $monthly_ps_order_key_list[$val['main_id']] ?? '';
  963. if ($month) {
  964. $salary_map[$val['employee_id'] . '_' . $month] = $val;
  965. }
  966. }
  967. // var_dump($salary_map);die;
  968. // 2. 计算每个员工在每个月的全月总工时
  969. $employee_monthly_total_min = [];
  970. foreach ($month_employee_list as $row) {
  971. $key = $row['employee_id'] . '_' . $row['order_month'];
  972. if (!isset($employee_monthly_total_min[$key])) {
  973. $employee_monthly_total_min[$key] = 0;
  974. }
  975. $employee_monthly_total_min[$key] += $row['total_work'];
  976. }
  977. // 3. 计算分摊天数与工资
  978. $item_month_list = [];
  979. foreach ($month_employee_list as $item) {
  980. $key = $item['employee_id'] . '_' . $item['order_month'];
  981. $item_key = $item['order_month'] . '_' . $item['item_id'].'_'.$item['employee_id'] ;
  982. if (!isset($item_month_list[$item_key])) {
  983. $item_month_list[$item_key] = [
  984. "month" => $item['order_month'],
  985. "allocated_salary" => 0,
  986. "employee_id" => $item['employee_id'],
  987. "work_minutes" => 0,
  988. "salary" => $salary_map[$key]['salary'] ?? 0,
  989. "social_insurance" => $salary_map[$key]['social_insurance']??0,
  990. "public_housing_fund" => $salary_map[$key]['public_housing_fund']??0,
  991. "item_id" => $item['item_id'],
  992. ];
  993. }
  994. $total_min = $employee_monthly_total_min[$key] ?? 0;
  995. // B. 计算工资分摊:(项目工时 / 月总工时) * 月工资
  996. if ($total_min > 0) {
  997. $ratio = round($item['total_work'] / $total_min,4);
  998. }else{
  999. $ratio = 0;
  1000. }
  1001. $item_month_list[$item_key]['radio'] = $ratio;
  1002. $item_month_list[$item_key]['total_min'] = $total_min;
  1003. $item_month_list[$item_key]['work_minutes'] += $item['total_work'];
  1004. }
  1005. $item_month_list = collect($item_month_list)->sortBy('month')->values()->all();
  1006. $items = collect($item_month_list)->pluck('item_id')->unique()->values()->all();
  1007. $employee_ids = collect($item_month_list)->pluck('employee_id')->unique()->values()->all();
  1008. $employee = Employee::Clear($user, $data);
  1009. $employee_list = $employee->wherein('id', $employee_ids)->select("major","title", "id")->get()->toArray();
  1010. $employee_key_list = [];
  1011. foreach ($employee_list as $v){
  1012. $employee_key_list[$v['id']] = $v;
  1013. }
  1014. $item = Item::Clear($user, $data);
  1015. $item_title_key_list = $item->wherein('id', $items)->pluck("title", "id")->toArray();
  1016. $item_code_key_list = $item->wherein('id', $items)->pluck("code", "id")->toArray();
  1017. //项目编码、项目名称、姓名、人员类别、应出勤工时、研发出勤工时、研发工时占比、归集工资总额、归集社保金额、归集公积金、研发工资总额、研发社保金额、研发公积金
  1018. $item_month_list = collect($item_month_list)->transform(function ($item) use ($item_title_key_list, $item_code_key_list,$employee_key_list) {
  1019. $item['item_title'] = $item_title_key_list[$item['item_id']] ?? "未知项目({$item['item_id']})";
  1020. $item['item_code'] = $item_code_key_list[$item['item_id']] ?? "未知项目({$item['item_id']})";
  1021. $item['employee_title'] = $employee_key_list[$item['employee_id']]['title'] ?? "未知人员({$item['employee_id']})";
  1022. $item['major'] = $employee_key_list[$item['employee_id']]['major'] ?? "未知人员({$item['employee_id']})";
  1023. $item['total_min'] = round($item['total_min']/60,2);
  1024. $item['work_minutes'] = round($item['work_minutes']/60,2);
  1025. $item['work_salary'] = round($item['salary']*$item['radio'],2);
  1026. $item['work_social_insurance'] = round($item['social_insurance']*$item['radio'],2);
  1027. $item['work_public_housing_fund'] = round($item['public_housing_fund']*$item['radio'],2);
  1028. return $item;
  1029. })->all();
  1030. return [true, $item_month_list];
  1031. }
  1032. }