hr_lite 0.57.0 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9104f47987b7a8a7e613fd6d07ba269de6b1f2af1ecdd39eed2684b7a62f891
4
- data.tar.gz: 3a73e730997fefd65b794b47dc14d0c1d836ac60484ee32c5256f601d4cd8043
3
+ metadata.gz: 3c02612fcf572f2b42eab074882c2c07b975e9e272b6b94eb58b3a40edb00cdc
4
+ data.tar.gz: a537c7010fc7e5c5bbfc5e85eaa62d4afb4bc5b04eae953500d64ed5f4d1530e
5
5
  SHA512:
6
- metadata.gz: 89aae488789fee0bf918a04c4570ed9186860be2acadbca4647d314bd41a1d6008709d10bd341a1636639534086bac276e335e50b2753b1b02ff49f51e8cb241
7
- data.tar.gz: b152d8230fcccf6c6eb7dbcfbfc5152258d125fa3083a3dad7c439b1188059d01166f0e8ada8d7a5c25900f2223dbb5f192a8d4a502fee126ff4e8b210ae6dd4
6
+ metadata.gz: 54ca6bb8b70ce33601c8d163caf80556bdec2c653b0360f4d3ff418b0180a14fe2487292ef83499d989c5b064b919857bb083197789fef628bf42f736544a6c5
7
+ data.tar.gz: cf2c5930379f973d7a08870b87e3b3c7c28117e4f09921f5465702117c931195203df20292d4532e4b40ce4708479be770627acf7fa5e0a524791c5c81e1db5a
data/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.58.0] - 2026-10-21
11
+
12
+ Leave and birthdays for the whole team, Keka-style. No migrations. Hosts schedule `HrLite::TeamNoticesJob` daily at 10am.
13
+
14
+ - **Pending leave shows on Home.** "On leave this week" lists pending requests too, tagged "Pending approval". Approval is management's decision, not a reason to hide the leave.
15
+ - **10am "On leave today" email** to every current employee: name and dates, never the leave type. No one out, no email.
16
+ - **10am "Birthdays today" email**, same rules. A 29 Feb birthday falls on 28 Feb outside leap years.
17
+ - **Birthdays this week** card on Home, Monday to Sunday, today marked.
18
+
19
+ ## [0.57.1] - 2026-10-19
20
+
21
+ Forms in the host's iOS and Android staff apps (D24). No migrations.
22
+
23
+ - **A save shows its confirmation again.** Native pages reload on every Turbo visit, and the page Turbo fetched and threw away used to spend the flash. A GET fetched by Turbo in the app now keeps it for the reload.
24
+ - **A refused form keeps its errors and what was typed.** A 422 used to be discarded and the form reloaded blank. It is now drawn in place (with `turbo-cache-control: no-cache`), and the next visit reloads as before.
25
+ - Browsers are unchanged: they never load Turbo.
26
+
10
27
  ## [0.57.0] - 2026-10-19
11
28
 
12
29
  No migration.
data/README.md CHANGED
@@ -384,6 +384,7 @@ whatever you run). Each is idempotent and sends nothing on a quiet day.
384
384
  | Job | Schedule | Purpose |
385
385
  |---|---|---|
386
386
  | `HrLite::DailyDigestJob` | each morning | Leadership digest: who is out today, pending approvals, flagged punches, missing checkouts |
387
+ | `HrLite::TeamNoticesJob` | daily, 10am | Emails every current employee who is on leave today and whose birthday it is; each email is skipped when its list is empty |
387
388
  | `HrLite::PayrollAutoDraftJob` | monthly, on the 1st | Draft and compute the previous month's payroll from attendance, then notify the approvers (`payroll.approve`) and payroll operators |
388
389
  | `HrLite::CaReviewReminderJob` | weekly (it acts only in March) | Tell CA reviewers how many slips and declarations are still unverified before 31 March |
389
390
  | `HrLite::CaReviewOverdueJob` | 31 March, evening | Tell payroll and HR what the CA has not verified, and any flags left open |
@@ -9,6 +9,9 @@ module HrLite
9
9
  before_action :hr_authenticate!
10
10
  before_action :hr_set_current_actor
11
11
  around_action :hr_use_time_zone
