UnifiedCostCalculationService.php 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447
  1. <?php
  2. namespace app\service;
  3. use think\Db;
  4. use think\Exception;
  5. use think\Log;
  6. /**
  7. * 统一成本核算服务类
  8. * 五项计算完成后统一插入数据库
  9. */
  10. class UnifiedCostCalculationService
  11. {
  12. // 存储中间数据的数组
  13. protected $monthlyCostDetails = [];
  14. protected $allocationFactors = [];
  15. // 配置常量
  16. const FLOOR_GROUP_MAP = [
  17. '1' => ['02、胶印机组', '03、卷凹机组', '06、单凹机组', '05、圆切机组', '04、圆烫机组', '10、模切机组', '09、烫金机组'],
  18. '2' => ['01、切纸机组', '11、检品机组', '07、丝印机组', '12、覆膜机组', '08、喷码机组'],
  19. ];
  20. // 科目名称映射
  21. const SUBJECT_MAPPING = [
  22. '废气处理' => '废气处理',
  23. '锅炉' => '锅炉',
  24. '空压机' => '空压机',
  25. '热水锅炉' => '热水锅炉',
  26. '真空鼓风机' => '真空鼓风机',
  27. '中央空调' => '中央空调',
  28. '待分摊总额' => '待分摊总额',
  29. ];
  30. // 字段默认值
  31. const DEFAULT_FIELD_VALUES = [
  32. '直接水电' => 0,
  33. '分摊材料' => 0,
  34. '车间人工' => 0,
  35. '部门人工附加' => 0,
  36. '分摊水电' => 0,
  37. '废气处理' => 0,
  38. '锅炉' => 0,
  39. '空压机' => 0,
  40. '热水锅炉' => 0,
  41. '真空鼓风机' => 0,
  42. '中央空调' => 0,
  43. '分摊其他' => 0,
  44. 'A_小时折旧额' => 0,
  45. '直接材料' => 0,
  46. '考核直接材料' => 0,
  47. '直接折旧' => 0,
  48. '分摊折旧' => 0,
  49. '场地租金' => 0,
  50. '成本合计' => 0,
  51. ];
  52. // 批次大小
  53. const BATCH_SIZE = 100;
  54. /**
  55. * 主入口:执行所有成本计算并统一入库
  56. */
  57. public function calculateAndSaveAll(array $param): array
  58. {
  59. Log::info("开始成本计算,参数: " . json_encode($param));
  60. Db::startTrans();
  61. try {
  62. if (!isset($param['month'])) {
  63. throw new Exception("缺少必要参数: month");
  64. }
  65. $month = $param['month'];
  66. $sysId = $param['sys_id'] ?? uniqid();
  67. Log::info("开始清空旧数据");
  68. $this->clearOldData($month);
  69. Log::info("开始计算直接人工");
  70. $this->calculateDirectLabor($param);
  71. Log::info("直接人工计算完成,记录数: " . count($this->monthlyCostDetails));
  72. Log::info("开始计算直接水电");
  73. $this->calculateDirectUtilities($param);
  74. Log::info("开始计算间接材料");
  75. $this->calculateIndirectMaterials($month);
  76. Log::info("开始计算间接人工");
  77. $this->calculateIndirectLabor($month);
  78. Log::info("开始计算分摊水电");
  79. $this->calculateApportionedUtilities($param);
  80. Log::info("开始保存数据");
  81. $this->saveAllData($month, $sysId);
  82. Db::commit();
  83. $this->logSuccess($month);
  84. return $this->buildSuccessResponse($month);
  85. } catch (\Throwable $t) { // 使用 Throwable 而不是 Exception
  86. Db::rollback();
  87. // 记录详细的错误信息
  88. $errorInfo = [
  89. 'message' => $t->getMessage(),
  90. 'code' => $t->getCode(),
  91. 'file' => $t->getFile(),
  92. 'line' => $t->getLine(),
  93. 'trace' => $t->getTraceAsString(),
  94. 'param' => $param
  95. ];
  96. Log::error("统一成本核算失败: " . json_encode($errorInfo, JSON_UNESCAPED_UNICODE));
  97. return [
  98. 'success' => false,
  99. 'message' => '成本核算失败: ' . $t->getMessage(),
  100. 'error' => $t->getMessage(),
  101. 'error_details' => $errorInfo
  102. ];
  103. }
  104. }
  105. /**
  106. * 清空旧数据
  107. */
  108. protected function clearOldData(string $month): void
  109. {
  110. $this->monthlyCostDetails = [];
  111. $this->allocationFactors = [];
  112. }
  113. /**
  114. * 1. 计算直接人工
  115. */
  116. protected function calculateDirectLabor(array $param): void
  117. {
  118. try {
  119. $month = $param['month'];
  120. $sysId = $param['sys_id'] ?? '';
  121. $now = date('Y-m-d H:i:s');
  122. $list = Db::name('绩效工资汇总')
  123. ->alias('a')
  124. ->join('工单_工艺资料 b', 'a.sczl_gdbh = b.Gy0_gdbh and a.sczl_yjno = b.Gy0_yjno and a.sczl_gxh = b.Gy0_gxh')
  125. ->join('设备_基本资料 c', 'a.sczl_jtbh = c.设备编号', 'LEFT')
  126. ->join('工单_印件资料 d', 'a.sczl_gdbh = d.Yj_Gdbh and a.sczl_yjno = d.yj_Yjno')
  127. ->field($this->getDirectLaborFields())
  128. ->where('a.sys_ny', $month)
  129. ->group('a.sczl_gdbh,a.sczl_yjno,a.sczl_gxh,a.sczl_jtbh')
  130. ->select();
  131. // 转换为数组(如果返回的是对象)
  132. if (!is_array($list)) {
  133. $list = $list->toArray();
  134. }
  135. Log::info("直接人工查询结果数: " . count($list));
  136. foreach ($list as $item) {
  137. // 确保 $item 是数组
  138. $itemArray = is_object($item) ? (array)$item : $item;
  139. if (!is_array($itemArray)) {
  140. Log::warning("直接人工数据项格式不正确: " . gettype($item));
  141. continue;
  142. }
  143. $this->monthlyCostDetails[] = $this->buildMonthlyCostDetail($itemArray, $month, $sysId, $now);
  144. }
  145. } catch (\Exception $e) {
  146. Log::error("计算直接人工失败: " . $e->getMessage());
  147. throw new Exception("直接人工计算失败: " . $e->getMessage());
  148. }
  149. }
  150. /**
  151. * 获取直接人工查询字段
  152. */
  153. protected function getDirectLaborFields(): string
  154. {
  155. return 'a.sczl_gdbh as 工单编号,a.sczl_yjno as 印件号,a.sczl_gxh as 工序号,
  156. sum(a.班组车头产量) as 班组车头产量,b.Gy0_gxmc as 工序名称,
  157. a.sczl_ms as 墨色数,c.使用部门,b.印刷方式,b.版距,b.工价系数,
  158. a.sczl_jtbh,d.yj_yjmc as 印件名称,sum(a.车头产量占用机时) as 占用机时,
  159. a.sys_rq as 年月,a.工序难度系数,sum(a.班组换算产量) as 班组换算产量,
  160. a.千件工价';
  161. }
  162. /**
  163. * 构建月度成本明细记录
  164. */
  165. protected function buildMonthlyCostDetail(array $data, string $month, string $sysId, string $now): array
  166. {
  167. $banju = $data['版距'] === '0.0' ? 1000 : $data['版距'];
  168. $moshushu = $this->calculateMoshushu($data);
  169. $chanliang = $this->calculateChanliang($data);
  170. $renGongFenTan = $this->calculateRenGongFenTan($chanliang, $data);
  171. return array_merge([
  172. '车间名称' => $data['使用部门'] ?? '',
  173. 'sys_ny' => $month,
  174. 'sczl_gdbh' => $data['工单编号'],
  175. '印件名称' => $data['印件名称'] ?? '',
  176. 'sczl_yjno' => $data['印件号'],
  177. 'sczl_gxh' => $data['工序号'],
  178. '工序名称' => $data['工序名称'] ?? '',
  179. 'sczl_jtbh' => $data['sczl_jtbh'] ?? '',
  180. '卷张换算系数' => floatval($banju) / 1000,
  181. '占用机时' => floatval($data['占用机时']) ?? 0,
  182. '班组车头产量' => floatval($data['班组车头产量']) ?? 0,
  183. 'sczl_ms' => floatval($moshushu),
  184. '工序难度系数' => floatval($data['工序难度系数']) ?? 1,
  185. '班组换算产量' => floatval($data['班组换算产量']) ?? 0,
  186. '千件工价' => floatval($data['千件工价']) ?? 0,
  187. '计件产量' => floatval($chanliang),
  188. '水电分摊因子' => floatval($data['占用机时']) ?? 0,
  189. '材料分摊因子' => floatval($chanliang),
  190. '人工分摊因子' => floatval($renGongFenTan),
  191. 'Sys_id' => $sysId,
  192. 'Sys_rq' => $now
  193. ], self::DEFAULT_FIELD_VALUES);
  194. }
  195. /**
  196. * 计算墨色数
  197. */
  198. protected function calculateMoshushu(array $data): float
  199. {
  200. if (strpos($data['工序名称'] ?? '', '切废') !== false) {
  201. return 0.2;
  202. }
  203. return $data['墨色数'] === '0.00' ? 1.0 : floatval($data['墨色数']);
  204. }
  205. /**
  206. * 计算产量
  207. */
  208. protected function calculateChanliang(array $data): float
  209. {
  210. return ($data['班组车头产量'] * $data['工序难度系数']) + $data['班组换算产量'];
  211. }
  212. /**
  213. * 计算人工分摊
  214. */
  215. protected function calculateRenGongFenTan(float $chanliang, array $data): float
  216. {
  217. return ($chanliang / 1000) * $data['千件工价'];
  218. }
  219. /**
  220. * 2. 计算直接水电
  221. */
  222. protected function calculateDirectUtilities(array $param): void
  223. {
  224. $month = $param['month'];
  225. if (empty($this->monthlyCostDetails)) {
  226. throw new Exception("请先执行直接人工计算");
  227. }
  228. $utilityData = $this->fetchDirectUtilities($month);
  229. if (empty($utilityData)) {
  230. Log::info("{$month}月份未找到直接水电费用数据");
  231. return;
  232. }
  233. $machineUtilities = $this->calculateMachineUtilities($utilityData);
  234. $workOrderHours = $this->getMachineWorkHours();
  235. $machineTotalHours = $this->calculateMachineTotalHours($workOrderHours);
  236. $allocationResults = $this->allocateUtilitiesToWorkOrders(
  237. $machineUtilities,
  238. $workOrderHours,
  239. $machineTotalHours
  240. );
  241. $this->updateDirectUtilitiesToCostDetails($allocationResults);
  242. }
  243. /**
  244. * 获取直接水电数据
  245. */
  246. protected function fetchDirectUtilities(string $month): array
  247. {
  248. return Db::name('成本_各月水电气')
  249. ->where('Sys_ny', $month)
  250. ->where('费用类型', '直接')
  251. ->field('设备编号, 部门名称, 科目名称, 耗电量, 单位电价, 耗气量, 单位气价')
  252. ->select();
  253. }
  254. /**
  255. * 计算机台水电费用
  256. */
  257. protected function calculateMachineUtilities(array $utilityData): array
  258. {
  259. $machineUtilities = [];
  260. foreach ($utilityData as $item) {
  261. $machineCode = $item['设备编号'] ?? '';
  262. if (empty($machineCode)) {
  263. continue;
  264. }
  265. $electricityCost = $this->calculateElectricityCost($item);
  266. $gasCost = $this->calculateGasCost($item);
  267. $totalCost = $electricityCost + $gasCost;
  268. if (!isset($machineUtilities[$machineCode])) {
  269. $machineUtilities[$machineCode] = [
  270. '机器编号' => $machineCode,
  271. '部门名称' => $item['部门名称'] ?? '',
  272. '总费用' => 0,
  273. '电费' => 0,
  274. '气费' => 0,
  275. ];
  276. }
  277. $machineUtilities[$machineCode]['总费用'] += $totalCost;
  278. $machineUtilities[$machineCode]['电费'] += $electricityCost;
  279. $machineUtilities[$machineCode]['气费'] += $gasCost;
  280. }
  281. return $machineUtilities;
  282. }
  283. /**
  284. * 计算电费
  285. */
  286. protected function calculateElectricityCost(array $item): float
  287. {
  288. return round(floatval($item['耗电量'] ?? 0) * floatval($item['单位电价'] ?? 0), 2);
  289. }
  290. /**
  291. * 计算气费
  292. */
  293. protected function calculateGasCost(array $item): float
  294. {
  295. return round(floatval($item['耗气量'] ?? 0) * floatval($item['单位气价'] ?? 0), 2);
  296. }
  297. /**
  298. * 获取机台工作小时数
  299. */
  300. protected function getMachineWorkHours(): array
  301. {
  302. $workHours = [];
  303. foreach ($this->monthlyCostDetails as $detail) {
  304. try {
  305. // 确保 $detail 是数组
  306. $detailArray = is_object($detail) ? (array)$detail : $detail;
  307. if (!is_array($detailArray)) {
  308. Log::warning("成本明细数据格式不正确: " . gettype($detail));
  309. continue;
  310. }
  311. $machineCode = $detailArray['sczl_jtbh'] ?? '';
  312. $hours = floatval($detailArray['占用机时'] ?? 0);
  313. if (!empty($machineCode) && $hours > 0) {
  314. $workHours[] = [
  315. 'sczl_gdbh' => $detailArray['sczl_gdbh'] ?? '',
  316. 'sczl_yjno' => $detailArray['sczl_yjno'] ?? '',
  317. 'sczl_gxh' => $detailArray['sczl_gxh'] ?? '',
  318. 'sczl_jtbh' => $machineCode,
  319. '占用机时' => $hours,
  320. '车间名称' => $detailArray['车间名称'] ?? '',
  321. ];
  322. }
  323. } catch (\Exception $e) {
  324. Log::error("处理机台工作小时数时出错: " . $e->getMessage());
  325. continue;
  326. }
  327. }
  328. Log::info("从月度成本明细数据中获取机台运行时间,记录数: " . count($workHours));
  329. return $workHours;
  330. }
  331. /**
  332. * 计算机台总小时数
  333. */
  334. protected function calculateMachineTotalHours(array $workOrderHours): array
  335. {
  336. $machineTotalHours = [];
  337. foreach ($workOrderHours as $workOrder) {
  338. $machineCode = $workOrder['sczl_jtbh'] ?? '';
  339. $hours = floatval($workOrder['占用机时'] ?? 0);
  340. if (!empty($machineCode) && $hours > 0) {
  341. $machineTotalHours[$machineCode] = ($machineTotalHours[$machineCode] ?? 0) + $hours;
  342. }
  343. }
  344. return $machineTotalHours;
  345. }
  346. /**
  347. * 分摊水电到工单
  348. */
  349. protected function allocateUtilitiesToWorkOrders(
  350. array $machineUtilities,
  351. array $workOrderHours,
  352. array $machineTotalHours
  353. ): array {
  354. $allocationResults = [];
  355. $processedCount = 0;
  356. $skippedCount = 0;
  357. foreach ($workOrderHours as $workOrder) {
  358. try {
  359. // 确保 $workOrder 是数组
  360. $orderArray = is_object($workOrder) ? (array)$workOrder : $workOrder;
  361. if (!is_array($orderArray)) {
  362. Log::warning("工单小时数据格式不正确: " . gettype($workOrder));
  363. $skippedCount++;
  364. continue;
  365. }
  366. $machineCode = $orderArray['sczl_jtbh'] ?? '';
  367. $hours = floatval($orderArray['占用机时'] ?? 0);
  368. if (empty($machineCode)) {
  369. Log::debug("工单缺少机器编号: " . json_encode($orderArray));
  370. $skippedCount++;
  371. continue;
  372. }
  373. if (!isset($machineUtilities[$machineCode])) {
  374. Log::debug("机器 {$machineCode} 没有水电费用数据");
  375. $skippedCount++;
  376. continue;
  377. }
  378. if ($machineUtilities[$machineCode]['总费用'] <= 0) {
  379. Log::debug("机器 {$machineCode} 水电费用为0");
  380. $skippedCount++;
  381. continue;
  382. }
  383. if (!isset($machineTotalHours[$machineCode]) || $machineTotalHours[$machineCode] <= 0) {
  384. Log::warning("机器 {$machineCode} 总运行时间为0,无法分摊");
  385. $skippedCount++;
  386. continue;
  387. }
  388. $allocationRatio = $hours / $machineTotalHours[$machineCode];
  389. $allocatedAmount = round($machineUtilities[$machineCode]['总费用'] * $allocationRatio, 2);
  390. $uniqueKey = $this->getWorkOrderUniqueKey($orderArray);
  391. $allocationResults[$uniqueKey] = [
  392. 'unique_key' => $uniqueKey,
  393. 'sczl_gdbh' => $orderArray['sczl_gdbh'] ?? '',
  394. 'sczl_yjno' => $orderArray['sczl_yjno'] ?? '',
  395. 'sczl_gxh' => $orderArray['sczl_gxh'] ?? '',
  396. 'sczl_jtbh' => $machineCode,
  397. '占用机时' => $hours,
  398. '分摊比例' => $allocationRatio,
  399. '分摊金额' => $allocatedAmount,
  400. '机台总费用' => $machineUtilities[$machineCode]['总费用'],
  401. '机台总工时' => $machineTotalHours[$machineCode],
  402. ];
  403. $processedCount++;
  404. } catch (\Exception $e) {
  405. Log::error("分摊水电到工单时出错: " . $e->getMessage());
  406. $skippedCount++;
  407. continue;
  408. }
  409. }
  410. Log::info("分摊水电处理完成,成功: {$processedCount}, 跳过: {$skippedCount}, 总计: " . count($workOrderHours));
  411. return $allocationResults;
  412. }
  413. /**
  414. * 获取工单唯一键
  415. */
  416. protected function getWorkOrderUniqueKey($workOrder): string
  417. {
  418. // 记录传入参数的类型和内容(用于调试)
  419. $type = gettype($workOrder);
  420. Log::debug("getWorkOrderUniqueKey 接收参数类型: {$type}");
  421. // 如果是对象,转换为数组
  422. if (is_object($workOrder)) {
  423. $workOrder = (array)$workOrder;
  424. }
  425. // 确保是数组
  426. if (!is_array($workOrder)) {
  427. Log::error("getWorkOrderUniqueKey 参数不是数组: {$type}, 值: " . print_r($workOrder, true));
  428. return 'invalid-' . uniqid();
  429. }
  430. // 调试:记录数组内容
  431. if (count($workOrder) < 4) {
  432. Log::debug("getWorkOrderUniqueKey 数组内容: " . json_encode($workOrder));
  433. }
  434. // 安全地获取所有可能键名
  435. $gdbh = '';
  436. $yjno = '';
  437. $gxh = '';
  438. $jtbh = '';
  439. // 尝试各种可能的键名
  440. if (isset($workOrder['sczl_gdbh'])) {
  441. $gdbh = $workOrder['sczl_gdbh'];
  442. } elseif (isset($workOrder['工单编号'])) {
  443. $gdbh = $workOrder['工单编号'];
  444. }
  445. if (isset($workOrder['sczl_yjno'])) {
  446. $yjno = $workOrder['sczl_yjno'];
  447. } elseif (isset($workOrder['印件号'])) {
  448. $yjno = $workOrder['印件号'];
  449. }
  450. if (isset($workOrder['sczl_gxh'])) {
  451. $gxh = $workOrder['sczl_gxh'];
  452. } elseif (isset($workOrder['工序号'])) {
  453. $gxh = $workOrder['工序号'];
  454. }
  455. if (isset($workOrder['sczl_jtbh'])) {
  456. $jtbh = $workOrder['sczl_jtbh'];
  457. } elseif (isset($workOrder['机器编号'])) {
  458. $jtbh = $workOrder['机器编号'];
  459. }
  460. // 验证所有字段都不为空
  461. if (empty($gdbh) || empty($yjno) || empty($gxh) || empty($jtbh)) {
  462. Log::warning("工单唯一键字段不完整: gdbh={$gdbh}, yjno={$yjno}, gxh={$gxh}, jtbh={$jtbh}");
  463. }
  464. $key = sprintf('%s-%s-%s-%s', $gdbh, $yjno, $gxh, $jtbh);
  465. Log::debug("生成的唯一键: {$key}");
  466. return $key;
  467. }
  468. /**
  469. * 更新直接水电到成本明细
  470. */
  471. protected function updateDirectUtilitiesToCostDetails(array $allocationResults): void
  472. {
  473. if (empty($allocationResults) || empty($this->monthlyCostDetails)) {
  474. Log::warning("水电费分配结果或成本明细数据为空,无法更新");
  475. return;
  476. }
  477. $updatedCount = 0;
  478. $totalAllocatedAmount = 0;
  479. $notFoundCount = 0;
  480. foreach ($this->monthlyCostDetails as &$costDetail) {
  481. try {
  482. // 确保 $costDetail 是数组
  483. $detailArray = is_object($costDetail) ? (array)$costDetail : $costDetail;
  484. $uniqueKey = $this->getWorkOrderUniqueKey($detailArray);
  485. if (isset($allocationResults[$uniqueKey])) {
  486. $allocatedAmount = $allocationResults[$uniqueKey]['分摊金额'] ?? 0;
  487. $costDetail['直接水电'] = $allocatedAmount;
  488. $totalAllocatedAmount += $allocatedAmount;
  489. $updatedCount++;
  490. } else {
  491. $notFoundCount++;
  492. }
  493. } catch (\Exception $e) {
  494. Log::error("更新直接水电费时出错: " . $e->getMessage());
  495. continue;
  496. }
  497. }
  498. Log::info("已更新直接水电费的工单数量: {$updatedCount}, 未找到的工单数: {$notFoundCount}, 分配总金额: {$totalAllocatedAmount}");
  499. // 如果有大量未找到的工单,记录详细信息
  500. if ($notFoundCount > 0 && $notFoundCount > $updatedCount) {
  501. Log::warning("有大量工单未匹配到水电费分配结果,可能键名不匹配");
  502. }
  503. }
  504. /**
  505. * 3. 计算间接材料分摊
  506. */
  507. protected function calculateIndirectMaterials(string $month): void
  508. {
  509. if (empty($this->monthlyCostDetails)) {
  510. return;
  511. }
  512. $date = substr($month, 0, 4) . '-' . substr($month, 4, 2);
  513. $totalMoney = $this->getIndirectMaterialTotal($date);
  514. if (!$totalMoney || $totalMoney <= 0) {
  515. return;
  516. }
  517. $totalChroma = $this->calculateTotalChroma();
  518. if ($totalChroma <= 0) {
  519. return;
  520. }
  521. $this->allocateIndirectMaterials($totalMoney, $totalChroma);
  522. }
  523. /**
  524. * 获取间接材料总额
  525. */
  526. protected function getIndirectMaterialTotal(string $date): float
  527. {
  528. $result = Db::name('材料出库单列表')
  529. ->where([
  530. '出库日期' => ['like', $date . '%'],
  531. '部门' => '印刷成本中心'
  532. ])
  533. ->whereNull('表体生产订单号')
  534. ->field('SUM(金额) as money')
  535. ->find();
  536. return $result ? floatval($result['money']) : 0;
  537. }
  538. /**
  539. * 计算总色度数
  540. */
  541. protected function calculateTotalChroma(): float
  542. {
  543. $totalChroma = 0;
  544. foreach ($this->monthlyCostDetails as $detail) {
  545. $totalChroma += floatval($detail['班组车头产量']) * floatval($detail['sczl_ms']);
  546. }
  547. return $totalChroma;
  548. }
  549. /**
  550. * 分摊间接材料
  551. */
  552. protected function allocateIndirectMaterials(float $totalMoney, float $totalChroma): void
  553. {
  554. foreach ($this->monthlyCostDetails as &$detail) {
  555. $chroma = floatval($detail['班组车头产量']) * floatval($detail['sczl_ms']);
  556. $money = round($totalMoney * ($chroma / $totalChroma), 2);
  557. $detail['分摊材料'] = $money;
  558. }
  559. }
  560. /**
  561. * 4. 计算间接人工分摊
  562. */
  563. protected function calculateIndirectLabor(string $month): void
  564. {
  565. $wageRatio = $this->getWageRatio();
  566. if (empty($wageRatio)) {
  567. return;
  568. }
  569. $monthWage = $this->getMonthlyWage($month);
  570. if (empty($monthWage)) {
  571. return;
  572. }
  573. $this->allocateIndirectLabor($wageRatio, $monthWage);
  574. }
  575. /**
  576. * 获取工资比例
  577. */
  578. protected function getWageRatio(): array
  579. {
  580. $workshopTotals = [];
  581. foreach ($this->monthlyCostDetails as $detail) {
  582. $workshop = $detail['车间名称'];
  583. $amount = floatval($detail['人工分摊因子']);
  584. $workshopTotals[$workshop] = ($workshopTotals[$workshop] ?? 0) + $amount;
  585. }
  586. $total = array_sum($workshopTotals);
  587. if ($total <= 0) {
  588. return [];
  589. }
  590. $ratios = [];
  591. foreach ($workshopTotals as $workshop => $workshopTotal) {
  592. $ratios[$workshop] = round($workshopTotal / $total, 4);
  593. }
  594. return $ratios;
  595. }
  596. /**
  597. * 获取月度工资数据
  598. */
  599. protected function getMonthlyWage(string $month): array
  600. {
  601. return Db::name('成本_各月其他费用')
  602. ->where('sys_ny', $month)
  603. ->field('部门人员工资,管理人员工资')
  604. ->find() ?: [];
  605. }
  606. /**
  607. * 分摊间接人工
  608. */
  609. protected function allocateIndirectLabor(array $wageRatio, array $monthWage): void
  610. {
  611. foreach ($wageRatio as $workshopName => $ratio) {
  612. $chromaData = $this->getChromaDataForWorkshop($workshopName);
  613. if (empty($chromaData['list']) || $chromaData['total'] == 0) {
  614. continue;
  615. }
  616. $this->allocateWageToWorkshop($workshopName, $ratio, $monthWage, $chromaData);
  617. }
  618. }
  619. /**
  620. * 获取车间色度数数据
  621. */
  622. protected function getChromaDataForWorkshop(string $workshop): array
  623. {
  624. $data = ['total' => 0, 'list' => []];
  625. foreach ($this->monthlyCostDetails as $detail) {
  626. if ($detail['车间名称'] === $workshop) {
  627. $chroma = floatval($detail['班组车头产量']) * floatval($detail['sczl_ms']);
  628. $data['total'] += $chroma;
  629. $data['list'][] = [
  630. 'sczl_gdbh' => $detail['sczl_gdbh'],
  631. 'sczl_yjno' => $detail['sczl_yjno'],
  632. 'sczl_gxh' => $detail['sczl_gxh'],
  633. 'sczl_jtbh' => $detail['sczl_jtbh'],
  634. 'chroma' => $chroma
  635. ];
  636. }
  637. }
  638. return $data;
  639. }
  640. /**
  641. * 分配工资到车间
  642. */
  643. protected function allocateWageToWorkshop(
  644. string $workshop,
  645. float $ratio,
  646. array $monthWage,
  647. array $chromaData
  648. ): void {
  649. $wageTypes = [
  650. '部门人员工资' => '车间人工',
  651. '管理人员工资' => '部门人工附加'
  652. ];
  653. foreach ($wageTypes as $wageType => $fieldName) {
  654. if (empty($monthWage[$wageType]) || $monthWage[$wageType] <= 0) {
  655. continue;
  656. }
  657. $workshopAmount = $ratio * $monthWage[$wageType];
  658. if ($chromaData['total'] <= 0) {
  659. continue;
  660. }
  661. $this->allocateWageToDetails($workshop, $workshopAmount, $chromaData, $fieldName);
  662. }
  663. }
  664. /**
  665. * 分配工资到明细记录
  666. */
  667. protected function allocateWageToDetails(
  668. string $workshop,
  669. float $workshopAmount,
  670. array $chromaData,
  671. string $fieldName
  672. ): void {
  673. foreach ($this->monthlyCostDetails as &$detail) {
  674. if ($detail['车间名称'] === $workshop) {
  675. $chroma = floatval($detail['班组车头产量']) * floatval($detail['sczl_ms']);
  676. $amount = round($chroma / $chromaData['total'] * $workshopAmount, 2);
  677. $detail[$fieldName] += $amount;
  678. }
  679. }
  680. }
  681. /**
  682. * 5. 计算分摊水电
  683. */
  684. protected function calculateApportionedUtilities(array $param): void
  685. {
  686. try {
  687. $month = $param['month'];
  688. $sysId = $param['sys_id'] ?? '';
  689. $utilityData = $this->fetchApportionedUtilities($month);
  690. if (empty($utilityData)) {
  691. Log::info("{$month}月份未找到分摊水电数据");
  692. return;
  693. }
  694. $machineAllocations = $this->calculateMachineAllocations($utilityData);
  695. if (!empty($machineAllocations)) {
  696. $this->generateAllocationFactors($machineAllocations, $month, $sysId);
  697. $this->allocateApportionedUtilities($machineAllocations);
  698. }
  699. } catch (\Exception $e) {
  700. Log::error("计算分摊水电时出错: " . $e->getMessage());
  701. throw new Exception("分摊水电计算失败: " . $e->getMessage());
  702. }
  703. }
  704. /**
  705. * 获取分摊水电数据
  706. */
  707. protected function fetchApportionedUtilities(string $month): array
  708. {
  709. return Db::name('成本_各月水电气')
  710. ->where('Sys_ny', $month)
  711. ->whereLike('费用类型', '%分摊%')
  712. ->select();
  713. }
  714. /**
  715. * 计算机台分摊金额
  716. */
  717. protected function calculateMachineAllocations(array $utilityData): array
  718. {
  719. $allocations = [];
  720. $machineHours = $this->getMachineHours();
  721. foreach ($utilityData as $item) {
  722. try {
  723. $subject = $this->simplifySubjectName($item['科目名称']);
  724. $amount = $this->calculateUtilityAmount($item);
  725. if ($amount <= 0) {
  726. continue;
  727. }
  728. $this->allocateBySubject($allocations, $subject, $amount, $machineHours);
  729. } catch (\Exception $e) {
  730. Log::error("计算机台分摊金额时出错: " . $e->getMessage());
  731. continue;
  732. }
  733. }
  734. return $allocations;
  735. }
  736. /**
  737. * 获取机台运行时间
  738. */
  739. protected function getMachineHours(): array
  740. {
  741. $hours = [];
  742. foreach ($this->monthlyCostDetails as $detail) {
  743. $machine = $detail['sczl_jtbh'];
  744. $hour = floatval($detail['占用机时']);
  745. if (!empty($machine)) {
  746. $hours[$machine] = ($hours[$machine] ?? 0) + $hour;
  747. }
  748. }
  749. return $hours;
  750. }
  751. /**
  752. * 按科目分摊
  753. */
  754. protected function allocateBySubject(
  755. array &$allocations,
  756. string $subject,
  757. float $amount,
  758. array $machineHours
  759. ): void {
  760. try {
  761. if ($subject === '待分摊总额') {
  762. $this->allocateByFloor($allocations, $amount, $machineHours);
  763. } elseif (in_array($subject, ['锅炉', '热水锅炉'])) {
  764. $this->allocateToRollCoater($allocations, $subject, $amount, $machineHours);
  765. } else {
  766. $this->allocateGlobally($allocations, $subject, $amount, $machineHours);
  767. }
  768. } catch (\Exception $e) {
  769. Log::error("按科目分摊时出错 (科目: {$subject}): " . $e->getMessage());
  770. }
  771. }
  772. /**
  773. * 按楼层分摊
  774. */
  775. protected function allocateByFloor(array &$allocations, float $amount, array $machineHours): void
  776. {
  777. $floorData = $this->groupMachinesByFloor($machineHours);
  778. if ($floorData['totalHours'] <= 0) {
  779. return;
  780. }
  781. foreach ($floorData['floors'] as $floor => $data) {
  782. if ($data['hours'] <= 0) {
  783. continue;
  784. }
  785. $floorAmount = $amount * ($data['hours'] / $floorData['totalHours']);
  786. $this->allocateToFloorMachines($allocations, $floorAmount, $data['machines'], $machineHours);
  787. }
  788. }
  789. /**
  790. * 按楼层分组机台
  791. */
  792. protected function groupMachinesByFloor(array $machineHours): array
  793. {
  794. $floors = ['1' => ['hours' => 0, 'machines' => []], '2' => ['hours' => 0, 'machines' => []]];
  795. $totalHours = 0;
  796. foreach ($machineHours as $machine => $hours) {
  797. $floor = $this->getFloorByMachine($machine);
  798. if ($floor && isset($floors[$floor])) {
  799. $floors[$floor]['hours'] += $hours;
  800. $floors[$floor]['machines'][] = $machine;
  801. $totalHours += $hours;
  802. }
  803. }
  804. return ['floors' => $floors, 'totalHours' => $totalHours];
  805. }
  806. /**
  807. * 分摊到楼层机台
  808. */
  809. protected function allocateToFloorMachines(
  810. array &$allocations,
  811. float $floorAmount,
  812. array $machines,
  813. array $machineHours
  814. ): void {
  815. $floorHours = 0;
  816. foreach ($machines as $machine) {
  817. $floorHours += $machineHours[$machine];
  818. }
  819. if ($floorHours <= 0) {
  820. return;
  821. }
  822. foreach ($machines as $machine) {
  823. $machineHoursAmount = $machineHours[$machine];
  824. if ($machineHoursAmount <= 0) {
  825. continue;
  826. }
  827. $allocations[$machine]['待分摊总额'] =
  828. ($allocations[$machine]['待分摊总额'] ?? 0) +
  829. round($floorAmount * ($machineHoursAmount / $floorHours), 2);
  830. }
  831. }
  832. /**
  833. * 只分摊到卷凹机组
  834. */
  835. protected function allocateToRollCoater(
  836. array &$allocations,
  837. string $subject,
  838. float $amount,
  839. array $machineHours
  840. ): void {
  841. $rollCoaterMachines = $this->filterRollCoaterMachines(array_keys($machineHours));
  842. $totalHours = 0;
  843. foreach ($rollCoaterMachines as $machine) {
  844. $totalHours += $machineHours[$machine];
  845. }
  846. if ($totalHours <= 0) {
  847. return;
  848. }
  849. foreach ($rollCoaterMachines as $machine) {
  850. $machineHoursAmount = $machineHours[$machine];
  851. if ($machineHoursAmount <= 0) {
  852. continue;
  853. }
  854. $allocations[$machine][$subject] =
  855. ($allocations[$machine][$subject] ?? 0) +
  856. round($amount * ($machineHoursAmount / $totalHours), 2);
  857. }
  858. }
  859. /**
  860. * 全局分摊
  861. */
  862. protected function allocateGlobally(
  863. array &$allocations,
  864. string $subject,
  865. float $amount,
  866. array $machineHours
  867. ): void {
  868. $totalHours = array_sum($machineHours);
  869. if ($totalHours <= 0) {
  870. Log::warning("全局分摊失败:总机时为0");
  871. return;
  872. }
  873. foreach ($machineHours as $machine => $hours) {
  874. try {
  875. // 确保 $machine 是有效的字符串键名
  876. if (!is_string($machine) && !is_numeric($machine)) {
  877. Log::warning("无效的机器键名: " . print_r($machine, true));
  878. continue;
  879. }
  880. $machine = (string)$machine;
  881. if ($hours <= 0) {
  882. continue;
  883. }
  884. if (!isset($allocations[$machine])) {
  885. $allocations[$machine] = [];
  886. }
  887. $machineAmount = round($amount * ($hours / $totalHours), 2);
  888. $allocations[$machine][$subject] =
  889. ($allocations[$machine][$subject] ?? 0) + $machineAmount;
  890. } catch (\Exception $e) {
  891. Log::error("全局分摊到机器 {$machine} 时出错: " . $e->getMessage());
  892. continue;
  893. }
  894. }
  895. }
  896. /**
  897. * 根据机台获取楼层
  898. */
  899. protected function getFloorByMachine(string $machine): ?string
  900. {
  901. $group = Db::name('设备_基本资料')
  902. ->where('设备编号', $machine)
  903. ->value('设备编组');
  904. if (!$group) {
  905. return null;
  906. }
  907. foreach (self::FLOOR_GROUP_MAP as $floor => $groupNames) {
  908. foreach ($groupNames as $groupName) {
  909. if (strpos($group, $groupName) !== false) {
  910. return $floor;
  911. }
  912. }
  913. }
  914. return null;
  915. }
  916. /**
  917. * 筛选卷凹机组的机台
  918. */
  919. protected function filterRollCoaterMachines(array $machines): array
  920. {
  921. $rollCoater = [];
  922. foreach ($machines as $machine) {
  923. $group = Db::name('设备_基本资料')
  924. ->where('设备编号', $machine)
  925. ->value('设备编组');
  926. if ($group && strpos($group, '03、卷凹机组') !== false) {
  927. $rollCoater[] = $machine;
  928. }
  929. }
  930. return $rollCoater;
  931. }
  932. /**
  933. * 简化科目名称
  934. */
  935. protected function simplifySubjectName(string $subject): string
  936. {
  937. foreach (self::SUBJECT_MAPPING as $keyword => $simple) {
  938. if (strpos($subject, $keyword) !== false) {
  939. return $simple;
  940. }
  941. }
  942. return $subject;
  943. }
  944. /**
  945. * 计算水电金额
  946. */
  947. protected function calculateUtilityAmount(array $item): float
  948. {
  949. $electricity = $this->calculateElectricityCost($item);
  950. $gas = $this->calculateGasCost($item);
  951. return $electricity + $gas;
  952. }
  953. /**
  954. * 生成分摊系数记录
  955. */
  956. protected function generateAllocationFactors(array $allocations, string $month, string $sysId): void
  957. {
  958. $now = date('Y-m-d H:i:s');
  959. foreach ($allocations as $machine => $subjects) {
  960. if (!is_string($machine) && !is_numeric($machine)) {
  961. Log::warning("无效的机台标识: " . print_r($machine, true));
  962. continue;
  963. }
  964. foreach ($subjects as $subject => $amount) {
  965. if (!is_string($subject)) {
  966. Log::warning("无效的科目名称: " . print_r($subject, true));
  967. continue;
  968. }
  969. $this->allocationFactors[] = [
  970. 'Sys_ny' => $month,
  971. '科目名称' => $subject,
  972. '设备编号' => (string)$machine,
  973. '分摊系数' => 1,
  974. '分摊金额' => floatval($amount),
  975. 'Sys_id' => $sysId,
  976. 'Sys_rq' => $now,
  977. ];
  978. }
  979. }
  980. }
  981. /**
  982. * 分配分摊水电
  983. */
  984. protected function allocateApportionedUtilities(array $machineAllocations): void
  985. {
  986. $machineRates = $this->calculateMachineRates($machineAllocations);
  987. foreach ($this->monthlyCostDetails as &$detail) {
  988. $machine = $detail['sczl_jtbh'];
  989. $hours = floatval($detail['占用机时']);
  990. if ($hours <= 0 || !isset($machineRates[$machine])) {
  991. continue;
  992. }
  993. $detail['直接水电'] = round($hours * 0.69, 2);
  994. foreach ($machineRates[$machine] as $subject => $rate) {
  995. $field = $this->getUtilityFieldName($subject);
  996. $detail[$field] = round($hours * $rate, 2);
  997. }
  998. }
  999. }
  1000. /**
  1001. * 计算机台每机时费用
  1002. */
  1003. protected function calculateMachineRates(array $allocations): array
  1004. {
  1005. $rates = [];
  1006. $machineHours = $this->getMachineHours();
  1007. foreach ($allocations as $machine => $subjects) {
  1008. $totalHours = $machineHours[$machine] ?? 0;
  1009. if ($totalHours <= 0) {
  1010. continue;
  1011. }
  1012. $rates[$machine] = [];
  1013. foreach ($subjects as $subject => $amount) {
  1014. $rates[$machine][$subject] = round($amount / $totalHours, 4);
  1015. }
  1016. }
  1017. return $rates;
  1018. }
  1019. /**
  1020. * 获取水电字段名
  1021. */
  1022. protected function getUtilityFieldName(string $subject): string
  1023. {
  1024. $map = [
  1025. '待分摊总额' => '分摊水电',
  1026. '废气处理' => '废气处理',
  1027. '锅炉' => '锅炉',
  1028. '空压机' => '空压机',
  1029. '热水锅炉' => '热水锅炉',
  1030. '真空鼓风机' => '真空鼓风机',
  1031. '中央空调' => '中央空调',
  1032. ];
  1033. return $map[$subject] ?? $subject;
  1034. }
  1035. /**
  1036. * 统一保存所有数据
  1037. */
  1038. protected function saveAllData(string $month, string $sysId): void
  1039. {
  1040. // 1. 删除旧数据
  1041. $this->deleteOldData($month);
  1042. // 2. 插入前检查数据结构
  1043. $this->validateDataStructure();
  1044. // 3. 插入新数据
  1045. $this->insertAllData();
  1046. }
  1047. /**
  1048. * 删除旧数据
  1049. */
  1050. protected function deleteOldData(string $month): void
  1051. {
  1052. Db::name('成本v23_月度成本明细')->where('sys_ny', $month)->delete();
  1053. Db::name('成本_各月分摊系数')->where('Sys_ny', $month)->delete();
  1054. }
  1055. /**
  1056. * 验证数据结构
  1057. */
  1058. protected function validateDataStructure(): void
  1059. {
  1060. if (empty($this->monthlyCostDetails)) {
  1061. Log::warning("月度成本明细数据为空");
  1062. return;
  1063. }
  1064. $tableName = '成本v23_月度成本明细';
  1065. try {
  1066. $columns = Db::query("DESCRIBE `{$tableName}`");
  1067. $columnNames = array_column($columns, 'Field');
  1068. Log::info("表{$tableName}结构字段数: " . count($columnNames));
  1069. foreach ($this->monthlyCostDetails as $index => &$row) {
  1070. // 确保行是数组
  1071. $rowArray = is_object($row) ? (array)$row : $row;
  1072. if (count($rowArray) !== count($columnNames)) {
  1073. Log::warning("第" . ($index + 1) . "行字段数不匹配: 数据" . count($rowArray) . "个,表" . count($columnNames) . "个");
  1074. $this->fixRowData($rowArray, $columnNames, $index);
  1075. $row = $rowArray;
  1076. }
  1077. // 检查所有数组键名是否为字符串
  1078. foreach ($rowArray as $key => $value) {
  1079. if (!is_string($key) && !is_int($key)) {
  1080. Log::error("发现非法键名类型 (行 {$index}, 键类型: " . gettype($key) . ")");
  1081. // 转换为字符串
  1082. unset($rowArray[$key]);
  1083. $rowArray[(string)$key] = $value;
  1084. }
  1085. }
  1086. }
  1087. } catch (\Exception $e) {
  1088. Log::error("验证数据结构时出错: " . $e->getMessage());
  1089. }
  1090. }
  1091. /**
  1092. * 修复行数据
  1093. */
  1094. protected function fixRowData(array &$row, array $columnNames, int $index): void
  1095. {
  1096. $fixedRow = [];
  1097. foreach ($columnNames as $column) {
  1098. $fixedRow[$column] = $row[$column] ?? 0;
  1099. }
  1100. $this->monthlyCostDetails[$index] = $fixedRow;
  1101. Log::info("已修复第" . ($index + 1) . "行数据");
  1102. }
  1103. /**
  1104. * 插入所有数据
  1105. */
  1106. protected function insertAllData(): void
  1107. {
  1108. $this->insertMonthlyCostDetails();
  1109. $this->insertAllocationFactors();
  1110. }
  1111. /**
  1112. * 插入月度成本明细
  1113. */
  1114. protected function insertMonthlyCostDetails(): void
  1115. {
  1116. if (empty($this->monthlyCostDetails)) {
  1117. return;
  1118. }
  1119. $total = count($this->monthlyCostDetails);
  1120. for ($i = 0; $i < $total; $i += self::BATCH_SIZE) {
  1121. $batch = array_slice($this->monthlyCostDetails, $i, self::BATCH_SIZE);
  1122. $this->insertBatch($batch, '成本v23_月度成本明细', $i);
  1123. }
  1124. }
  1125. /**
  1126. * 插入批次数据
  1127. */
  1128. protected function insertBatch(array $batch, string $tableName, int $startIndex): void
  1129. {
  1130. $firstRow = reset($batch);
  1131. $fields = array_keys($firstRow);
  1132. $fieldStr = '`' . implode('`,`', $fields) . '`';
  1133. $values = [];
  1134. foreach ($batch as $row) {
  1135. $rowValues = [];
  1136. foreach ($fields as $field) {
  1137. $value = $row[$field] ?? null;
  1138. $rowValues[] = is_numeric($value) ? $value : "'" . addslashes($value) . "'";
  1139. }
  1140. $values[] = '(' . implode(',', $rowValues) . ')';
  1141. }
  1142. $sql = "INSERT INTO `{$tableName}` ({$fieldStr}) VALUES " . implode(',', $values);
  1143. try {
  1144. Db::execute($sql);
  1145. Log::info("成功插入批次 " . (($startIndex / self::BATCH_SIZE) + 1));
  1146. } catch (\Exception $e) {
  1147. Log::error("插入批次失败: " . $e->getMessage());
  1148. throw $e;
  1149. }
  1150. }
  1151. /**
  1152. * 插入分摊系数
  1153. */
  1154. protected function insertAllocationFactors(): void
  1155. {
  1156. if (!empty($this->allocationFactors)) {
  1157. $total = count($this->allocationFactors);
  1158. for ($i = 0; $i < $total; $i += self::BATCH_SIZE) {
  1159. $batch = array_slice($this->allocationFactors, $i, self::BATCH_SIZE);
  1160. $this->insertBatch($batch, '成本_各月分摊系数', $i);
  1161. }
  1162. }
  1163. }
  1164. /**
  1165. * 记录成功日志
  1166. */
  1167. protected function logSuccess(string $month): void
  1168. {
  1169. Log::info("成本核算完成", [
  1170. 'month' => $month,
  1171. '月度成本明细记录数' => count($this->monthlyCostDetails),
  1172. '分摊系数记录数' => count($this->allocationFactors)
  1173. ]);
  1174. }
  1175. /**
  1176. * 构建成功响应
  1177. */
  1178. protected function buildSuccessResponse(string $month): array
  1179. {
  1180. return [
  1181. 'success' => true,
  1182. 'message' => '成本核算完成',
  1183. 'month' => $month,
  1184. 'stats' => [
  1185. 'monthly_cost_details' => count($this->monthlyCostDetails),
  1186. 'allocation_factors' => count($this->allocationFactors)
  1187. ]
  1188. ];
  1189. }
  1190. /**
  1191. * 记录错误日志
  1192. */
  1193. protected function logError(\Throwable $t): void
  1194. {
  1195. $errorDetails = [
  1196. '时间' => date('Y-m-d H:i:s'),
  1197. '错误类型' => get_class($t),
  1198. '错误信息' => $t->getMessage(),
  1199. '错误代码' => $t->getCode(),
  1200. '文件' => $t->getFile(),
  1201. '行号' => $t->getLine(),
  1202. '堆栈跟踪' => $t->getTraceAsString(),
  1203. '月度成本明细数' => count($this->monthlyCostDetails),
  1204. '分摊系数数' => count($this->allocationFactors),
  1205. ];
  1206. Log::error("统一成本核算失败详情: " . json_encode($errorDetails, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
  1207. }
  1208. /**
  1209. * 构建错误响应
  1210. */
  1211. protected function buildErrorResponse(Exception $e): array
  1212. {
  1213. return [
  1214. 'success' => false,
  1215. 'message' => '成本核算失败: ' . $e->getMessage(),
  1216. 'error' => $e->getMessage()
  1217. ];
  1218. }
  1219. }