From b3d3b84c85dfa994d78fba6792a86a00f84bfa4e Mon Sep 17 00:00:00 2001 From: Nishant Samel Date: Thu, 2 May 2024 12:36:20 +0530 Subject: [PATCH] Handle carryover leaves using `Carryover` model (#1824) - This will be used to calculate previous year carryforward leaves count Co-authored-by: Nishant Samel --- app/models/carryover.rb | 40 +++++++++++++++++++ app/models/company.rb | 1 + app/models/leave_type.rb | 1 + app/models/user.rb | 1 + app/services/timeoff_entries/index_service.rb | 18 ++++----- .../20240429142938_create_carryovers.rb | 19 +++++++++ db/schema.rb | 22 +++++++++- spec/factories/carryovers.rb | 14 +++++++ spec/models/carryover_spec.rb | 20 ++++++++++ 9 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 app/models/carryover.rb create mode 100644 db/migrate/20240429142938_create_carryovers.rb create mode 100644 spec/factories/carryovers.rb create mode 100644 spec/models/carryover_spec.rb diff --git a/app/models/carryover.rb b/app/models/carryover.rb new file mode 100644 index 0000000000..e9caacd61d --- /dev/null +++ b/app/models/carryover.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: carryovers +# +# id :bigint not null, primary key +# discarded_at :datetime +# duration :integer +# from_year :integer +# to_year :integer +# total_leave_balance :integer +# created_at :datetime not null +# updated_at :datetime not null +# company_id :bigint not null +# leave_type_id :bigint not null +# user_id :bigint not null +# +# Indexes +# +# index_carryovers_on_company_id (company_id) +# index_carryovers_on_discarded_at (discarded_at) +# index_carryovers_on_leave_type_id (leave_type_id) +# index_carryovers_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (company_id => companies.id) +# fk_rails_... (leave_type_id => leave_types.id) +# fk_rails_... (user_id => users.id) +# +class Carryover < ApplicationRecord + include Discard::Model + + belongs_to :user + belongs_to :company + belongs_to :leave_type + + validates :from_year, :to_year, :total_leave_balance, :duration, presence: true +end diff --git a/app/models/company.rb b/app/models/company.rb index b7a4cbc1db..a77cbeb576 100644 --- a/app/models/company.rb +++ b/app/models/company.rb @@ -44,6 +44,7 @@ class Company < ApplicationRecord has_many :timeoff_entries, through: :users has_many :holidays, dependent: :destroy has_many :holiday_infos, through: :holidays, dependent: :destroy + has_many :carryovers resourcify diff --git a/app/models/leave_type.rb b/app/models/leave_type.rb index f9a4178fe5..b44f8b7a79 100644 --- a/app/models/leave_type.rb +++ b/app/models/leave_type.rb @@ -70,6 +70,7 @@ class LeaveType < ApplicationRecord belongs_to :leave, class_name: "Leave" has_many :timeoff_entries, dependent: :destroy + has_many :carryovers validates :name, presence: true, format: { with: /\A[a-zA-Z\s]+\z/ }, length: { maximum: 20 } validates :color, presence: true, uniqueness: { scope: :leave_id, message: "has already been taken for this leave" } diff --git a/app/models/user.rb b/app/models/user.rb index 8ff3fe20e8..31291c2b68 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -72,6 +72,7 @@ def initialize(msg = "Spam User Login") has_many :clients, through: :projects has_many :client_members, dependent: :destroy has_many :timeoff_entries, dependent: :destroy + has_many :carryovers rolify strict: true diff --git a/app/services/timeoff_entries/index_service.rb b/app/services/timeoff_entries/index_service.rb index 851d50bc92..5685adc0f5 100644 --- a/app/services/timeoff_entries/index_service.rb +++ b/app/services/timeoff_entries/index_service.rb @@ -176,15 +176,15 @@ def user_joined_date def calculate_previous_year_carryforward(leave_type) return 0 unless leave_type - total_leave_type_days = calculate_total_duration(leave_type, previous_year) - - timeoff_entries_duration = leave_type.timeoff_entries.kept.where(user_id:).sum(:duration) - - net_duration = (total_leave_type_days * 8 * 60) - timeoff_entries_duration - - carry_forward_duration = leave_type.carry_forward_days * 8 * 60 - - net_duration > carry_forward_duration ? carry_forward_duration : net_duration > 0 ? net_duration : 0 + last_year_carryover = + Carryover.find_by( + user_id:, + company_id: current_company.id, + leave_type_id: leave_type.id, + from_year: previous_year + ) + + last_year_carryover && (last_year_carryover.duration > 0) ? last_year_carryover.duration : 0 end end end diff --git a/db/migrate/20240429142938_create_carryovers.rb b/db/migrate/20240429142938_create_carryovers.rb new file mode 100644 index 0000000000..18f0342ca5 --- /dev/null +++ b/db/migrate/20240429142938_create_carryovers.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class CreateCarryovers < ActiveRecord::Migration[7.1] + def change + create_table :carryovers do |t| + t.references :user, null: false, foreign_key: true + t.references :company, null: false, foreign_key: true + t.references :leave_type, null: false, foreign_key: true + t.integer :from_year + t.integer :to_year + t.integer :total_leave_balance + t.integer :duration + t.datetime :discarded_at + + t.timestamps + end + add_index :carryovers, :discarded_at + end +end diff --git a/db/schema.rb b/db/schema.rb index 813770e521..66a85e37c2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_04_11_174949) do +ActiveRecord::Schema[7.1].define(version: 2024_04_29_142938) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -100,6 +100,23 @@ t.index ["visit_token"], name: "index_ahoy_visits_on_visit_token", unique: true end + create_table "carryovers", force: :cascade do |t| + t.bigint "user_id", null: false + t.bigint "company_id", null: false + t.bigint "leave_type_id", null: false + t.integer "from_year" + t.integer "to_year" + t.integer "total_leave_balance" + t.integer "duration" + t.datetime "discarded_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["company_id"], name: "index_carryovers_on_company_id" + t.index ["discarded_at"], name: "index_carryovers_on_discarded_at" + t.index ["leave_type_id"], name: "index_carryovers_on_leave_type_id" + t.index ["user_id"], name: "index_carryovers_on_user_id" + end + create_table "client_members", force: :cascade do |t| t.bigint "client_id", null: false t.bigint "user_id", null: false @@ -532,6 +549,9 @@ add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" + add_foreign_key "carryovers", "companies" + add_foreign_key "carryovers", "leave_types" + add_foreign_key "carryovers", "users" add_foreign_key "client_members", "clients" add_foreign_key "client_members", "companies" add_foreign_key "client_members", "users" diff --git a/spec/factories/carryovers.rb b/spec/factories/carryovers.rb new file mode 100644 index 0000000000..fa1ec861ca --- /dev/null +++ b/spec/factories/carryovers.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :carryover do + user + company + leave_type + from_year { 2023 } + to_year { 2024 } + total_leave_balance { 10000 } # in minutes + duration { 2400 } # in minutes + discarded_at { "2024-04-29 19:59:39" } + end +end diff --git a/spec/models/carryover_spec.rb b/spec/models/carryover_spec.rb new file mode 100644 index 0000000000..eb2fd6115c --- /dev/null +++ b/spec/models/carryover_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Carryover, type: :model do + subject { build(:carryover) } + + describe "Associations" do + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:company) } + it { is_expected.to belong_to(:leave_type) } + end + + describe "Validations" do + it { is_expected.to validate_presence_of(:from_year) } + it { is_expected.to validate_presence_of(:to_year) } + it { is_expected.to validate_presence_of(:total_leave_balance) } + it { is_expected.to validate_presence_of(:duration) } + end +end