Laravel更新leave_balances表指定用户总请假天数计算错误问题
问题根因
- 核心统计逻辑完全错误:
getSumOfLeaveTaken()方法里写的Leave::where(条件)->first()->sum('num_days'),执行顺序是先按where条件取1条记录返回模型实例,再在实例上调用sum(),此时sum()会绕过之前所有的查询条件,直接对leaves全表的num_days做聚合统计,返回全系统所有请假单的总天数。只要有新用户提交请假,全表总和就会增加,所有用户的余额统计值都会同步上涨,和你描述的异常现象完全吻合。 - 方法参数不匹配:控制器调用
getSumOfLeaveTaken()时传入了请假类别ID,但方法本身没有定义入参接收,反而硬编码了leave_category_id=1作为查询条件,不管用户选什么类别的假期,都只会按类别1的规则查询,会导致不同请假类别的数据串扰。 - 维度匹配缺失:
leave_balances表是按「用户+请假类别+年份」三个维度存储余额,但不管是查询余额记录还是统计请假天数时,都没有加年份过滤条件,会把用户往年的请假记录也算到当年余额里,后续跨年度时会出现数据错误。 - 变量作用域不规范:控制器更新余额的分支中,
where('created_by', $userId)的$userId没有在当前作用域显式赋值,依赖前序查询时的临时赋值,容易出现取值异常。
修正代码
首先调整Leave模型的统计方法,去掉硬编码,通过入参传递查询条件,直接在查询构造器上调用聚合函数:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Leave extends Model { use HasFactory; protected $table = 'leaves'; protected $fillable = [ 'created_by', 'leave_category_id', 'start_date', 'end_date', 'num_days', 'reason', 'publication_status', 'deletion_status', ]; /** * 统计指定用户指定类别的累计请假天数 * @param int $leaveCategoryId 请假类别ID * @param int $userId 用户ID * @param int|null $year 统计年份,不传则统计所有年份 * @return int */ public static function getSumOfLeaveTaken(int $leaveCategoryId, int $userId, ?int $year = null): int { $query = self::where('leave_category_id', $leaveCategoryId) ->where('created_by', $userId) ->where('publication_status', 1) // 建议加审批通过状态过滤,避免把待审批、驳回的请假算入统计 ->where('deletion_status', 0); // 过滤已删除的请假单 if ($year) { $query->whereYear('created_at', $year); } return (int)($query->sum('num_days') ?? 0); } }
再调整控制器store方法中余额计算的相关逻辑,补全查询维度,规范变量赋值:
public function store(Request $request) { $sdates = date("D", strtotime($request->start_date)); $edates = date("D", strtotime($request->end_date)); $leave_application = $this->validate($request, [ 'leave_category_id' => 'required', 'start_date' => 'required', 'end_date' => 'required', ]); $start_date = Carbon::parse(request('start_date')); $end_date = Carbon::parse(request('end_date')); // 如果业务规则请假包含结束当天,需要给diffInWeekdays传第二个参数true,否则天数会少算1天 $days = $start_date->diffInWeekdays($end_date); $weekly_holidays = WorkingDay::where('working_status', 0)->get(['day'])->toArray(); if($weekly_holidays != null){ foreach ($weekly_holidays as $weekly_holiday) { if ($sdates == $weekly_holiday['day'] || $edates == $weekly_holiday['day']) { return redirect()->route('leave.index')->with('exception', 'You select a holiday !'); } } } $monthly_holidays = Holiday::where('holiday_date', '=', $request->start_date)->first(['holiday_date']); if($monthly_holidays == null){ $result = Leave::create($leave_application +[ 'num_days' => $days, 'reason' => request('reason'), 'created_by' => auth()->id() ]); // 显式定义公共变量,避免作用域异常 $currentUserId = auth()->id(); $currentCategoryId = $request->leave_category_id; $nowInTarawa = Carbon::now('Pacific/Tarawa'); $currentYear = intval($nowInTarawa->format('Y')); // 查询余额时补全年份维度,避免跨年度数据串扰 $balanceRecord = LeaveBalance::where('leave_category_id', $currentCategoryId) ->where('created_by', $currentUserId) ->where('year', $currentYear) ->first(); // 传入所有必要参数调用统计方法 $totalTaken = Leave::getSumOfLeaveTaken($currentCategoryId, $currentUserId, $currentYear); if(!$balanceRecord){ $leave_balance = new LeaveBalance(); $leave_balance->leave_category_id = $currentCategoryId; $leave_balance->created_by = $currentUserId; $leave_balance->year = $currentYear; $leave_balance->total_leave_taken = $totalTaken; $leave_balance->save(); }else{ LeaveBalance::where('created_by', $currentUserId) ->where('leave_category_id', $currentCategoryId) ->where('year', $currentYear) ->update(['total_leave_taken' => $totalTaken]); } $inserted_id = $result->id; if (!empty($inserted_id)) { return redirect()->route('leave.index')->with('message', 'Add successfully.'); } return redirect()->route('leave.index')->with('exception', 'Operation failed !'); } if($monthly_holidays != null){ return redirect()->route('leave.index')->with('exception', 'You select a holiday !'); } }
额外优化建议
- 不要在模型方法内部直接调用
Auth类获取登录用户ID,模型层只负责数据逻辑,所需参数通过入参传入,避免命令行、队列等非HTTP场景下调用方法时出现登录态缺失的错误。 - 现有每周假日校验逻辑只判断了请假开始、结束日期是不是假日,没有遍历整个请假时间段的日期,如果请假跨多天,中间夹着法定假日或周休日不会被识别,会多算请假天数。
- 建议给
leave_balances表的created_by、leave_category_id、year三个字段建联合唯一索引,从数据库层面避免同一用户同一年份同一类别出现多条余额记录。
内容的提问来源于stack exchange,提问作者Kairaoi Ientumoa
相关产品推荐
相关产品推荐

