【開発ログ⑪】有休付与日から現在までの消化日数の合計を計算〜残日数まで


前提について

はじめまして、
プログラミングスクールに通ういりふねと申します。この記事は、スクールの課題である個人アプリの開発の記録を書くことで、自身のアウトプットに利用しています。もし、読んでいただけた方がいましたら、フィードバックをしていただけたら嬉しいです。

開発するのは「有給休暇管理ツール」です。仕様は過去記事をどうぞ。

アプリはデプロイまで行いますが、サービスとして提供するものではありません。あくまでも自学習の一環ですので、ご理解下さい。では本題へどうぞ。

今回の実施内容

前回までで、従業員の入社日をもとに付与日(入社日から6ヶ月後の有休が付与される日)と勤続年数まで計算できました。今日は、消化日数の合計を計算させます。具体的には以下の手順で考えます。

  • 消化日数の合計とは?
  • 今回扱う消化日数の合計は?
  • 合計する対象範囲を検索
  • 検索結果を合計させる
  • 合計した結果をビューに表示
  • ローカルブラウザで確認できればOK

消化日数の合計とは?

まず毎年もらえる有給休暇は、付与されてから2年間有効となります。消化する場合は、古いものから順に消化されるのもポイントです。

例えば、2018年4月1日に入社した方が、有休のうち3日間を2回に分けて消化した場合、以下のような流れになります。

日付 有休の増減 残日数
2018/4/1 入社時は増減なし 0日
2018/10/1 有休10日付与 10日
2019/1/1 有休3日消化★ 7日
2019/10/1 有休11日付与 18日
2020/1/1 有休3日消化★ 15日
2020/5/17 有休の残日数を確認 15日
2020/10/1 有休4日は未使用のまま消滅、有休12日付与 23日

つまり、現在2020/5/17日時点の有休消化日の合計は、以下の2つをレコードをテーブルから検索し、合計すればよいということになります。

  • 直近付与日から現在までの合計(2019/10/01~2020/5/17)
  • 2つ前の付与日から直近付与日までの合計(2018/10/1~2019/10/1)

ただし、この計算式で行く場合は、別途2020/10/01に未消化のまま消滅する有休も計算させる必要があります。

今回扱う消化日数の合計は?

今回、最終的にアプリケーションでで実装したい日数計算は、以下の通りになります。

① 過去2年間の付与日数の合計
② 直近付与日から現在までの消化日数合計
③ 2つ前の付与日から直近付与日までの消化日数合計
④ ①から②と③を引いた残日数

長々書きましたが、私自身の備忘録として書かせていただきました。よって、今後考え方が変わるかも知れません。あくまで記事作成時点のいりふねの脳内の出来事とお考え下さい。
で、今回実装したのは、「②直近付与日から現在までの消化日数合計」なので、これの紹介を以下より行います。

合計する対象範囲を検索

前回までは、社員のレコードのみで表を作成していましたが、今回から有給休暇のレコードも計算に入ってきます。社員はemployeeテーブル、有給休暇はholidayテーブルにそれぞれ格納されているので、コントローラーから編集します。

employees_controller.rb
class EmployeesController < ApplicationController
  def index
    @employees = Employee.where(branch_id: params[:branch_id]).includes(:holidays)
  end
〜中略〜
end

後半にincludesメソッドでemployee_idをもつholidayのレコードも一緒に取得しています。includesメソッドはカリキュラムで「N+1問題」を学んだときに登場しました。その際は、1対多関係のテーブルのうち、外部キーを持つ多のレコードを取得する際に、主キーを持つレコードを先読みするために使用すると学びました。今回は主キーをもつレコードに紐付く外部キーのレコードを取得しているので、順番が逆になっていますが、無事使えました。

参考にした記事「Railsで1対多のテーブルデータを取得し表示させる」@bitarx

次にモデルファイルで、「直近付与日から現在まで」検索を行わせるメソッドを定義します。

models/employee.rb
class Employee < ApplicationRecord
〜中略〜
  def range_to_add_or_delete
    grant_day = hire_date >> 6
    month = grant_day.month
    day = grant_day.mday
    year = Date.today.year
    grant_date_this_year = Time.local(year, month, day)

    if grant_date_this_year > Date.today
      last_year = year - 1
      grant_date_this_year = Time.local(last_year, month, day)
    else
      grant_date_this_year
    end

    holidays.where(created_at: grant_date_this_year..Date.tomorrow)
