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,165 @@
|
|
1
|
+
module Attend
|
2
|
+
module Model::AttendanceLog
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
attribute :source, :string, default: 'machine'
|
7
|
+
attribute :state, :string, default: 'init'
|
8
|
+
attribute :name, :string
|
9
|
+
attribute :record_at, :datetime
|
10
|
+
attribute :processed, :boolean, default: false
|
11
|
+
attribute :kind, :string
|
12
|
+
attribute :note, :string
|
13
|
+
attribute :record_at_str, :string
|
14
|
+
attribute :number, :string
|
15
|
+
|
16
|
+
belongs_to :unsure_member, class_name: 'Member', foreign_key: 'number', primary_key: 'attendance_number', optional: true
|
17
|
+
belongs_to :member, optional: true
|
18
|
+
belongs_to :attendance, optional: true
|
19
|
+
|
20
|
+
validates :record_at, presence: true, unless: -> { self.source == 'machine' }
|
21
|
+
validates :record_at_str, presence: true, if: -> { self.source == 'machine' }
|
22
|
+
|
23
|
+
scope :pending, -> { where(state: 'init', processed: false) }
|
24
|
+
|
25
|
+
enum state: {
|
26
|
+
init: 'init',
|
27
|
+
valid: 'valid',
|
28
|
+
invalid: 'invalid'
|
29
|
+
}, _prefix: true
|
30
|
+
|
31
|
+
enum kind: {
|
32
|
+
start_at: 'start_at',
|
33
|
+
finish_at: 'finish_at',
|
34
|
+
interval_start_at: 'interval_start_at',
|
35
|
+
interval_finish_at: 'interval_finish_at'
|
36
|
+
}
|
37
|
+
|
38
|
+
enum source: {
|
39
|
+
machine: 'machine',
|
40
|
+
myself: 'myself',
|
41
|
+
admin: 'admin'
|
42
|
+
}
|
43
|
+
|
44
|
+
delegate :name, to: :member, prefix: true
|
45
|
+
acts_as_notify :default,
|
46
|
+
only: [:record_at, :note],
|
47
|
+
methods: [:member_name, :state_i18n]
|
48
|
+
acts_as_notify :request,
|
49
|
+
only: [:record_at, :note],
|
50
|
+
methods: [:member_name]
|
51
|
+
|
52
|
+
after_initialize if: :new_record? do
|
53
|
+
self.state ||= 'init'
|
54
|
+
end
|
55
|
+
before_save :sync_member_id, if: -> { number_changed? }
|
56
|
+
end
|
57
|
+
|
58
|
+
def sync_member_id
|
59
|
+
self.member_id = self.unsure_member&.id
|
60
|
+
if self.member
|
61
|
+
self.record_at = self.record_at_str.in_time_zone(member.timezone)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def do_trigger(params = {})
|
66
|
+
self.trigger_to(params.slice(:state))
|
67
|
+
|
68
|
+
self.class.transaction do
|
69
|
+
self.save!
|
70
|
+
to_notification(
|
71
|
+
member: member,
|
72
|
+
link: url_helpers.oa_attendance_logs_url(id: self.id),
|
73
|
+
verbose: true,
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def send_notification
|
79
|
+
to_notification(
|
80
|
+
member: member.parent,
|
81
|
+
cc_emails: [
|
82
|
+
self.member.office&.absence_email
|
83
|
+
],
|
84
|
+
link: url_helpers.my_admin_attendance_logs_url(id: self.id),
|
85
|
+
code: :request,
|
86
|
+
verbose: true,
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
def editable?
|
91
|
+
self.state_init? && self.myself?
|
92
|
+
end
|
93
|
+
|
94
|
+
def record_at_begin
|
95
|
+
record_at.at_beginning_of_day
|
96
|
+
end
|
97
|
+
|
98
|
+
def record_at_end
|
99
|
+
record_at.at_end_of_day
|
100
|
+
end
|
101
|
+
|
102
|
+
def compute
|
103
|
+
analyze
|
104
|
+
self.attendance.compute_summary!
|
105
|
+
end
|
106
|
+
|
107
|
+
def analyze
|
108
|
+
return unless self.member
|
109
|
+
att = self.member.attendances.find_or_initialize_by('attend_on': self.record_at)
|
110
|
+
_kind = att.compute_time_type self.record_at
|
111
|
+
|
112
|
+
self.kind = _kind
|
113
|
+
self.processed = true
|
114
|
+
self.state = 'valid'
|
115
|
+
|
116
|
+
self.attendance = att
|
117
|
+
|
118
|
+
self.class.transaction do
|
119
|
+
self.redress_same_logs
|
120
|
+
att.save!
|
121
|
+
self.save!
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def redress_same_logs
|
126
|
+
return AttendanceLog.none unless self.attendance
|
127
|
+
logs = AttendanceLog.where.not(id: self.id).where(member_id: self.member_id, attendance_id: self.attendance_id)
|
128
|
+
times = [attendance.start_at, attendance.interval_start_at, attendance.interval_finish_at, attendance.finish_at]
|
129
|
+
kinds = ['start_at', 'interval_start_at', 'interval_finish_at', 'finish_at']
|
130
|
+
|
131
|
+
logs.each do |log|
|
132
|
+
index = times.find_index { |x| x == log.record_at }
|
133
|
+
if index
|
134
|
+
_kind = kinds[index]
|
135
|
+
else
|
136
|
+
_kind = nil
|
137
|
+
end
|
138
|
+
log.kind = _kind
|
139
|
+
log.save!
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class_methods do
|
144
|
+
def analyze
|
145
|
+
AttendanceLog.where(processed: false).each do |al|
|
146
|
+
al.analyze
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def prepare
|
151
|
+
Attendance.where(member_id: nil).each do |i|
|
152
|
+
i.sync_member_id
|
153
|
+
i.save
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.analyze_number
|
158
|
+
Member.where(enabled: true, attendance_number: [nil, '']).each do |member|
|
159
|
+
num = AttendanceLog.find_by(source: 'machine', name: member.profile&.real_name)&.number
|
160
|
+
member.update(attendance_number: num)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Attend
|
2
|
+
module Model::AttendanceSetting
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
attribute :effect_on, :date
|
7
|
+
attribute :expire_on, :date
|
8
|
+
attribute :on_time, :string, default: '08:30'
|
9
|
+
attribute :off_time, :string
|
10
|
+
attribute :note, :string
|
11
|
+
|
12
|
+
belongs_to :member
|
13
|
+
|
14
|
+
enum state: {
|
15
|
+
init: 'init',
|
16
|
+
approved: 'approved',
|
17
|
+
denied: 'denied'
|
18
|
+
}, _default: 'init'
|
19
|
+
|
20
|
+
after_initialize do
|
21
|
+
self.compute_off_time
|
22
|
+
end
|
23
|
+
before_save :compute_off_time, if: -> { on_time_changed? }
|
24
|
+
|
25
|
+
acts_as_notify
|
26
|
+
end
|
27
|
+
|
28
|
+
def do_trigger(params = {})
|
29
|
+
self.trigger_to(params.slice(:state))
|
30
|
+
|
31
|
+
self.class.transaction do
|
32
|
+
self.save!
|
33
|
+
member.save!
|
34
|
+
to_notification(
|
35
|
+
member: member,
|
36
|
+
title: "Your Attendance Setting has been #{self.state_i18n}",
|
37
|
+
body: "Your Attendance Setting has been #{self.state_i18n}",
|
38
|
+
link: url_helpers.oa_attendance_settings_url
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def compute_off_time
|
44
|
+
if ['08', '8'].include? on_time.to_s.split(':').first
|
45
|
+
self.off_time = '17:30'
|
46
|
+
else
|
47
|
+
self.off_time = '18:00'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class_methods do
|
52
|
+
def time_options
|
53
|
+
[
|
54
|
+
['09:00', '09:00'],
|
55
|
+
['08:30', '08:30']
|
56
|
+
]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Attend
|
2
|
+
module Model::AttendanceStat
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
attribute :costed_absence, :json, default: {}
|
7
|
+
attribute :redeeming_absence, :json, default: {}
|
8
|
+
attribute :free_absence, :json, default: {}
|
9
|
+
attribute :allowance_days, :integer
|
10
|
+
attribute :late_days, :integer
|
11
|
+
attribute :absence_redeeming_hours, :float
|
12
|
+
attribute :costed_absence, :string, limit: 1024
|
13
|
+
attribute :cost_absence_hours, :float
|
14
|
+
attribute :free_absence, :string, limit: 1024
|
15
|
+
attribute :redeeming_absence, :string, limit: 1024
|
16
|
+
attribute :holiday_redeeming_hours, :float
|
17
|
+
attribute :processed, :boolean, default: false
|
18
|
+
|
19
|
+
belongs_to :member
|
20
|
+
belongs_to :financial_month
|
21
|
+
has_many :absences,
|
22
|
+
->(o){ default_where('start_at-gte': o.financial_month.begin_date.beginning_of_day, 'finish_at-lte': o.financial_month.end_date.end_of_day) }, #todo timezone consider
|
23
|
+
foreign_key: :member_id,
|
24
|
+
primary_key: :member_id
|
25
|
+
has_many :attendances,
|
26
|
+
->(o){ default_where('attend_on-gte': o.financial_month.begin_date, 'attend_on-lte': o.financial_month.end_date, workday: true) },
|
27
|
+
foreign_key: :member_id,
|
28
|
+
primary_key: :member_id
|
29
|
+
end
|
30
|
+
|
31
|
+
def compute_summary
|
32
|
+
self.late_days = self.attendances.default_where({ 'absence_minutes-gt': 0, late_absence_id: nil, leave_absence_id: nil }, allow: [nil]).count
|
33
|
+
self.allowance_days = self.attendances.default_where('kind-not': 'no_meal').count
|
34
|
+
|
35
|
+
redeeming_hours = self.attendances.sum(:overtime_hours) <= 10 ? self.attendances.sum(:overtime_hours) : 10
|
36
|
+
leave_hours = self.absences.where(type: 'PersonLeave').sum(:hours) >= 8 ? 8 : self.absences.where(type: 'PersonLeave').sum(:hours)
|
37
|
+
|
38
|
+
self.absence_redeeming_hours = (redeeming_hours - leave_hours) > 0 ? redeeming_hours - leave_hours : 0
|
39
|
+
|
40
|
+
if redeeming_hours > 2
|
41
|
+
redeeming_hours -= 2
|
42
|
+
self.holiday_redeeming_hours = redeeming_hours
|
43
|
+
else
|
44
|
+
self.absence_redeeming_hours = redeeming_hours
|
45
|
+
end
|
46
|
+
|
47
|
+
self.absence_summary
|
48
|
+
self.processed = true
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def absence_summary
|
53
|
+
absence_hash = self.attendances.default_where({ 'absence_minutes-gt': 0, 'absence_minutes-lte': 15, late_absence_id: nil, leave_absence_id: nil }, allow: [nil]).pluck(:absence_minutes, :attend_on).sort.map(&:reverse!).to_h
|
54
|
+
|
55
|
+
time = 0
|
56
|
+
absence_hash.each do |k, v|
|
57
|
+
time += v
|
58
|
+
break if time > 120
|
59
|
+
self.free_absence[k] = absence_hash.delete(k)
|
60
|
+
end
|
61
|
+
absence_hash.merge! self.attendances.default_where({ 'absence_minutes-gt': 15, late_absence_id: nil, leave_absence_id: nil }, allow: [nil]).pluck(:absence_minutes, :attend_on).sort.map(&:reverse!).to_h
|
62
|
+
|
63
|
+
remain = absence_redeeming_hours
|
64
|
+
absence_hash.each do |k, v|
|
65
|
+
o = (v / 30.0).ceil / 2.0
|
66
|
+
remain -= o
|
67
|
+
break if remain < 0
|
68
|
+
self.redeeming_absence[k] = absence_hash.delete(k)
|
69
|
+
end
|
70
|
+
|
71
|
+
self.redeeming_absence = {} if absence_hash.blank?
|
72
|
+
self.costed_absence = absence_hash
|
73
|
+
self.cost_absence_hours = (self.costed_absence.values.sum / 30.0).ceil / 2.0
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
def overtime_hash
|
78
|
+
return @overtime_hash if @overtime_hash
|
79
|
+
@overtime_hash = self.attendances.default_where('overtime_hours-gt': 0).pluck(:overtime_hours, :attend_on).sort.map(&:reverse!).to_h
|
80
|
+
end
|
81
|
+
|
82
|
+
def compute_summary!
|
83
|
+
self.compute_summary
|
84
|
+
self.save
|
85
|
+
end
|
86
|
+
|
87
|
+
class_methods do
|
88
|
+
def init
|
89
|
+
_financial_month = FinancialMonth.current_month
|
90
|
+
|
91
|
+
Attendance.distinct(:member_id).default_where('attend_on-gte': _financial_month.begin_date, 'attend_on-lte': _financial_month.end_date).pluck(:member_id).each do |member_id|
|
92
|
+
AttendanceStat.find_or_create_by(member_id: member_id, financial_month_id: _financial_month.id)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def compute_summary!
|
97
|
+
self.where(processed: false).each do |at|
|
98
|
+
at.compute_summary!
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Attend
|
2
|
+
module Model::ExtraDay
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
attribute :the_day, :date
|
7
|
+
attribute :name, :string
|
8
|
+
attribute :kind, :string, comment: 'holiday, workday'
|
9
|
+
attribute :scope, :string
|
10
|
+
|
11
|
+
belongs_to :organ, optional: true
|
12
|
+
|
13
|
+
enum kind: {
|
14
|
+
holiday: 'holiday',
|
15
|
+
workday: 'workday'
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Attend
|
2
|
+
module Model::FinancialMonth
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
thread_mattr_accessor :current_country, instance_accessor: true
|
7
|
+
|
8
|
+
attribute :begin_date, :date
|
9
|
+
attribute :end_date, :date
|
10
|
+
attribute :working_days, :string
|
11
|
+
attribute :color, :string, default: '#8fdf82'
|
12
|
+
|
13
|
+
belongs_to :organ, optional: true
|
14
|
+
|
15
|
+
has_many :attendance_settings, dependent: :nullify
|
16
|
+
|
17
|
+
validate :validate_date_range, if: -> { begin_date_changed? || end_date_changed? }
|
18
|
+
end
|
19
|
+
|
20
|
+
def workdays
|
21
|
+
return @workdays if @workdays
|
22
|
+
origin = (begin_date..end_date).reject { |day| day.on_weekend? }
|
23
|
+
@workdays = origin + extra_workdays.pluck(:the_day) - holidays.pluck(:the_day)
|
24
|
+
end
|
25
|
+
|
26
|
+
def work_day?(date = Date.today)
|
27
|
+
workdays.include?(date)
|
28
|
+
end
|
29
|
+
|
30
|
+
def rest_days
|
31
|
+
return @rest_days if @rest_days
|
32
|
+
origin = (begin_date..end_date).select { |day| day.on_weekend? }
|
33
|
+
@rest_days = (origin - extra_workdays.pluck(:the_day) + holidays.pluck(:the_day)).uniq
|
34
|
+
end
|
35
|
+
|
36
|
+
def rest_day?(date = Date.today)
|
37
|
+
rest_days.include?(date)
|
38
|
+
end
|
39
|
+
|
40
|
+
def extra_workdays
|
41
|
+
ExtraDay.workday.default_where(country: FinancialMonth.current_country, 'the_day-gte': begin_date, 'the_day-lte': end_date)
|
42
|
+
end
|
43
|
+
|
44
|
+
def holidays
|
45
|
+
ExtraDay.holiday.default_where(country: FinancialMonth.current_country, 'the_day-gte': begin_date, 'the_day-lte': end_date)
|
46
|
+
end
|
47
|
+
|
48
|
+
def extras
|
49
|
+
(extra_workdays + holidays).map do |day|
|
50
|
+
{
|
51
|
+
title: day.name,
|
52
|
+
start: day.the_day
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def month
|
58
|
+
self.end_date.month
|
59
|
+
end
|
60
|
+
|
61
|
+
def date_range
|
62
|
+
[begin_date, end_date].join(' ~ ')
|
63
|
+
end
|
64
|
+
|
65
|
+
def events
|
66
|
+
workdays.map do |workday|
|
67
|
+
{
|
68
|
+
title: 'Start',
|
69
|
+
start: workday,
|
70
|
+
color: self.color
|
71
|
+
}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def reset_attendance_settings
|
76
|
+
attendance_settings.each do |as|
|
77
|
+
as.sync_time
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def validate_date_range
|
82
|
+
if self.end_date <= self.begin_date
|
83
|
+
self.errors.add :base, 'End date must later than begin date!'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def next_month
|
88
|
+
FinancialMonth.default_where('begin_date-gt': self.end_date).order(begin_date: :asc).first
|
89
|
+
end
|
90
|
+
|
91
|
+
class_methods do
|
92
|
+
def current_all
|
93
|
+
FinancialMonth.default_where('end_date-gte': Date.today)
|
94
|
+
end
|
95
|
+
|
96
|
+
def next_all
|
97
|
+
FinancialMonth.default_where('begin_date-gt': Date.today)
|
98
|
+
end
|
99
|
+
|
100
|
+
def current_month(day = Date.today)
|
101
|
+
FinancialMonth.default_where('begin_date-lte': day, 'end_date-gte': day).take
|
102
|
+
end
|
103
|
+
|
104
|
+
def next_month(day = Date.today)
|
105
|
+
self.current_month(day).next_month
|
106
|
+
end
|
107
|
+
|
108
|
+
def available_rest_days
|
109
|
+
Array(self.current_month&.rest_days) + Array(self.next_month&.rest_days)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|