You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 22:40:51