end

まず、冒頭のgrant_dayは、法定付与日(入社日から6ヵ月後)を計算しています。これに「.month」や「.mday」のメソッドを使用して、月と日の数字を取り出して変数に格納しています。年については、「Date.today」で今日の日付を用意し、それに「.year」メソッドを実行することで年の数字を取り出しています。最後に「Time.local」で3つの数字を合体させ、一旦直近付与日(grant_date_this_year)を完成させます。

参考にさせていただいた記事「[Ruby入門] 14. 日付と時刻を扱う(全パターン網羅)」@prgseek

続けて、IF文の箇所ですが、条件に応じて年を加工して、最終的な直近付与日を確定させています。条件は、先程作った直近付与日(grant_date_this_year)が現在の日付より大きいかというものです。これを行う理由は、法定付与日が12月の方などは、直近法定付与日の結果が2020年12月1日と未来の日付になってしまうからです。そこで、一旦完成させた直近付与日が、現在の日付よりも大きくなっている場合は、年からマイナス1を実行して、「last_year」という新しい変数で、直近付与日を作り直しています。else以降は、必要かどうかが、現段階で判断できません。すみません。

途中結果のビューですが、消化日数のカラムに各社員の直近付与日が表示されました。4月入社の昇太師匠も未来の日付にならずに済んでいます。良かった〜!!

コードの解説に戻ります。
最後の1文で完成した直近付与日から現在までの範囲のcreated_atを検索し、必要なレコードを検索させます。

参考にさせていただいた記事「ActiveRecordで日付・時刻の範囲検索をシンプルに書く方法」@hachi8833

検索結果を合計させる

指定の範囲で検索できたので、これを元に合計を出していきます。モデルファイルに合計を計算させるメソッドを別で書きます。
なお、今回は開発途中ということで付与日の合計日数も同じ方式で計算することにしました。前述した通り付与日の合計の算出方法は別にありますが、付与日の合計には他に実装しなければならないメソッドがあるので、今回はビュー確認用で一旦同じにしておきます。

models/employee.rb
class Employee < ApplicationRecord
〜中略〜
  def total_delete_day
    total_delete_day = range_to_add_or_delete.sum(:delete_day)
  end
  def total_add_day
    total_add_day = range_to_add_or_delete.sum(:add_day)
  end
  def calculate_remaining_days
    a = total_delete_day
    b = total_add_day
    b - a
  end
end

「.sum」メソッドの引数は、合計を出したいカラム名になります。
参考にさせていただいた記事「【Rails】カラムの合計値を求める!」@tomokichi_ruby

ついでにcalculate_remaining_daysを定義し、残日数を計算させています。本来であれば、付与日の合計と消化日の合計を引いた結果が、マイナスであればエラーが出るように条件分岐させるべきですが、同じくビュー確認用でシンプルに引き算だけさせます。

合計した結果をビューに表示

branches/index.html.haml
.main
  =render 'branches/mainheader'
  .main__body
    社員データの編集は「名前」をクリック、削除は右端です。
    %table 
      %tr
~中略~
        %th{id: "short"}
          付与日数
        %th{id: "short"}
          消化日数
        %th{id: "short"}
          残日数
        - @employees.each do |employee|
          %tr
〜中略〜
            %th{id: "short"}
              = employee.total_add_day
            %th{id: "short"}
              = employee.total_delete_day
            %th{id: "short"}
              = employee.calculate_remaining_days
〜中略〜

先程、定義したメソッドを「index.html.haml」に反映させました。ビューの結果は以下のとおりです。無事に計算させることができました。

今日の積み上げ

だいぶ、Qiitaの記事投稿に慣れてきました。内容の割に文章が多いと感じますが、自分が調べたことや躓いたこと、同じ初学者の助けになればと思えば、解説がバカ丁寧になるものなのかな?と思うようにしています。とはいえ、参考にさせていただいた記事のように簡潔に書いて初学者の参考になる記事も多いので、簡潔さを意識したいです。以上。