<?php namespace app\common\service\wareHousFee\zhongtong\freight; use app\common\model\supplyChain\ScmDayOutboundData; use app\common\model\wareHousFee\facility\GoodsWeightModel; use app\common\model\wareHousFee\zhongtong\freight\ZhongTongDetailsModel; use app\common\model\wareHousFee\zhongtong\freight\ZhongTongMaterialsModel; use app\common\model\wareHousFee\zhongtong\freight\ZhongTongSpecialGoodsModel; use app\common\model\wareHousFee\zhongtong\freight\ZhongTongStorageQuoteModel; use app\common\model\wareHousFee\zhongtong\freight\ZhongTongQuoteModel; use think\db\exception\DataNotFoundException; use think\db\exception\DbException; use think\db\exception\ModelNotFoundException; use think\facade\Db; use think\facade\Log; class ZhongTongDetailsService { public function __construct() { } /** * @notes 中通仓储费运费明细核对逻辑(优化版) * @notes 实现100万条数据核对性能优化,使用批量更新技术控制在合理时间内 * @param string $ yearMonth 年月,格式为YYYY-MM * @return bool 处理状态 * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException * @author 胡军 * @date 2025/07/15 */ public function detailsVerifyDo(string $ yearMonth): bool { // 调用getMonthTimeRange方法获取当月时间范围 $ timeRange = $ this->getMonthTimeRange($ yearMonth); $ startTime = $ timeRange['startTime']; $ endTime = $ timeRange['endTime']; // 性能优化配置 $ batchSize = 500; // 批次大小,每次读取的数据条数 $ updateBatchSize = 200; // 批量更新大小,每200条更新一次数据库 $ reconnectInterval = 200; // 每200批次(10万条)重新连接一次数据库 $ processedCount = 0; // 已处理记录数 $ updateCount = 0; // 已更新记录数 $ status = true; // 处理状态 try { // 使用chunk分批处理数据,减少内存占用 ZhongTongDetailsModel::where('shipping_time', 'between', [$ startTime, $ endTime]) ->chunk($ batchSize, function ($ details) use ( &$ processedCount, &$ updateCount, &$ status, $ updateBatchSize, $ reconnectInterval, $ yearMonth ) { static $ batchCount = 0; $ batchCount++; // 批量更新缓冲区 $ updateBuffer = []; foreach ($ details as $ detail) { try { // 准备更新数据数组 $ updateData = []; // 1. 理论重量 // 规则:中通明细数据表:la_zhongtong_details当中的字段销售渠道sales_channel去关联la_scm_day_outbound_data每日出库数据明细表的billNo关联单号字段, // 查询出la_scm_day_outbound_data表对应记录的货品编号goodsNo和数量quantity,查询出来的结果可能会有多条哈!然后再根据查询结果la_scm_day_outbound_data表记录里面的 // skuBarcode去关联la_warehouse_facility当中的barcode去查询字段single_item_weight, // 最后用la_scm_day_outbound_data表的数量quantity去乘以la_warehouse_facility表当中的single_item_weight就是理论重量了。 $ theoreticalWeight = 0.0; $ outboundData = ScmDayOutboundData::where('billNo', $ detail->sales_channel)->select(); if(empty($ outboundData)) { Log::warning("【中通运费明细费用核对销售渠道和每日出库数据明细表的billNo关联不匹配】--月份为:{$ yearMonth}的费用核对销售渠道和每日出库数据明细表的billNo关联不匹配导致后续很多字段无法计算,明细数据id为{$ detail['id']}"); continue; }else{ foreach ($ outboundData as $ item) { $ facility = GoodsWeightModel::where('barcode', $ item['skuBarcode'])->find(); if ($ facility) { $ singleWeight = (float)($ facility['single_item_weight'] ?? 0); $ quantity = (int)($ item['quantity'] ?? 0); $ theoreticalWeight += $ singleWeight * $ quantity; }else{ Log::warning("【中通运费明细费用核对skuBarcode字段与la_warehouse_facility当中的barcode不匹配】--月份为:{$ yearMonth}的费用核对中通运费明细费用核对skuBarcode字段与la_warehouse_facility当中的barcode不匹配导致后续很多字段无法计算,明细数据id为{$ detail['id']}"); } } } //理论重量:四舍五入到小数点后3位 $ updateData['theoretical_weight'] = round($ theoreticalWeight, 3); // 2.耗材重量 // 规则:中通明细数据表la_zhongtong_details当中的字段型号model去关联la_zhongtong_materials表当中的material_name耗材名称字段,从而拿到weight字段的值就是耗材重量。 $ materialWeight = 0.0; $ material = ZhongTongMaterialsModel::where('material_name', $ detail->model)->find(); if ($ material) { $ materialWeight = (float)($ material['weight'] ?? 0); }else{ Log::warning("【中通运费明细费用核对字段型号model与la_zhongtong_materials表当中的material_name耗材名称不匹配】--月份为:{$ yearMonth}的费用核对字段型号model与la_zhongtong_materials表当中的material_name耗材名称不匹配导致后续很多字段无法计算,明细数据id为{$ detail['id']}"); continue; } //耗材重量:四舍五入到小数点后3位 $ updateData['material_weight'] = round($ materialWeight, 3); // 3.货品重量差异 // 规则:中通明细数据表la_zhongtong_details当中的字段weight减去上面计算的耗材重量再减去理论重量就是货品重量差异 $ detailWeight = (float)($ detail->weight ?? 0); $ goodsWeightDiff = $ detailWeight - $ materialWeight - $ theoreticalWeight; //货品重量差异:四舍五入到小数点后3位 $ updateData['goods_weight_diff'] = round($ goodsWeightDiff, 3); // 4.货品数量 // 规则:中通明细数据表la_zhongtong_details当中的字段total_goods_count减去la_zhongtong_details表当中的card_quantity卡片数量就是货品数量 $ totalGoodsCount = (int)($ detail->total_goods_count ?? 0); $ cardQuantity = (int)($ detail->card_quantity ?? 0); $ goodsQuantity = $ totalGoodsCount - $ cardQuantity; //货品数量为整数 $ updateData['goods_quantity'] = $ goodsQuantity; // 5.续件数量 // 规则:如果上面计算的货品数量小于等于5那么续件数量就是0; // 如果上面计算的货品数量大于5那么续件数量就是货品数量减去5; $ additionalQuantity = $ goodsQuantity > 5 ? $ goodsQuantity - 5 : 0; //续件数量也是整数 $ updateData['additional_quantity'] = $ additionalQuantity; // 6.操作费 // 计算规则:首先是基于中通明细数据表la_zhongtong_details当中的货品摘要字段goods_summary去匹配la_zhongtong_special_goods表当中的 // 摘要信息goods_code拿到对应的la_zhongtong_special_goods表记录的operation_fee作为操作费,如果匹配不到那么就是另外的一种算法: // 查询la_zhongtong_storage_quote表数据当中的费用类型为"订单生产B2C"的记录(可能有多条), // 判断la_zhongtong_details表当中的货品数量goods_quantity的值如果小于等于5则固定为0.6;如果是大于5则:0.6+(上面计算的续件数量 * 01); $ operationFee = 0.0; $ specialGoods = ZhongTongSpecialGoodsModel::where('goods_code', $ detail->goods_summary)->find(); //print_r($ specialGoods->toArray());die; if ($ specialGoods) { //操作费第一种情况 $ operationFee = (float)($ specialGoods['operation_fee'] ?? 0); } else { //操作费第二种情况 另外的算法 if ($ goodsQuantity <= 5) { $ operationFee = 0.6; } else { $ operationFee = 0.6 + ($ additionalQuantity * 0.1); } } //操作费:四舍五入到小数点后3位 $ updateData['operation_fee'] = round($ operationFee, 3); // 7.理论耗材(复杂业务逻辑) // 规则:首先是基于中通明细数据表:la_zhongtong_details当中的货品摘要字段goods_summary去匹配la_zhongtong_special_goods表当中的摘要信息goods_code拿 // 到对应的la_zhongtong_special_goods表记录的material_name作为理论耗材,如果匹配不到那么就是另外的一种算法: // 根据上面我们拿到的理论重量去查询la_zhongtong_materials表 // 当中的适用重量区间字段weight_range,这个weight_range字段是一个用varchar类型表示的区间范围,有可能是1.5<x<=3,也有可能是 13<x,也有可能是斜杠或者空, // 我们就按照<分割取第一个元素那就是起始范围值,用<=分割取最后一个元素那就是结束范围值,如果匹配不到后面我会告诉你怎么做;然后拿到匹配到的记录里面的material_name字段值就 // 是理论耗材;这还没有完哈!现在我们还需要做一步判断,la_zhongtong_details当中的model型号字段值去匹配la_zhongtong_materials表当中的material_name耗材名称字 // 段进而拿到匹配到的记录里面的cost费用的值比如叫做a;然后再用上面拿到的理论耗材的值去匹配la_zhongtong_materials表当中的material_name耗材名称字段进而拿到匹配到的记录 // 里面的cost费用的值比如叫做b,然后判断a和b的值的大小, // 如果a的值小于b那么就将理论耗材的值更新为model字段的值否则维持上面得到的理论耗材的值不变; $ theoreticalMaterial = ''; //操作费的时候我们已经查询过整条记录直接拿来用即可 if ($ specialGoods) { //ege:冷藏包装 $ theoreticalMaterial = $ specialGoods['material_name'] ?? ''; } else { //否则:根据理论重量查询重量区间匹配 $ theoreticalMaterial = $ this->calculateTheoreticalMaterialByWeight($ theoreticalWeight); } //现在我们还需要做一步判断,费用比较优化选择 if (!empty($ theoreticalMaterial) && $ theoreticalMaterial !== '...') { $ theoreticalMaterial = $ this->optimizeTheoreticalMaterialByCost($ detail->model, $ theoreticalMaterial); //理论耗材 $ updateData['theoretical_material'] = $ theoreticalMaterial; }else{ Log::warning("【中通运费明细费用核对理论耗材核算不匹配】--月份为:{$ yearMonth}的费用核对理论耗材不匹配导致后续很多字段无法计算,明细数据id为{$ detail['id']}"); continue; } // 8.耗材是否一致 // 规则:如果上面计算的理论耗材等于la_zhongtong_details表当中的model型号字段的值则一致 否则 不一致 // 即 理论耗材 == 型号字段 ? 1(一致) : 0(不一致) $ materialConsistent = ($ theoreticalMaterial == $ detail->model) ? 1 : 0; //耗材是否一致为整数类型 $ updateData['material_consistent'] = $ materialConsistent; // 9.理论耗材费 // 规则:根据上面拿到的理论耗材去查询la_zhongtong_materials耗材报价表当中的material_name对应的cost费用的值就是理论耗材费 $ theoreticalMaterialFee = 0.0; if ($ theoreticalMaterial !== '...') { $ theoreticalMaterialModel = ZhongTongMaterialsModel::where('material_name', $ theoreticalMaterial)->find(); if ($ theoreticalMaterialModel) { $ theoreticalMaterialFee = (float)($ theoreticalMaterialModel['cost'] ?? 0); }else{ Log::warning("【中通运费明细费用核对理论耗材费核算不匹配】--月份为:{$ yearMonth}的费用核对理论耗材费不匹配导致后续很多字段无法计算,明细数据id为{$ detail['id']}"); continue; } } //理论耗材费保留三位小数 $ updateData['theoretical_material_fee'] = round($ theoreticalMaterialFee, 3); // 10.计费重量 // 规则:上面计算的理论重量 加上(根据理论耗材的值 去表la_zhongtong_materials耗材报价表当中的material_name对应的weight重量的值) 就是计费重量。 $ theoreticalMaterialWeight = 0.0; if ($ theoreticalMaterial !== '...') { $ theoreticalMaterialModel = ZhongTongMaterialsModel::where('material_name', $ theoreticalMaterial)->find(); if ($ theoreticalMaterialModel) { $ theoreticalMaterialWeight = (float)($ theoreticalMaterialModel['weight'] ?? 0); } //TODO:此处是否也要进行判断?如果没有拿到重量则应该continue跳过该条记录 待完善! } $ billingWeight = $ theoreticalWeight + $ theoreticalMaterialWeight; //计费重量保留3位小数 $ updateData['billing_weight_calc'] = round($ billingWeight, 3); // 11.理论快递运费 // 规则:中通明细数据表:la_zhongtong_details当中的字段物流公司logistics_company如果等于荆州-中通云仓唯品自营专用顺丰,则 理论快递运费等于0; // 否则需要根据一下规则计算:根据la_zhongtong_details当中的记录省份、物流公司、计费重量去匹配la_zhongtong_quote表当中的目的省名dest_province、 // 快递express_fee,但是计费重量在la_zhongtong_quote表当中是分了区间的,比如数据表当中: // `price_0_5` decimal(10,3) DEFAULT '0.000' COMMENT '0<X≤0.5kg价格', // `price_5_10` decimal(10,3) DEFAULT '0.000' COMMENT '0.5<X≤1kg价格', // `price_10_20` decimal(10,3) DEFAULT '0.000' COMMENT '1<X≤2kg价格', // `price_20_30` decimal(10,3) DEFAULT '0.000' COMMENT '2<X≤3kg价格', // `price_30_50` decimal(10,3) DEFAULT '0.000' COMMENT '3<X≤5kg价格', // `first_weight_5kg` decimal(10,3) DEFAULT '0.000' COMMENT '首重5kg价格', // `additional_1kg` decimal(10,3) DEFAULT '0.000' COMMENT '续重1kg价格', // 你得需要根据计费重量判断是在哪个区间就使用哪个字段的值哈!然后才能真正匹配到正确的那条la_zhongtong_quote表当中的记录!这一点一定要注意啊! // 匹配到正确的记录之后如果是小于等于5kg的就判断具体区间选择使用哪条记录计算理论快递运费: // `price_0_5` decimal(10,3) DEFAULT '0.000' COMMENT '0<X≤0.5kg价格', // `price_5_10` decimal(10,3) DEFAULT '0.000' COMMENT '0.5<X≤1kg价格', // `price_10_20` decimal(10,3) DEFAULT '0.000' COMMENT '1<X≤2kg价格', // `price_20_30` decimal(10,3) DEFAULT '0.000' COMMENT '2<X≤3kg价格', // `price_30_50` decimal(10,3) DEFAULT '0.000' COMMENT '3<X≤5kg价格', // 如果是大于5kg的比如6kg 那么理论费用就是first_weight_5kg字段对应的值 + (6-1)* additional_1kg 才是理论重量,这么说明白了吗? $ theoreticalExpressFee = $ this->calculateTheoreticalExpressFee( $ detail->logistics_company, $ detail->province, $ billingWeight, $ yearMonth ); //理论快递运费保留两位小数 $ updateData['theoretical_express_fee'] = round($ theoreticalExpressFee, 3); // 12.北京上海加收 // 规则:若la_zhongtong_details当中的字段物流公司logistics_company 等于 “荆州-中通云仓唯品自营专用顺丰”,北京上海加收的值就是0;若不等于“荆州-中通云仓唯品自营专用顺丰” // 但是la_zhongtong_details当中的省份字段province等于北京,则北京上海加收=1;如果省份等于上海 ,北京上海加收的值就是0.5 $ beijingShanghaiSurcharge = $ this->calculateBeijingShanghaiSurcharge( $ detail->logistics_company, $ detail->province ); $ updateData['beijing_shanghai_theoretical_surcharge'] = $ beijingShanghaiSurcharge; // 将更新数据添加到缓冲区 $ updateBuffer[$ detail->id] = $ updateData; $ processedCount++; } catch (\Exception $ e) { // 记录错误但不中断处理 Log::warning("【中通仓储费运费明细核对】处理记录ID {$ detail->id} 时出错: " . $ e->getMessage()); $ processedCount++; } } // 批量更新数据库(分块处理避免SQL过长) if (!empty($ updateBuffer)) { $ this->batchUpdateChunked($ updateBuffer, $ updateBatchSize); $ updateCount += count($ updateBuffer); Log::info("【中通仓储费运费明细核对】批次更新完成,本批次更新 " . count($ updateBuffer) . " 条,累计更新 {$ updateCount} 条"); } // 定期重连数据库,避免长时间连接超时 if ($ batchCount % $ reconnectInterval === 0) { $ this->reconnectDatabase(); Log::info("【中通仓储费运费明细核对】已处理 {$ processedCount} 条数据,进行数据库重连"); } // 释放内存,避免内存溢出 unset($ updateBuffer); gc_collect_cycles(); // 强制垃圾回收 }); } catch (\Exception $ e) { Log::error("【中通仓储费运费明细核对】处理异常: " . $ e->getMessage()); $ status = false; } Log::info("【中通仓储费运费明细核对】处理完成,年月:{$ yearMonth},共处理 {$ processedCount} 条,成功更新 {$ updateCount} 条记录"); return $ status && $ updateCount > 0; } /** * 根据年月获取当月时间范围(精确到秒) * @param string $ yearMonth 年月,格式为YYYY-MM * @return array 包含startTime和endTime的关联数组 * @author 胡军 * @date 2025/06/23 */ private function getMonthTimeRange(string $ yearMonth): array { // 验证输入格式 (YYYY-MM) if (!preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $ yearMonth)) { throw new \InvalidArgumentException('输入格式不正确,必须为YYYY-MM格式'); } list($ year, $ month) = explode('-', $ yearMonth); // 构建开始时间 $ startTime = "{$ year}-{$ month}-01 00:00:00"; // 使用DateTime类计算当月最后一天 $ lastDay = (new \DateTime("{$ year}-{$ month}-01")) ->modify('last day of this month') ->format('d'); // 构建结束时间 $ endTime = "{$ year}-{$ month}-{$ lastDay} 23:59:59"; return [ 'startTime' => $ startTime, 'endTime' => $ endTime ]; } /** * @notes 安全批量更新(分块处理) * @notes 将大批量数据分割成小块,避免SQL语句过长导致执行失败 * @param array $ data 待更新数据 [id => [field => value]] * @param int $ chunkSize 分块大小,默认为200 * @author 胡军 * @date 2025/07/15 */ private function batchUpdateChunked(array $ data, int $ chunkSize = 200): void { // 使用array_chunk函数将数据分割成多个指定大小的子数组 // 例如:$ data有1000条记录,$ chunkSize=200,则分割成5个200条的子数组 $ chunks = array_chunk($ data, $ chunkSize, true); foreach ($ chunks as $ chunk) { $ this->safeBatchUpdate($ chunk); } } /** * @notes 安全批量更新(使用CASE WHEN技术) * @notes 核心技术:使用CASE WHEN语句实现批量更新,避免逐条UPDATE的性能问题 * @param array $ data 待更新数据 [id => [field => value]] * @author 胡军 * @date 2025/07/15 */ private function safeBatchUpdate(array $ data): void { if (empty($ data)) { return; } // 数据表名 $ table = 'la_zhongtong_details'; // 需要更新的字段列表(确保字段名与数据库表结构一致) $ fields = [ 'theoretical_weight', // 理论重量 'material_weight', // 耗材重量 'goods_weight_diff', // 货品重量差异 'goods_quantity', // 货品数量 'additional_quantity', // 续件数量 'operation_fee', // 操作费 'theoretical_material', // 理论耗材 'material_consistent', // 耗材是否一致(0=不一致,1=一致) 'theoretical_material_fee', // 理论耗材费 'billing_weight_calc', // 计费重量 'theoretical_express_fee', // 理论快递运费 'beijing_shanghai_theoretical_surcharge' // 北京上海加收理论值 ]; $ cases = []; // 存储CASE WHEN语句 $ params = []; // 存储参数值 $ ids = []; // 存储所有ID // 构建CASE WHEN更新语句 // 核心技术原理: // UPDATE table SET // field1 = CASE id WHEN 1 THEN value1 WHEN 2 THEN value2 END, // field2 = CASE id WHEN 1 THEN value3 WHEN 2 THEN value4 END // WHERE id IN (1,2) foreach ($ fields as $ field) { $ cases[$ field] = []; foreach ($ data as $ id => $ row) { $ cases[$ field][$ id] = $ row[$ field] ?? null; $ params[] = $ row[$ field] ?? null; } } // 构建完整的SQL语句 $ sql = "UPDATE {$ table} SET "; $ setParts = []; foreach ($ fields as $ field) { $ caseSql = "{$ field} = CASE id "; foreach ($ cases[$ field] as $ id => $ value) { $ caseSql .= "WHEN {$ id} THEN ? "; $ ids[$ id] = $ id; // 收集所有ID } $ caseSql .= "END"; $ setParts[] = $ caseSql; } $ sql .= implode(', ', $ setParts); $ sql .= " WHERE id IN (" . implode(',', array_keys($ ids)) . ")"; // 执行批量更新 try { Db::execute($ sql, $ params); } catch (\Exception $ e) { Log::error("【中通仓储费运费明细核对】批量更新失败: " . $ e->getMessage()); Log::error("【中通仓储费运费明细核对】SQL语句: " . $ sql); Log::error("【中通仓储费运费明细核对】参数: " . json_encode(array_slice($ params, 0, 20))); // 只记录前20个参数避免日志过长 Log::error("【中通仓储费运费明细核对】更新数据样例: " . json_encode(array_slice($ data, 0, 2, true))); // 记录前2条数据样例 throw $ e; } } /** * @notes 理论耗材(重量区间匹配) * @param float $ theoreticalWeight 理论重量 * @return string 理论耗材名称 * @author 胡军 * @date 2025/07/15 */ private function calculateTheoreticalMaterialByWeight(float $ theoreticalWeight): string { /* 规则参考着看: 首先是基于中通明细数据表:la_zhongtong_details当中的货品摘要字段goods_summary去匹配la_zhongtong_special_goods表当中的摘要信息goods_code拿 到对应的la_zhongtong_special_goods表记录的material_name作为理论耗材,如果匹配不到那么就是另外的一种算法:根据上面我们拿到的理论重量去查询la_zhongtong_materials表 当中的适用重量区间字段weight_range,这个weight_range字段是一个用varchar类型表示的区间范围,有可能是1.5<x<=3,也有可能是 13<x,也有可能是斜杠或者空, 我们就按照<分割取第一个元素那就是起始范围值,用<=分割取最后一个元素那就是结束范围值,如果匹配不到后面我会告诉你怎么做;然后拿到匹配到的记录里面的material_name字段 值就是理论耗材;这还没有完哈!现在我们还需要做一步判断,la_zhongtong_details当中的model型号字段值去匹配la_zhongtong_materials表当中的material_name耗材名称字 段进而拿到匹配到的记录里面的cost费用的值比如叫做a;然后再用上面拿到的理论耗材的值去匹配la_zhongtong_materials表当中的material_name耗材名称字段进而拿到匹配到的记录 里面的cost费用的值比如叫做b,然后判断a和b的值的大小,如果a的值小于b那么就将理论耗材的值更新为model字段的值否则维持上面得到的理论耗材的值不变;*/ //查询所有耗材的重量区间 $ materials = ZhongTongMaterialsModel::whereNotNull('weight_range') ->where('weight_range', '<>', '') ->select() ->toArray(); foreach($ materials as $ material) { $ weightRange = $ material['weight_range']; // 跳过无效的重量区间(斜杠或空值) if (empty($ weightRange) || $ weightRange === '/' || $ weightRange === '\\') { continue; } // 解析重量区间:如 "1.5<x<=3" 或 "13<x" $ startWeight = null; $ endWeight = null; // 按"<"分割取第一个元素作为起始范围值 if (strpos($ weightRange, '<') !== false) { $ parts = explode('<', $ weightRange); if (count($ parts) >= 2 && is_numeric($ parts[0])) { $ startWeight = (float)$ parts[0]; } } // 按"<="分割取最后一个元素作为结束范围值 if (strpos($ weightRange, '<=') !== false) { $ parts = explode('<=', $ weightRange); if (count($ parts) >= 2 && is_numeric($ parts[count($ parts) - 1])) { $ endWeight = (float)$ parts[count($ parts) - 1]; } } // 判断理论重量是否在区间内 $ inRange = true; if ($ startWeight !== null && $ theoreticalWeight <= $ startWeight) { $ inRange = false; } if ($ endWeight !== null && $ theoreticalWeight > $ endWeight) { $ inRange = false; } if ($ inRange) { //如果在区间之内 那么直接返回material_name耗材名称的值 return $ material['material_name'] ?? ''; } } //匹配不到时返回占位符 return '...'; } /** * @notes 根据费用比较优化理论耗材选择 * @param string $ modelName 型号名称 * @param string $ theoreticalMaterial 理论耗材名称 * @return string 优化后的理论耗材名称 * @author 胡军 * @date 2025/07/15 */ private function optimizeTheoreticalMaterialByCost(string $ modelName, string $ theoreticalMaterial): string { // 获取型号对应的费用 (a) $ modelMaterial = ZhongTongMaterialsModel::where('material_name', $ modelName)->find(); $ costA = $ modelMaterial ? (float)($ modelMaterial['cost'] ?? 0) : 0; // 获取理论耗材对应的费用 (b) $ theoreticalMaterialModel = ZhongTongMaterialsModel::where('material_name', $ theoreticalMaterial)->find(); $ costB = $ theoreticalMaterialModel ? (float)($ theoreticalMaterialModel['cost'] ?? 0) : 0; // 如果a < b,使用型号作为理论耗材;否则维持理论耗材不变 if ($ costA < $ costB) { return $ modelName; } return $ theoreticalMaterial; } /** * @notes 计算理论快递运费 * @param string $ logisticsCompany 物流公司 * @param string $ province 省份 * @param float $ billingWeight 计费重量 * @return float 理论快递运费 * @author 胡军 * @date 2025/07/15 */ private function calculateTheoreticalExpressFee(string $ logisticsCompany, string $ province, float $ billingWeight, string $ yearMonth): float { if ($ logisticsCompany === '荆州-中通云仓唯品自营专用顺丰') { return 0.0; } //清理省份名称(去掉"省"、"市"等后缀) $ cleanProvince = str_replace(['省', '市', '自治区', '特别行政区'], '', $ province); //查询报价表匹配记录(根据目的省名+快递匹配) $ quote = ZhongTongQuoteModel::where('dest_province', 'like', "%{$ cleanProvince}%")->where('express_fee', 'like', "%{$ logisticsCompany}%")->find(); if (!$ quote) { //如果匹配不到,尝试直接用原省份名称+快递匹配 $ quote = ZhongTongQuoteModel::where('dest_province','=', $ province)->where('express_fee', '=', $ logisticsCompany)->find(); } if (!$ quote) { Log::warning("【中通运费明细费用核对理论快递运费核算不匹配】--月份为:{$ yearMonth}的费用核对理论耗材费不匹配导致后续很多字段无法计算,明细数据id为{$ detail['id']}"); return 0.0; } // 根据计费重量判断使用哪个价格区间 if ($ billingWeight > 0 && $ billingWeight <= 0.5) { return (float)($ quote['price_0_5'] ?? 0); } elseif ($ billingWeight > 0.5 && $ billingWeight <= 1) { return (float)($ quote['price_5_10'] ?? 0); } elseif ($ billingWeight > 1 && $ billingWeight <= 2) { return (float)($ quote['price_10_20'] ?? 0); } elseif ($ billingWeight > 2 && $ billingWeight <= 3) { return (float)($ quote['price_20_30'] ?? 0); } elseif ($ billingWeight > 3 && $ billingWeight <= 5) { return (float)($ quote['price_30_50'] ?? 0); } elseif ($ billingWeight > 5) { // 大于5kg:首重5kg价格 + (重量-5) × 续重1kg价格 $ firstWeight5kg = (float)($ quote['first_weight_5kg'] ?? 0); $ additional1kg = (float)($ quote['additional_1kg'] ?? 0); return $ firstWeight5kg + (($ billingWeight - 5) * $ additional1kg); } return 0.0; } /** * @notes 计算北京上海加收 * @param string $ logisticsCompany 物流公司 * @param string $ province 省份 * @return float 北京上海加收费用 * @author 胡军 * @date 2025/07/15 */ private function calculateBeijingShanghaiSurcharge(string $ logisticsCompany, string $ province): float { if ($ logisticsCompany === '荆州-中通云仓唯品自营专用顺丰') { return 0.0; } // 根据省份判断加收费用 if ($ province === '北京') { return 1.0; } elseif ($ province === '上海') { return 0.5; } return 0.0; } /** * @notes 数据库重连方法 * @notes 长时间处理大数据时,定期重连数据库避免连接超时 * @author 胡军 * @date 2025/07/15 */ private function reconnectDatabase(): void { try { $ connection = Db::connect(); $ connection->close(); $ connection->connect(); Log::info("【中通仓储费运费明细核对】数据库重连成功"); } catch (\Exception $ e) { Log::error("【中通仓储费运费明细核对】数据库重连失败: " . $ e->getMessage()); } } } 这是我的php代码我需要让你帮我优化一下,因为我需要通过detailsVerifyDo()方法去处理100万的数据,虽然我已经进行了优化但是还是有瓶颈,请你帮我优化一下代码!要求提供最终优化好的完整代码,另外我的注释信息也要保留