12
+ # Native pages reload every Turbo visit (see the layout): the page Turbo
13
+ # fetched and threw away must not spend the flash the reload shows.
14
+ after_action -> { flash.keep if request.get? && request.headers["X-Turbo-Request-Id"] && HrLite.config.native_app.call(request) }
12
15
 
13
16
  helper_method :hr_current_user, :hr_admin?, :hr_leadership?, :hr_superadmin?, :hr_operations?, :hr_display_name,
14
17
  :hr_can?, :hr_reaches?, :hr_access
@@ -3,9 +3,11 @@ module HrLite
3
3
  def index
4
4
  @latest_kudos = Kudo.recent.includes(:giver, kudo_mentions: :user).limit(3)
5
5
  @awards = Award.current
6
- @out_week = LeaveRequest.approved.overlapping_range(Date.current, Date.current.end_of_week)
6
+ # Pending shows too: approval is management's call, not a reason to hide the leave.
7
+ @out_week = LeaveRequest.where(status: %w[pending approved]).overlapping_range(Date.current, Date.current.end_of_week)
7
8
  .includes(:user).where(user_id: HrLite.active_employees.map(&:id))
8
9
  .sort_by { |leave| hr_display_name(leave.user).downcase }
10
+ @birthdays = EmployeeProfile.birthdays_in(Date.current.all_week)
9
11
  @attention = attention_days
10
12
  @waiting = Approval.pending_for(hr_current_user).count
11
13
  @mine_pending = [ LeaveRequest.includes(:leave_type), RegularizationRequest, CompOffRequest, WfhRequest ]
@@ -0,0 +1,26 @@
1
+ module HrLite
2
+ # 10am email to every current employee: who is on leave today, whose
3
+ # birthday it is. Each goes only when its list has someone on it.
4
+ class TeamNoticesJob < ApplicationJob
5
+ queue_as :default
6
+
7
+ def perform(date: Date.current)
8
+ staff = HrLite.active_employees(on: date)
9
+ out = LeaveRequest.where(status: %w[pending approved], user_id: staff.map(&:id))
10
+ .overlapping_range(date, date).includes(:user)
11
+ announce("leave.out_today", "On leave today", staff, date,
12
+ out.map { |l| "#{HrLite.display_name(l.user)} — #{l.date_range_label}#{' (pending approval)' if l.pending?}" })
13
+ announce("birthday.today", "Birthdays today", staff, date,
14
+ EmployeeProfile.birthdays_in(date..date).map { |p, _| "Happy birthday, #{HrLite.display_name(p.user)}" })
15
+ end
16
+
17
+ private
18
+
19
+ def announce(event, title, staff, date, lines)
20
+ return if lines.empty?
21
+
22
+ Notifications.publish(event, title: "#{title} — #{date.strftime('%a %-d %b')}", lines: lines.sort,
23
+ email_to: staff, path: "/")
24
+ end
25
+ end
26
+ end
@@ -101,6 +101,19 @@ module HrLite
101
101
  date_of_joining <= date && (date_of_exit.nil? || date_of_exit >= date)
102
102
  end
103
103
 
104
+ # 29 Feb birthdays fall on 28 Feb outside leap years.
105
+ def birthday_in(year)
106
+ day = Date.valid_date?(year, date_of_birth.month, date_of_birth.day) ? date_of_birth.day : 28
107
+ Date.new(year, date_of_birth.month, day)
108
+ end
109
+
110
+ # [profile, day] for current staff whose birthday falls in `range`, earliest first.
111
+ def self.birthdays_in(range)
112
+ where(user_id: HrLite.active_employees(on: range.first).map(&:id)).where.not(date_of_birth: nil).includes(:user)
113
+ .filter_map { |p| (day = range.find { |d| p.birthday_in(d.year) == d }) && [ p, day ] }
114
+ .sort_by { |p, day| [ day, HrLite.display_name(p.user).downcase ] }
115
+ end
116
+
104
117
  def employment_range_in(month)
105
118
  from = [ date_of_joining, month.beginning_of_month ].max
106
119
  to = [ date_of_exit || month.end_of_month, month.end_of_month ].min
@@ -40,6 +40,22 @@
40
40
  <%= render "hr_lite/leave_requests/out_list", leaves: @out_week, empty: "Nobody is on leave this week." %>
41
41
  </section>
42
42
 
