rails_attend 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +22 -0
- data/Rakefile +32 -0
- data/app/controllers/attend/admin/absence_stats_controller.rb +60 -0
- data/app/controllers/attend/admin/absences_controller.rb +77 -0
- data/app/controllers/attend/admin/attendance_logs_controller.rb +103 -0
- data/app/controllers/attend/admin/attendance_settings_controller.rb +86 -0
- data/app/controllers/attend/admin/attendance_stats_controller.rb +58 -0
- data/app/controllers/attend/admin/attendances_controller.rb +57 -0
- data/app/controllers/attend/admin/base_controller.rb +4 -0
- data/app/controllers/attend/admin/extra_days_controller.rb +63 -0
- data/app/controllers/attend/admin/financial_months_controller.rb +69 -0
- data/app/controllers/attend/admin/overtimes_controller.rb +59 -0
- data/app/controllers/attend/me/absences_controller.rb +71 -0
- data/app/controllers/attend/me/attendance_logs_controller.rb +74 -0
- data/app/controllers/attend/me/attendance_settings_controller.rb +52 -0
- data/app/controllers/attend/me/attendance_stats_controller.rb +27 -0
- data/app/controllers/attend/me/attendances_controller.rb +43 -0
- data/app/controllers/attend/me/base_controller.rb +4 -0
- data/app/controllers/attend/me/calendars_controller.rb +33 -0
- data/app/controllers/attend/me/overtimes_controller.rb +65 -0
- data/app/javascript/packs/controllers/attend/admin/attendance_logs/index.js +1 -0
- data/app/javascript/packs/controllers/attend/admin/attendance_logs/my.js +14 -0
- data/app/javascript/packs/controllers/attend/admin/attendance_settings/create.js +1 -0
- data/app/javascript/packs/controllers/attend/admin/attendance_settings/index.js +1 -0
- data/app/javascript/packs/controllers/attend/admin/attendance_settings/my.js +13 -0
- data/app/javascript/packs/controllers/attend/admin/attendance_settings/new.js +11 -0
- data/app/javascript/packs/controllers/attend/me/absences/new.js +16 -0
- data/app/javascript/packs/controllers/attend/me/absences/update.js +3 -0
- data/app/javascript/packs/controllers/attend/me/attendance_settings/create.js +1 -0
- data/app/javascript/packs/controllers/attend/me/attendance_settings/edit.js +1 -0
- data/app/javascript/packs/controllers/attend/me/attendance_settings/new.js +2 -0
- data/app/javascript/packs/controllers/attend/me/calendars/show.js +48 -0
- data/app/jobs/attendance_job.rb +14 -0
- data/app/models/absence/attend/annual_leave.rb +5 -0
- data/app/models/absence/attend/bereavement_leave.rb +5 -0
- data/app/models/absence/attend/business_trip.rb +5 -0
- data/app/models/absence/attend/children_day_leave.rb +5 -0
- data/app/models/absence/attend/compensatory_leave.rb +5 -0
- data/app/models/absence/attend/marriage_leave.rb +4 -0
- data/app/models/absence/attend/maternity_leave.rb +5 -0
- data/app/models/absence/attend/parental_leave.rb +5 -0
- data/app/models/absence/attend/personal_leave.rb +5 -0
- data/app/models/absence/attend/prenatal_leave.rb +4 -0
- data/app/models/absence/attend/sick_leave.rb +5 -0
- data/app/models/absence/attend/vacation_leave.rb +5 -0
- data/app/models/absence/attend/welfare_leave.rb +5 -0
- data/app/models/attend/absence.rb +5 -0
- data/app/models/attend/absence_stat.rb +5 -0
- data/app/models/attend/application_record.rb +5 -0
- data/app/models/attend/attendance.rb +5 -0
- data/app/models/attend/attendance_log.rb +5 -0
- data/app/models/attend/attendance_setting.rb +5 -0
- data/app/models/attend/attendance_stat.rb +5 -0
- data/app/models/attend/ext/member.rb +33 -0
- data/app/models/attend/extra_day.rb +5 -0
- data/app/models/attend/financial_month.rb +5 -0
- data/app/models/attend/model/absence/business_trip.rb +10 -0
- data/app/models/attend/model/absence/compensatory_leave.rb +5 -0
- data/app/models/attend/model/absence/personal_leave.rb +5 -0
- data/app/models/attend/model/absence/welfare_leave/annual_leave.rb +16 -0
- data/app/models/attend/model/absence/welfare_leave/vacation_leave.rb +16 -0
- data/app/models/attend/model/absence/welfare_leave.rb +5 -0
- data/app/models/attend/model/absence.rb +208 -0
- data/app/models/attend/model/absence_stat.rb +17 -0
- data/app/models/attend/model/attendance.rb +314 -0
- data/app/models/attend/model/attendance_log.rb +165 -0
- data/app/models/attend/model/attendance_setting.rb +61 -0
- data/app/models/attend/model/attendance_stat.rb +104 -0
- data/app/models/attend/model/extra_day.rb +21 -0
- data/app/models/attend/model/financial_month.rb +114 -0
- data/app/models/attend/model/overtime.rb +93 -0
- data/app/models/attend/overtime.rb +5 -0
- data/app/models/attend.rb +11 -0
- data/app/models/concerns/absent.rb +103 -0
- data/app/views/admin/_attend_nav.html.erb +15 -0
- data/app/views/attend/admin/absence_stats/_filter_form.html.erb +9 -0
- data/app/views/attend/admin/absence_stats/_form.html.erb +12 -0
- data/app/views/attend/admin/absence_stats/_index_tbody.html.erb +3 -0
- data/app/views/attend/admin/absence_stats/_index_thead.html.erb +4 -0
- data/app/views/attend/admin/absence_stats/index.html.erb +5 -0
- data/app/views/attend/admin/absences/_absence.html.erb +38 -0
- data/app/views/attend/admin/absences/_edit_form.html.erb +3 -0
- data/app/views/attend/admin/absences/_filter_form.html.erb +8 -0
- data/app/views/attend/admin/absences/_form.html.erb +5 -0
- data/app/views/attend/admin/absences/_index_tbody.html.erb +18 -0
- data/app/views/attend/admin/absences/_index_thead.html.erb +10 -0
- data/app/views/attend/admin/absences/_new_form.html.erb +3 -0
- data/app/views/attend/admin/absences/_show.html.erb +11 -0
- data/app/views/attend/admin/absences/index.html.erb +5 -0
- data/app/views/attend/admin/absences/my.html.erb +5 -0
- data/app/views/attend/admin/attendance_logs/_edit_form.html.erb +5 -0
- data/app/views/attend/admin/attendance_logs/_filter_form.html.erb +11 -0
- data/app/views/attend/admin/attendance_logs/_form.html.erb +6 -0
- data/app/views/attend/admin/attendance_logs/_index_thead.html.erb +19 -0
- data/app/views/attend/admin/attendance_logs/_list.html.erb +61 -0
- data/app/views/attend/admin/attendance_logs/_new_form.html.erb +3 -0
- data/app/views/attend/admin/attendance_logs/index.html.erb +5 -0
- data/app/views/attend/admin/attendance_logs/my.html.erb +5 -0
- data/app/views/attend/admin/attendance_settings/_button.html.erb +4 -0
- data/app/views/attend/admin/attendance_settings/_filter_form.html.erb +11 -0
- data/app/views/attend/admin/attendance_settings/_form.html.erb +7 -0
- data/app/views/attend/admin/attendance_settings/_index_tbody.html.erb +15 -0
- data/app/views/attend/admin/attendance_settings/_index_thead.html.erb +10 -0
- data/app/views/attend/admin/attendance_settings/_new_form.html.erb +8 -0
- data/app/views/attend/admin/attendance_settings/_show_table.html.erb +12 -0
- data/app/views/attend/admin/attendance_settings/index.html.erb +5 -0
- data/app/views/attend/admin/attendance_settings/my.html.erb +5 -0
- data/app/views/attend/admin/attendance_stats/_filter_form.html.erb +10 -0
- data/app/views/attend/admin/attendance_stats/_form.html.erb +6 -0
- data/app/views/attend/admin/attendance_stats/_index_thead.html.erb +10 -0
- data/app/views/attend/admin/attendance_stats/index.html.erb +44 -0
- data/app/views/attend/admin/attendances/_button.html.erb +2 -0
- data/app/views/attend/admin/attendances/_filter_form.html.erb +10 -0
- data/app/views/attend/admin/attendances/_form.html.erb +23 -0
- data/app/views/attend/admin/attendances/_index_tbody.html.erb +61 -0
- data/app/views/attend/admin/attendances/_index_thead.html.erb +22 -0
- data/app/views/attend/admin/attendances/_new_form.html.erb +4 -0
- data/app/views/attend/admin/attendances/index.html.erb +7 -0
- data/app/views/attend/admin/base/_financial_month_menu.html.erb +10 -0
- data/app/views/attend/admin/base/_nav.html.erb +3 -0
- data/app/views/attend/admin/base/_nav_attendances.html.erb +7 -0
- data/app/views/attend/admin/extra_days/_breadcrumb.html.erb +0 -0
- data/app/views/attend/admin/extra_days/_edit_form.html.erb +4 -0
- data/app/views/attend/admin/extra_days/_filter_form.html.erb +5 -0
- data/app/views/attend/admin/extra_days/_form.html.erb +4 -0
- data/app/views/attend/admin/extra_days/_index_tbody.html.erb +3 -0
- data/app/views/attend/admin/extra_days/_index_thead.html.erb +4 -0
- data/app/views/attend/admin/extra_days/_new_form.html.erb +4 -0
- data/app/views/attend/admin/extra_days/index.html.erb +7 -0
- data/app/views/attend/admin/financial_months/_breadcrumb.html.erb +0 -0
- data/app/views/attend/admin/financial_months/_edit_form.html.erb +4 -0
- data/app/views/attend/admin/financial_months/_filter_form.html.erb +9 -0
- data/app/views/attend/admin/financial_months/_form.html.erb +4 -0
- data/app/views/attend/admin/financial_months/_index_tbody.html.erb +9 -0
- data/app/views/attend/admin/financial_months/_index_thead.html.erb +6 -0
- data/app/views/attend/admin/financial_months/_new_form.html.erb +4 -0
- data/app/views/attend/admin/financial_months/events.json.jbuilder +9 -0
- data/app/views/attend/admin/financial_months/index.html.erb +7 -0
- data/app/views/attend/admin/financial_months/index.js +60 -0
- data/app/views/attend/admin/financial_months/show.html.erb +10 -0
- data/app/views/attend/admin/overtimes/_button.html.erb +0 -0
- data/app/views/attend/admin/overtimes/_edit_form.html.erb +4 -0
- data/app/views/attend/admin/overtimes/_filter_form.html.erb +8 -0
- data/app/views/attend/admin/overtimes/_form.html.erb +3 -0
- data/app/views/attend/admin/overtimes/_index_tbody.html.erb +22 -0
- data/app/views/attend/admin/overtimes/_index_thead.html.erb +9 -0
- data/app/views/attend/admin/overtimes/_new_form.html.erb +4 -0
- data/app/views/attend/admin/overtimes/index.html.erb +5 -0
- data/app/views/attend/admin/overtimes/my.html.erb +8 -0
- data/app/views/attend/me/absences/_absence.html.erb +34 -0
- data/app/views/attend/me/absences/_edit_form.html.erb +3 -0
- data/app/views/attend/me/absences/_filter_form.html.erb +11 -0
- data/app/views/attend/me/absences/_form.html.erb +33 -0
- data/app/views/attend/me/absences/_index_tbody.html.erb +24 -0
- data/app/views/attend/me/absences/_index_thead.html.erb +8 -0
- data/app/views/attend/me/absences/_new_form.html.erb +3 -0
- data/app/views/attend/me/absences/_redeeming_form.html.erb +5 -0
- data/app/views/attend/me/absences/_show.html.erb +11 -0
- data/app/views/attend/me/absences/dashboard.html.erb +44 -0
- data/app/views/attend/me/absences/index.html.erb +7 -0
- data/app/views/attend/me/absences/redeeming.js.erb +6 -0
- data/app/views/attend/me/absences/show.html.erb +1 -0
- data/app/views/attend/me/attendance_logs/_edit_form.html.erb +4 -0
- data/app/views/attend/me/attendance_logs/_filter_form.html.erb +11 -0
- data/app/views/attend/me/attendance_logs/_form.html.erb +11 -0
- data/app/views/attend/me/attendance_logs/_index_thead.html.erb +9 -0
- data/app/views/attend/me/attendance_logs/_new_form.html.erb +4 -0
- data/app/views/attend/me/attendance_logs/index.html.erb +42 -0
- data/app/views/attend/me/attendance_settings/_edit_form.html.erb +6 -0
- data/app/views/attend/me/attendance_settings/_index_tbody.html.erb +15 -0
- data/app/views/attend/me/attendance_settings/_index_thead.html.erb +12 -0
- data/app/views/attend/me/attendance_settings/_new_form.html.erb +8 -0
- data/app/views/attend/me/attendance_settings/index.html.erb +5 -0
- data/app/views/attend/me/attendance_stats/_form.html.erb +6 -0
- data/app/views/attend/me/attendance_stats/_search_form.html.erb +5 -0
- data/app/views/attend/me/attendance_stats/edit.html.erb +9 -0
- data/app/views/attend/me/attendance_stats/index.html.erb +49 -0
- data/app/views/attend/me/attendance_stats/new.html.erb +9 -0
- data/app/views/attend/me/attendance_stats/show.html.erb +28 -0
- data/app/views/attend/me/attendances/_button.html.erb +3 -0
- data/app/views/attend/me/attendances/_filter_form.html.erb +9 -0
- data/app/views/attend/me/attendances/_form.html.erb +22 -0
- data/app/views/attend/me/attendances/_index_tbody.html.erb +49 -0
- data/app/views/attend/me/attendances/_index_thead.html.erb +19 -0
- data/app/views/attend/me/attendances/index.html.erb +7 -0
- data/app/views/attend/me/base/_menu.html.erb +8 -0
- data/app/views/attend/me/base/_nav_absences.html.erb +9 -0
- data/app/views/attend/me/base/_nav_attendances.html.erb +10 -0
- data/app/views/attend/me/calendars/events.json.jbuilder +19 -0
- data/app/views/attend/me/calendars/show.html.erb +12 -0
- data/app/views/attend/me/overtimes/_edit_form.html.erb +4 -0
- data/app/views/attend/me/overtimes/_filter_form.html.erb +9 -0
- data/app/views/attend/me/overtimes/_form.html.erb +16 -0
- data/app/views/attend/me/overtimes/_index_tbody.html.erb +23 -0
- data/app/views/attend/me/overtimes/_index_thead.html.erb +6 -0
- data/app/views/attend/me/overtimes/_new_form.html.erb +4 -0
- data/app/views/attend/me/overtimes/index.html.erb +7 -0
- data/app/views/me/_attend_nav.html.erb +12 -0
- data/app/views/me/_nav_attendance_settings.html.erb +8 -0
- data/config/locales/en.attributes.yml +34 -0
- data/config/locales/en.controller.yml +18 -0
- data/config/locales/en.enum.yml +43 -0
- data/config/locales/en.notify.yml +38 -0
- data/config/locales/zh.attributes.yml +34 -0
- data/config/locales/zh.controller.yml +40 -0
- data/config/locales/zh.enum.yml +43 -0
- data/config/locales/zh.notify.yml +38 -0
- data/config/routes.rb +70 -0
- data/lib/rails_attend/config.rb +11 -0
- data/lib/rails_attend/engine.rb +25 -0
- data/lib/rails_attend.rb +2 -0
- metadata +269 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
module Attend
|
2
|
+
module Ext::Member
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
has_one :absence_stat, -> { where(year: Date.today.year) }
|
7
|
+
has_many :absences, class_name: 'Attend::Absence', dependent: :destroy
|
8
|
+
has_many :overtimes, class_name: 'Attend::Overtime', dependent: :destroy
|
9
|
+
has_many :attendances, class_name: 'Attend::Attendance', dependent: :destroy
|
10
|
+
has_many :attendance_logs, class_name: 'Attend::AttendanceLog', dependent: :nullify
|
11
|
+
has_many :attendance_stats, class_name: 'Attend::AttendanceStat', dependent: :destroy
|
12
|
+
has_many :attendance_settings, class_name: 'Attend::AttendanceSetting', dependent: :destroy
|
13
|
+
|
14
|
+
composed_of :absent, mapping: ['id', 'member_id']
|
15
|
+
end
|
16
|
+
|
17
|
+
def attendance_setting(date = Date.today)
|
18
|
+
_attendance_setting = self.attendance_settings.default_where('financial_month.begin_date-lte': date).first
|
19
|
+
_attendance_setting || self.attendance_settings.build(financial_month: FinancialMonth.current_month)
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_time(date = Date.today)
|
23
|
+
hour, min = (self.attendance_setting(date).on_time).split(':')
|
24
|
+
date.in_time_zone(timezone).change(hour: hour, min: min)
|
25
|
+
end
|
26
|
+
|
27
|
+
def off_time(date = Date.today)
|
28
|
+
hour, min = (self.attendance_setting(date).off_time).split(':')
|
29
|
+
date.in_time_zone(timezone).change(hour: hour, min: min)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Attend
|
2
|
+
module Model::Absence::WelfareLeave::AnnualLeave #年假
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
validate :validate_left_hours, if: -> { start_at_changed? || finish_at_changed? }
|
7
|
+
end
|
8
|
+
|
9
|
+
def validate_left_hours
|
10
|
+
if self.hours > self.absent.left_hours.to_f
|
11
|
+
self.errors.add :base, 'You do not have enough annual hours!'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Attend
|
2
|
+
module Model::Absence::WelfareLeave::VacationLeave
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
validate :validate_left_hours, if: -> { start_at_changed? || finish_at_changed? }
|
7
|
+
end
|
8
|
+
|
9
|
+
def validate_left_hours
|
10
|
+
if self.hours > self.absent.left_hours.to_f
|
11
|
+
self.errors.add :base, 'You do not have enough annual hours!'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
module Attend
|
2
|
+
module Model::Absence
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
attribute :type, :string
|
7
|
+
attribute :state, :string, default: 'init'
|
8
|
+
attribute :hours, :float, default: 0
|
9
|
+
attribute :kind, :string
|
10
|
+
attribute :start_at, :datetime
|
11
|
+
attribute :finish_at, :datetime
|
12
|
+
attribute :note, :string, limit: 2048
|
13
|
+
attribute :comment, :string, limit: 2048
|
14
|
+
attribute :redeeming, :boolean
|
15
|
+
attribute :redeeming_days, :string, array: true
|
16
|
+
attribute :processed, :boolean, default: false
|
17
|
+
attribute :divided, :boolean, default: false
|
18
|
+
|
19
|
+
belongs_to :member
|
20
|
+
belongs_to :merged, class_name: self.name, optional: true
|
21
|
+
has_many :divideds, class_name: self.name, foreign_key: 'merged_id'
|
22
|
+
|
23
|
+
composed_of :absent, mapping: [
|
24
|
+
['member_id', 'member_id'],
|
25
|
+
['type', 'type']
|
26
|
+
]
|
27
|
+
|
28
|
+
enum state: {
|
29
|
+
init: 'init',
|
30
|
+
approved: 'approved',
|
31
|
+
denied: 'denied'
|
32
|
+
}
|
33
|
+
|
34
|
+
scope :current_range, -> (begin_date, finish_date) {
|
35
|
+
default_where('start_at-gte': begin_date, 'finish_at-lte': finish_date, state: ['init', 'approved'])
|
36
|
+
}
|
37
|
+
scope :current_range_stat, -> (begin_date, finish_date) {
|
38
|
+
current_range(begin_date, finish_date).group(:type).sum(:hours)
|
39
|
+
}
|
40
|
+
scope :current_range_events, -> (begin_date, finish_date, department_ids) {
|
41
|
+
current_range(begin_date, finish_date).default_where('member.department_id': department_ids).map do |i|
|
42
|
+
{
|
43
|
+
title: i.member.name,
|
44
|
+
start: i.start_at,
|
45
|
+
end: i.finish_at,
|
46
|
+
url: i.url_helpers.oa_absence_path(i.id)
|
47
|
+
}
|
48
|
+
end.as_json
|
49
|
+
}
|
50
|
+
|
51
|
+
validate :validate_same_month, if: -> { start_at_changed? || finish_at_changed? }
|
52
|
+
validates :start_at, presence: true
|
53
|
+
validates :finish_at, presence: true
|
54
|
+
|
55
|
+
before_save :compute_hours
|
56
|
+
after_create_commit :send_notification
|
57
|
+
|
58
|
+
delegate :name, to: :member, prefix: true
|
59
|
+
acts_as_notify(
|
60
|
+
:default,
|
61
|
+
only: [:hours, :start_at, :finish_at, :note],
|
62
|
+
methods: [:member_name, :type_i18n, :state_i18n]
|
63
|
+
)
|
64
|
+
acts_as_notify(
|
65
|
+
:request,
|
66
|
+
only: [:hours, :start_at, :finish_at, :note],
|
67
|
+
methods: [:member_name, :type_i18n, :state_i18n],
|
68
|
+
cc_emails: [
|
69
|
+
-> (o){ o.member.office&.absence_email },
|
70
|
+
-> (o){ o.member.email },
|
71
|
+
-> (o){ o.cc_emails }
|
72
|
+
]
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def do_trigger(params = {})
|
77
|
+
self.trigger_to(params.slice(:state))
|
78
|
+
|
79
|
+
if self.approved?
|
80
|
+
sync_attendance
|
81
|
+
end
|
82
|
+
|
83
|
+
self.class.transaction do
|
84
|
+
self.save!
|
85
|
+
to_notification(
|
86
|
+
member: member,
|
87
|
+
link: url_helpers.oa_absences_url(id: self.id),
|
88
|
+
verbose: true
|
89
|
+
)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def send_notification
|
94
|
+
to_notification(
|
95
|
+
member: member.parent,
|
96
|
+
link: url_helpers.my_admin_absences_url(id: self.id),
|
97
|
+
code: :request,
|
98
|
+
verbose: true
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
def compute_hours
|
103
|
+
_hours = 0
|
104
|
+
|
105
|
+
if workdays.size > 2
|
106
|
+
_hours += (workdays.size - 2) * 8
|
107
|
+
end
|
108
|
+
|
109
|
+
if workdays.size > 1
|
110
|
+
_seconds_start = TimeHelper.interval(start_time, off_time)
|
111
|
+
_hours += _seconds_start.div(1800) / 2.0
|
112
|
+
|
113
|
+
_seconds_finish = TimeHelper.interval(on_time, finish_time)
|
114
|
+
_hours += (_seconds_finish).div(1800) / 2.0
|
115
|
+
else
|
116
|
+
_start_at = start_time > on_time ? start_time : on_time
|
117
|
+
_finish_at = finish_time < off_time ? finish_time : off_time
|
118
|
+
_seconds = TimeHelper.interval(_start_at, _finish_at)
|
119
|
+
_hours = _seconds.div(1800) / 2.0
|
120
|
+
end
|
121
|
+
|
122
|
+
self.hours = _hours
|
123
|
+
self.processed = true
|
124
|
+
end
|
125
|
+
|
126
|
+
def compute_hours!
|
127
|
+
self.compute_hours
|
128
|
+
self.save
|
129
|
+
end
|
130
|
+
|
131
|
+
def workdays
|
132
|
+
@workdays ||= (start_time.to_date..finish_time.to_date).reject { |date| financial_month.rest_day?(date) }
|
133
|
+
end
|
134
|
+
|
135
|
+
def on_time
|
136
|
+
return @on_time if @on_time
|
137
|
+
|
138
|
+
if workdays.size > 1
|
139
|
+
@on_time = self.member.on_time.change year: finish_at.year, month: finish_at.month, day: finish_at.day
|
140
|
+
else
|
141
|
+
@on_time = self.member.on_time.change year: start_at.year, month: start_at.month, day: start_at.day
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# todo repeat with attendance
|
146
|
+
def start_time
|
147
|
+
start_at.in_time_zone(member.timezone)
|
148
|
+
end
|
149
|
+
|
150
|
+
# todo repeat with attendance
|
151
|
+
def finish_time
|
152
|
+
finish_at.in_time_zone(member.timezone)
|
153
|
+
end
|
154
|
+
|
155
|
+
def off_time
|
156
|
+
return @off_time if @off_time
|
157
|
+
|
158
|
+
if workdays.size > 1
|
159
|
+
@off_time = self.member.off_time.change year: start_at.year, month: start_at.month, day: start_at.day
|
160
|
+
else
|
161
|
+
@off_time = self.member.off_time.change year: finish_at.year, month: finish_at.month, day: finish_at.day
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def sync_attendance
|
166
|
+
self.workdays.each_with_index do |workday, index|
|
167
|
+
att = self.member.attendances.find_or_initialize_by('attend_on': workday)
|
168
|
+
|
169
|
+
if index == 0 && self.start_at <= self.on_time
|
170
|
+
att.late_absence_id = self.id
|
171
|
+
elsif index == 0 && self.finish_at >= self.off_time
|
172
|
+
att.leave_absence_id = self.id
|
173
|
+
else
|
174
|
+
att.interval_absence_id = self.id
|
175
|
+
end
|
176
|
+
|
177
|
+
att.save
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def financial_months
|
182
|
+
FinancialMonth.default_where('begin_date-lte': self.start_at.to_date, 'end_date-gte': self.finish_at.to_date)
|
183
|
+
end
|
184
|
+
|
185
|
+
def financial_month
|
186
|
+
@financial_month ||= financial_months.take
|
187
|
+
end
|
188
|
+
|
189
|
+
def validate_same_month
|
190
|
+
count = financial_months.count
|
191
|
+
if count > 1
|
192
|
+
self.errors.add :base, 'You can not request crossing financial month, please make more than one request'
|
193
|
+
end
|
194
|
+
if self.finish_at < self.start_at
|
195
|
+
self.errors.add :base, 'Start time can not be later than end'
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
class_methods do
|
200
|
+
def available_rest_days
|
201
|
+
# todo improve performance, use bsearch
|
202
|
+
_rest_days = FinancialMonth.available_rest_days.select { |d| d >= Date.today }
|
203
|
+
Array(_rest_days).map { |d| [d.to_s, d.to_s] }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Attend
|
2
|
+
module Model::AbsenceStat
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
attribute :year, :string
|
7
|
+
attribute :annual_days, :float
|
8
|
+
attribute :annual_add, :float
|
9
|
+
attribute :left_annual_days, :float
|
10
|
+
attribute :vacation_days, :float
|
11
|
+
attribute :details, :string, limit: 1024
|
12
|
+
|
13
|
+
belongs_to :member
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,314 @@
|
|
1
|
+
module Attend
|
2
|
+
module Model::Attendance
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
attribute :late_minutes, :integer
|
7
|
+
attribute :leave_minutes, :integer
|
8
|
+
attribute :overtime_hours, :float
|
9
|
+
attribute :attend_hours, :float
|
10
|
+
attribute :interval_hours, :float
|
11
|
+
attribute :total_hours, :float
|
12
|
+
attribute :attend_on, :date
|
13
|
+
attribute :start_at, :datetime
|
14
|
+
attribute :finish_at, :datetime
|
15
|
+
attribute :interval_start_at, :datetime
|
16
|
+
attribute :interval_finish_at, :datetime
|
17
|
+
attribute :kind, :string
|
18
|
+
attribute :absence_minutes, :integer
|
19
|
+
attribute :absence_redeeming, :boolean
|
20
|
+
attribute :lost_logs, :string, array: true
|
21
|
+
attribute :workday, :boolean, default: true
|
22
|
+
attribute :processed, :boolean, default: false
|
23
|
+
|
24
|
+
belongs_to :member
|
25
|
+
belongs_to :interval_absence, class_name: 'Absence', optional: true
|
26
|
+
belongs_to :late_absence, class_name: 'Absence', optional: true
|
27
|
+
belongs_to :leave_absence, class_name: 'Absence', optional: true
|
28
|
+
has_many :attendance_logs, dependent: :nullify
|
29
|
+
|
30
|
+
enum kind: {
|
31
|
+
belated: 'belated',
|
32
|
+
asked_leave: 'asked_leave',
|
33
|
+
no_meal: 'no_meal',
|
34
|
+
allowance: 'allowance'
|
35
|
+
}
|
36
|
+
|
37
|
+
validates :attend_on, uniqueness: { scope: :member_id }
|
38
|
+
before_save :sync_workday, if: -> { attend_on_changed? }
|
39
|
+
end
|
40
|
+
|
41
|
+
def compute_time_type(record_at)
|
42
|
+
arr = [start_at, interval_start_at, interval_finish_at, finish_at, record_at].compact.uniq
|
43
|
+
arr.sort!
|
44
|
+
|
45
|
+
if arr.length < 4
|
46
|
+
arr.mjust! 4, nil
|
47
|
+
end
|
48
|
+
|
49
|
+
if arr.length == 5
|
50
|
+
if arr[1] < self.on_time
|
51
|
+
arr.delete_at(1)
|
52
|
+
elsif arr[-2] > self.off_time
|
53
|
+
arr.delete_at(-2)
|
54
|
+
else
|
55
|
+
arr.delete_at(2)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
logger.debug "-----> results: #{arr.map(&->(o){ o.to_s }).inspect}"
|
60
|
+
|
61
|
+
result = arr.find_index { |x| x == record_at }
|
62
|
+
|
63
|
+
self.start_at = arr[0]
|
64
|
+
self.interval_start_at = arr[1] if arr[1] && arr[1] > self.on_time
|
65
|
+
self.interval_finish_at = arr[-2] if arr[-2] && arr[-2] < self.off_time
|
66
|
+
self.finish_at = arr[-1]
|
67
|
+
|
68
|
+
if result
|
69
|
+
['start_at', 'interval_start_at', 'interval_finish_at', 'finish_at'].fetch(result)
|
70
|
+
else
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def compute_summary
|
76
|
+
if self.finish_at && self.start_at
|
77
|
+
self.total_hours = (self.finish_at - self.start_at).div(1800) / 2.0
|
78
|
+
else
|
79
|
+
return
|
80
|
+
end
|
81
|
+
|
82
|
+
late_summary
|
83
|
+
leave_summary
|
84
|
+
self.absence_minutes = self.late_minutes.to_i + self.leave_minutes.to_i
|
85
|
+
|
86
|
+
interval_summary
|
87
|
+
total_leave = self.interval_hours.to_f + (self.absence_minutes / 30.0).ceil / 2.0
|
88
|
+
self.attend_hours = 8 - total_leave
|
89
|
+
|
90
|
+
if self.attend_hours < 8 && self.attend_hours > 4
|
91
|
+
self.kind = 'belated'
|
92
|
+
elsif self.attend_hours < 4
|
93
|
+
self.kind = 'no_meal'
|
94
|
+
else
|
95
|
+
self.kind = 'allowance'
|
96
|
+
end
|
97
|
+
|
98
|
+
self.overtime_hours = morning_over + evening_over
|
99
|
+
self.processed = true
|
100
|
+
self.changes
|
101
|
+
end
|
102
|
+
|
103
|
+
def compute_summary!
|
104
|
+
self.compute_summary
|
105
|
+
self.save
|
106
|
+
end
|
107
|
+
|
108
|
+
def start_time
|
109
|
+
@start_time ||= start_at.in_time_zone(member.timezone)
|
110
|
+
end
|
111
|
+
|
112
|
+
def finish_time
|
113
|
+
@finish_time ||= finish_at.in_time_zone(member.timezone)
|
114
|
+
end
|
115
|
+
|
116
|
+
def interval_start_time
|
117
|
+
@interval_start_time ||= interval_start_at.in_time_zone(member.timezone)
|
118
|
+
end
|
119
|
+
|
120
|
+
def interval_finish_time
|
121
|
+
@interval_finish_time ||= interval_finish_at.in_time_zone(member.timezone)
|
122
|
+
end
|
123
|
+
|
124
|
+
def off_time
|
125
|
+
return @off_time if @off_time
|
126
|
+
if night_shift
|
127
|
+
@off_time = self.attend_on.to_datetime.in_time_zone(member.timezone).change hour: 21
|
128
|
+
else
|
129
|
+
@off_time = self.member.off_time(self.attend_on)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def on_time
|
134
|
+
return @on_time if @on_time
|
135
|
+
if night_shift
|
136
|
+
@on_time = self.attend_on.to_datetime.in_time_zone(member.timezone).change hour: 13
|
137
|
+
else
|
138
|
+
@on_time = self.member.on_time(self.attend_on)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def night_shift
|
143
|
+
return false unless member.department&.night_shift
|
144
|
+
|
145
|
+
self.start_time.hour <= 13 && self.start_time.hour >= 11 && self.finish_time.hour >= 21
|
146
|
+
end
|
147
|
+
|
148
|
+
def lunch_time
|
149
|
+
return @lunch_time if @lunch_time
|
150
|
+
start = self.member.lunch_time[0].change year: attend_on.year, month: attend_on.month, day: attend_on.day
|
151
|
+
finish = self.member.lunch_time[1].change year: attend_on.year, month: attend_on.month, day: attend_on.day
|
152
|
+
@lunch_time = [start, finish]
|
153
|
+
end
|
154
|
+
|
155
|
+
def interval_time
|
156
|
+
return @interval_time if @interval_time
|
157
|
+
return [] unless interval_start_time && interval_finish_time
|
158
|
+
|
159
|
+
if interval_start_time > lunch_time[0] && interval_start_time < lunch_time[1]
|
160
|
+
start = lunch_time[1]
|
161
|
+
else
|
162
|
+
start = interval_start_time
|
163
|
+
end
|
164
|
+
|
165
|
+
if interval_finish_time > lunch_time[0] && interval_finish_time < lunch_time[1] && interval_finish_time > start
|
166
|
+
finish = lunch_time[0]
|
167
|
+
elsif interval_finish_time > lunch_time[0] && interval_finish_time < lunch_time[1] && interval_finish_time <= start
|
168
|
+
finish = lunch_time[1]
|
169
|
+
else
|
170
|
+
finish = interval_finish_time
|
171
|
+
end
|
172
|
+
|
173
|
+
@interval_time = [start, finish]
|
174
|
+
end
|
175
|
+
|
176
|
+
def morning_time
|
177
|
+
@morning_time ||= on_time - 30.minutes
|
178
|
+
end
|
179
|
+
|
180
|
+
def evening_time
|
181
|
+
@evening_time ||= off_time + 90.minutes
|
182
|
+
end
|
183
|
+
|
184
|
+
def evening_over
|
185
|
+
return 0 if self.finish_at.blank? || leave_early?
|
186
|
+
if self.finish_time < evening_time
|
187
|
+
result = self.finish_time.to_i - off_time.to_i
|
188
|
+
else
|
189
|
+
result = evening_time.to_i - off_time.to_i
|
190
|
+
end
|
191
|
+
|
192
|
+
result.div(1800) / 2.0
|
193
|
+
end
|
194
|
+
|
195
|
+
def morning_over
|
196
|
+
return 0 if self.start_at.blank? || late?
|
197
|
+
if self.start_time < morning_time
|
198
|
+
result = on_time.to_i - morning_time.to_i
|
199
|
+
else
|
200
|
+
result = on_time.to_i - self.start_time.to_i
|
201
|
+
end
|
202
|
+
|
203
|
+
result.div(1800) / 2.0
|
204
|
+
end
|
205
|
+
|
206
|
+
def late?
|
207
|
+
self.start_at && self.start_time > self.on_time
|
208
|
+
end
|
209
|
+
|
210
|
+
def late_summary
|
211
|
+
return unless self.start_time
|
212
|
+
unless late?
|
213
|
+
self.late_absence_id = nil
|
214
|
+
return if self.late_minutes.blank?
|
215
|
+
end
|
216
|
+
|
217
|
+
if self.start_time > self.on_time
|
218
|
+
self.late_minutes = ((self.start_time.to_i - on_time.to_i) / 60.0).floor
|
219
|
+
_late = Absence.default_where(member_id: self.member_id, 'start_at-lte': start_time, 'finish_at-gte': start_time, 'finish_at-lte': off_time).take
|
220
|
+
if _late
|
221
|
+
self.late_absence_id = _late.id
|
222
|
+
else
|
223
|
+
self.late_absence_id = nil
|
224
|
+
end
|
225
|
+
else
|
226
|
+
self.late_minutes = nil
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def leave_early?
|
231
|
+
self.finish_at && self.finish_time < self.off_time
|
232
|
+
end
|
233
|
+
|
234
|
+
def leave_summary
|
235
|
+
return unless self.finish_at
|
236
|
+
unless leave_early?
|
237
|
+
self.leave_absence_id = nil
|
238
|
+
return if self.leave_minutes.blank?
|
239
|
+
end
|
240
|
+
|
241
|
+
if self.finish_time < self.off_time
|
242
|
+
self.leave_minutes = ((off_time.to_i - self.finish_time.to_i) / 60.0).floor
|
243
|
+
_leave = Absence.default_where(member_id: self.member_id, 'start_at-gte': finish_time, 'finish_at-lte': off_time).take
|
244
|
+
if _leave
|
245
|
+
self.leave_absence_id = _leave.id
|
246
|
+
else
|
247
|
+
self.leave_absence_id = nil
|
248
|
+
end
|
249
|
+
else
|
250
|
+
self.leave_minutes = nil
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def interval_leave?
|
255
|
+
return false unless interval_start_at && interval_finish_at
|
256
|
+
!(self.interval_start_time > self.lunch_time[0] && self.interval_finish_time < self.lunch_time[1])
|
257
|
+
end
|
258
|
+
|
259
|
+
def interval_summary
|
260
|
+
return unless interval_leave?
|
261
|
+
|
262
|
+
_interval = Absence.default_where(member_id: self.member_id, 'start_at-lte': interval_start_time, 'finish_at-gte': interval_finish_time).take
|
263
|
+
if _interval
|
264
|
+
self.interval_absence_id = _interval.id
|
265
|
+
else
|
266
|
+
self.interval_absence_id = nil
|
267
|
+
end
|
268
|
+
|
269
|
+
_interval_seconds = TimeHelper.interval(self.interval_time[0], self.interval_time[1], interval_start: '12:30')
|
270
|
+
interval = (_interval_seconds / 1800.0).ceil / 2.0
|
271
|
+
self.interval_hours = interval
|
272
|
+
end
|
273
|
+
|
274
|
+
def financial_month
|
275
|
+
@financial_month ||= FinancialMonth.current_month(self.attend_on)
|
276
|
+
end
|
277
|
+
|
278
|
+
def sync_workday
|
279
|
+
if self.rest_day?
|
280
|
+
self.workday = false
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def work_day?
|
285
|
+
financial_month.work_day?(self.attend_on)
|
286
|
+
end
|
287
|
+
|
288
|
+
def rest_day?
|
289
|
+
financial_month.rest_day?(self.attend_on)
|
290
|
+
end
|
291
|
+
|
292
|
+
class_methods do
|
293
|
+
def compute_summary!
|
294
|
+
self.where(processed: false).each do |at|
|
295
|
+
at.compute_summary!
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def notify_completion
|
300
|
+
normal_ids = self.distinct(:member_id).where(finish_time: nil).where.not(start_at: nil).pluck(:member_id)
|
301
|
+
normal_ids.each do |member_id|
|
302
|
+
AttendanceMailer.normal_completion(member_id).deliver_later
|
303
|
+
end
|
304
|
+
|
305
|
+
interval_ids = self.distinct(:member_id).where(interval_finish_at: nil).where.not(interval_start_at: nil).pluck(:member_id)
|
306
|
+
interval_ids.each do |member_id|
|
307
|
+
AttendanceMailer.interval_completion(member_id).deliver_later
|
308
|
+
end
|
309
|
+
[normal_ids, interval_ids]
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|
314
|
+
end
|