43
+ <section class="hrl-card">
44
+ <h2 class="hrl-card__title">Birthdays this week</h2>
45
+ <% if @birthdays.any? %>
46
+ <div class="hrl-feed">
47
+ <% @birthdays.each do |profile, day| %>
48
+ <div class="hrl-feed__item">
49
+ <div class="hrl-feed__body"><strong><%= hr_display_name(profile.user) %></strong>
50
+ <div class="hrl-feed__meta"><%= day == Date.current ? "Today" : day.strftime("%a %-d %b") %></div></div>
51
+ </div>
52
+ <% end %>
53
+ </div>
54
+ <% else %>
55
+ <p class="hrl-muted hrl-small">No birthdays this week.</p>
56
+ <% end %>
57
+ </section>
58
+
43
59
  <% if @awards.any? %>
44
60
  <section class="hrl-card">
45
61
  <h2 class="hrl-card__title">Employee of the month, quarter and year</h2>
@@ -4,7 +4,7 @@
4
4
  <% leaves.first(10).each do |leave| %>
5
5
  <div class="hrl-feed__item">
6
6
  <div class="hrl-feed__body"><%= tag.strong hr_display_name(leave.user), title: "On leave: #{leave.date_range_label}" %>
7
- <div class="hrl-feed__meta"><%= leave.date_range_label %></div></div>
7
+ <div class="hrl-feed__meta"><%= leave.date_range_label %><%= " · Pending approval" if leave.pending? %></div></div>
8
8
  </div>
9
9
  <% end %>
10
10
  </div>
@@ -10,8 +10,14 @@
10
10
  <% if HrLite.config.native_app.call(request) %>
11
11
  <%# The host's iOS/Android shells drive pages through Turbo. "reload" makes
12
12
  every visit a full page load, so the plain scripts below run exactly
13
- as they do in a browser. Browsers never load Turbo here. %>
14
- <meta name="turbo-visit-control" content="reload">
13
+ as they do in a browser. Browsers never load Turbo here. A refused
14
+ form (422) is drawn in place instead: the reload would bring the form
15
+ back blank, with no error. no-cache keeps that render out of history. %>
16
+ <% if response.unprocessable? %>
17
+ <meta name="turbo-cache-control" content="no-cache">
18
+ <% else %>
19
+ <meta name="turbo-visit-control" content="reload">
20
+ <% end %>
15
21
  <%= javascript_include_tag "hr_lite/vendor/turbo.min", type: "module" %>
16
22
  <% end %>
17
23
  <%= stylesheet_link_tag "hr_lite/hr_lite", media: "all" %>
@@ -56,6 +56,9 @@ module HrLite
56
56
  "policy.published" => { bell: true, email: true, leadership_email: false, leadership_bell: false },
57
57
  "holiday.published" => { bell: true, email: true, leadership_email: false, leadership_bell: false },
58
58
  "digest.daily" => { bell: false, email: false, leadership_email: true, leadership_bell: false },
59
+ # 10am team emails (TeamNoticesJob) to every current employee; skipped when the list is empty.
60
+ "leave.out_today" => { bell: false, email: true, leadership_email: false, leadership_bell: false },
61
+ "birthday.today" => { bell: false, email: true, leadership_email: false, leadership_bell: false },
59
62
  "resignation.submitted" => { bell: true, email: false, leadership_email: true, leadership_bell: true },
60
63
  "resignation.accepted" => { bell: true, email: true, leadership_email: true, leadership_bell: false },
61
64
  "resignation.withdrawn" => { bell: true, email: false, leadership_email: true, leadership_bell: false },
@@ -1,3 +1,3 @@
1
1
  module HrLite
2
- VERSION = "0.57.0"
2
+ VERSION = "0.58.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hr_lite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.57.0
4
+ version: 0.58.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - kshitiz sinha
@@ -173,6 +173,7 @@ files:
173
173
  - app/jobs/hr_lite/payroll_auto_draft_job.rb
174
174
  - app/jobs/hr_lite/payroll_compute_job.rb
175
175
  - app/jobs/hr_lite/slips_ready_job.rb
176
+ - app/jobs/hr_lite/team_notices_job.rb
176
177
  - app/jobs/hr_lite/work_mode_expire_job.rb
177
178
  - app/mailers/hr_lite/application_mailer.rb
178
179
  - app/mailers/hr_lite/event_mailer.rb