rails_attend 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (214) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +22 -0
  4. data/Rakefile +32 -0
  5. data/app/controllers/attend/admin/absence_stats_controller.rb +60 -0
  6. data/app/controllers/attend/admin/absences_controller.rb +77 -0
  7. data/app/controllers/attend/admin/attendance_logs_controller.rb +103 -0
  8. data/app/controllers/attend/admin/attendance_settings_controller.rb +86 -0
  9. data/app/controllers/attend/admin/attendance_stats_controller.rb +58 -0
  10. data/app/controllers/attend/admin/attendances_controller.rb +57 -0
  11. data/app/controllers/attend/admin/base_controller.rb +4 -0
  12. data/app/controllers/attend/admin/extra_days_controller.rb +63 -0
  13. data/app/controllers/attend/admin/financial_months_controller.rb +69 -0
  14. data/app/controllers/attend/admin/overtimes_controller.rb +59 -0
  15. data/app/controllers/attend/me/absences_controller.rb +71 -0
  16. data/app/controllers/attend/me/attendance_logs_controller.rb +74 -0
  17. data/app/controllers/attend/me/attendance_settings_controller.rb +52 -0
  18. data/app/controllers/attend/me/attendance_stats_controller.rb +27 -0
  19. data/app/controllers/attend/me/attendances_controller.rb +43 -0
  20. data/app/controllers/attend/me/base_controller.rb +4 -0
  21. data/app/controllers/attend/me/calendars_controller.rb +33 -0
  22. data/app/controllers/attend/me/overtimes_controller.rb +65 -0
  23. data/app/javascript/packs/controllers/attend/admin/attendance_logs/index.js +1 -0
  24. data/app/javascript/packs/controllers/attend/admin/attendance_logs/my.js +14 -0
  25. data/app/javascript/packs/controllers/attend/admin/attendance_settings/create.js +1 -0
  26. data/app/javascript/packs/controllers/attend/admin/attendance_settings/index.js +1 -0
  27. data/app/javascript/packs/controllers/attend/admin/attendance_settings/my.js +13 -0
  28. data/app/javascript/packs/controllers/attend/admin/attendance_settings/new.js +11 -0
  29. data/app/javascript/packs/controllers/attend/me/absences/new.js +16 -0
  30. data/app/javascript/packs/controllers/attend/me/absences/update.js +3 -0
  31. data/app/javascript/packs/controllers/attend/me/attendance_settings/create.js +1 -0
  32. data/app/javascript/packs/controllers/attend/me/attendance_settings/edit.js +1 -0
  33. data/app/javascript/packs/controllers/attend/me/attendance_settings/new.js +2 -0
  34. data/app/javascript/packs/controllers/attend/me/calendars/show.js +48 -0
  35. data/app/jobs/attendance_job.rb +14 -0
  36. data/app/models/absence/attend/annual_leave.rb +5 -0
  37. data/app/models/absence/attend/bereavement_leave.rb +5 -0
  38. data/app/models/absence/attend/business_trip.rb +5 -0
  39. data/app/models/absence/attend/children_day_leave.rb +5 -0
  40. data/app/models/absence/attend/compensatory_leave.rb +5 -0
  41. data/app/models/absence/attend/marriage_leave.rb +4 -0
  42. data/app/models/absence/attend/maternity_leave.rb +5 -0
  43. data/app/models/absence/attend/parental_leave.rb +5 -0
  44. data/app/models/absence/attend/personal_leave.rb +5 -0
  45. data/app/models/absence/attend/prenatal_leave.rb +4 -0
  46. data/app/models/absence/attend/sick_leave.rb +5 -0
  47. data/app/models/absence/attend/vacation_leave.rb +5 -0
  48. data/app/models/absence/attend/welfare_leave.rb +5 -0
  49. data/app/models/attend/absence.rb +5 -0
  50. data/app/models/attend/absence_stat.rb +5 -0
  51. data/app/models/attend/application_record.rb +5 -0
  52. data/app/models/attend/attendance.rb +5 -0
  53. data/app/models/attend/attendance_log.rb +5 -0
  54. data/app/models/attend/attendance_setting.rb +5 -0
  55. data/app/models/attend/attendance_stat.rb +5 -0
  56. data/app/models/attend/ext/member.rb +33 -0
  57. data/app/models/attend/extra_day.rb +5 -0
  58. data/app/models/attend/financial_month.rb +5 -0
  59. data/app/models/attend/model/absence/business_trip.rb +10 -0
  60. data/app/models/attend/model/absence/compensatory_leave.rb +5 -0
  61. data/app/models/attend/model/absence/personal_leave.rb +5 -0
  62. data/app/models/attend/model/absence/welfare_leave/annual_leave.rb +16 -0
  63. data/app/models/attend/model/absence/welfare_leave/vacation_leave.rb +16 -0
  64. data/app/models/attend/model/absence/welfare_leave.rb +5 -0
  65. data/app/models/attend/model/absence.rb +208 -0
  66. data/app/models/attend/model/absence_stat.rb +17 -0
  67. data/app/models/attend/model/attendance.rb +314 -0
  68. data/app/models/attend/model/attendance_log.rb +165 -0
  69. data/app/models/attend/model/attendance_setting.rb +61 -0
  70. data/app/models/attend/model/attendance_stat.rb +104 -0
  71. data/app/models/attend/model/extra_day.rb +21 -0
  72. data/app/models/attend/model/financial_month.rb +114 -0
  73. data/app/models/attend/model/overtime.rb +93 -0
  74. data/app/models/attend/overtime.rb +5 -0
  75. data/app/models/attend.rb +11 -0
  76. data/app/models/concerns/absent.rb +103 -0
  77. data/app/views/admin/_attend_nav.html.erb +15 -0
  78. data/app/views/attend/admin/absence_stats/_filter_form.html.erb +9 -0
  79. data/app/views/attend/admin/absence_stats/_form.html.erb +12 -0
  80. data/app/views/attend/admin/absence_stats/_index_tbody.html.erb +3 -0
  81. data/app/views/attend/admin/absence_stats/_index_thead.html.erb +4 -0
  82. data/app/views/attend/admin/absence_stats/index.html.erb +5 -0
  83. data/app/views/attend/admin/absences/_absence.html.erb +38 -0
  84. data/app/views/attend/admin/absences/_edit_form.html.erb +3 -0
  85. data/app/views/attend/admin/absences/_filter_form.html.erb +8 -0
  86. data/app/views/attend/admin/absences/_form.html.erb +5 -0
  87. data/app/views/attend/admin/absences/_index_tbody.html.erb +18 -0
  88. data/app/views/attend/admin/absences/_index_thead.html.erb +10 -0
  89. data/app/views/attend/admin/absences/_new_form.html.erb +3 -0
  90. data/app/views/attend/admin/absences/_show.html.erb +11 -0
  91. data/app/views/attend/admin/absences/index.html.erb +5 -0
  92. data/app/views/attend/admin/absences/my.html.erb +5 -0
  93. data/app/views/attend/admin/attendance_logs/_edit_form.html.erb +5 -0
  94. data/app/views/attend/admin/attendance_logs/_filter_form.html.erb +11 -0
  95. data/app/views/attend/admin/attendance_logs/_form.html.erb +6 -0
  96. data/app/views/attend/admin/attendance_logs/_index_thead.html.erb +19 -0
  97. data/app/views/attend/admin/attendance_logs/_list.html.erb +61 -0
  98. data/app/views/attend/admin/attendance_logs/_new_form.html.erb +3 -0
  99. data/app/views/attend/admin/attendance_logs/index.html.erb +5 -0
  100. data/app/views/attend/admin/attendance_logs/my.html.erb +5 -0
  101. data/app/views/attend/admin/attendance_settings/_button.html.erb +4 -0
  102. data/app/views/attend/admin/attendance_settings/_filter_form.html.erb +11 -0
  103. data/app/views/attend/admin/attendance_settings/_form.html.erb +7 -0
  104. data/app/views/attend/admin/attendance_settings/_index_tbody.html.erb +15 -0
  105. data/app/views/attend/admin/attendance_settings/_index_thead.html.erb +10 -0
  106. data/app/views/attend/admin/attendance_settings/_new_form.html.erb +8 -0
  107. data/app/views/attend/admin/attendance_settings/_show_table.html.erb +12 -0
  108. data/app/views/attend/admin/attendance_settings/index.html.erb +5 -0
  109. data/app/views/attend/admin/attendance_settings/my.html.erb +5 -0
  110. data/app/views/attend/admin/attendance_stats/_filter_form.html.erb +10 -0
  111. data/app/views/attend/admin/attendance_stats/_form.html.erb +6 -0
  112. data/app/views/attend/admin/attendance_stats/_index_thead.html.erb +10 -0
  113. data/app/views/attend/admin/attendance_stats/index.html.erb +44 -0
  114. data/app/views/attend/admin/attendances/_button.html.erb +2 -0
  115. data/app/views/attend/admin/attendances/_filter_form.html.erb +10 -0
  116. data/app/views/attend/admin/attendances/_form.html.erb +23 -0
  117. data/app/views/attend/admin/attendances/_index_tbody.html.erb +61 -0
  118. data/app/views/attend/admin/attendances/_index_thead.html.erb +22 -0
  119. data/app/views/attend/admin/attendances/_new_form.html.erb +4 -0
  120. data/app/views/attend/admin/attendances/index.html.erb +7 -0
  121. data/app/views/attend/admin/base/_financial_month_menu.html.erb +10 -0
  122. data/app/views/attend/admin/base/_nav.html.erb +3 -0
  123. data/app/views/attend/admin/base/_nav_attendances.html.erb +7 -0
  124. data/app/views/attend/admin/extra_days/_breadcrumb.html.erb +0 -0
  125. data/app/views/attend/admin/extra_days/_edit_form.html.erb +4 -0
  126. data/app/views/attend/admin/extra_days/_filter_form.html.erb +5 -0
  127. data/app/views/attend/admin/extra_days/_form.html.erb +4 -0
  128. data/app/views/attend/admin/extra_days/_index_tbody.html.erb +3 -0
  129. data/app/views/attend/admin/extra_days/_index_thead.html.erb +4 -0
  130. data/app/views/attend/admin/extra_days/_new_form.html.erb +4 -0
  131. data/app/views/attend/admin/extra_days/index.html.erb +7 -0
  132. data/app/views/attend/admin/financial_months/_breadcrumb.html.erb +0 -0
  133. data/app/views/attend/admin/financial_months/_edit_form.html.erb +4 -0
  134. data/app/views/attend/admin/financial_months/_filter_form.html.erb +9 -0
  135. data/app/views/attend/admin/financial_months/_form.html.erb +4 -0
  136. data/app/views/attend/admin/financial_months/_index_tbody.html.erb +9 -0
  137. data/app/views/attend/admin/financial_months/_index_thead.html.erb +6 -0
  138. data/app/views/attend/admin/financial_months/_new_form.html.erb +4 -0
  139. data/app/views/attend/admin/financial_months/events.json.jbuilder +9 -0
  140. data/app/views/attend/admin/financial_months/index.html.erb +7 -0
  141. data/app/views/attend/admin/financial_months/index.js +60 -0
  142. data/app/views/attend/admin/financial_months/show.html.erb +10 -0
  143. data/app/views/attend/admin/overtimes/_button.html.erb +0 -0
  144. data/app/views/attend/admin/overtimes/_edit_form.html.erb +4 -0
  145. data/app/views/attend/admin/overtimes/_filter_form.html.erb +8 -0
  146. data/app/views/attend/admin/overtimes/_form.html.erb +3 -0
  147. data/app/views/attend/admin/overtimes/_index_tbody.html.erb +22 -0
  148. data/app/views/attend/admin/overtimes/_index_thead.html.erb +9 -0
  149. data/app/views/attend/admin/overtimes/_new_form.html.erb +4 -0
  150. data/app/views/attend/admin/overtimes/index.html.erb +5 -0
  151. data/app/views/attend/admin/overtimes/my.html.erb +8 -0
  152. data/app/views/attend/me/absences/_absence.html.erb +34 -0
  153. data/app/views/attend/me/absences/_edit_form.html.erb +3 -0
  154. data/app/views/attend/me/absences/_filter_form.html.erb +11 -0
  155. data/app/views/attend/me/absences/_form.html.erb +33 -0
  156. data/app/views/attend/me/absences/_index_tbody.html.erb +24 -0
  157. data/app/views/attend/me/absences/_index_thead.html.erb +8 -0
  158. data/app/views/attend/me/absences/_new_form.html.erb +3 -0
  159. data/app/views/attend/me/absences/_redeeming_form.html.erb +5 -0
  160. data/app/views/attend/me/absences/_show.html.erb +11 -0
  161. data/app/views/attend/me/absences/dashboard.html.erb +44 -0
  162. data/app/views/attend/me/absences/index.html.erb +7 -0
  163. data/app/views/attend/me/absences/redeeming.js.erb +6 -0
  164. data/app/views/attend/me/absences/show.html.erb +1 -0
  165. data/app/views/attend/me/attendance_logs/_edit_form.html.erb +4 -0
  166. data/app/views/attend/me/attendance_logs/_filter_form.html.erb +11 -0
  167. data/app/views/attend/me/attendance_logs/_form.html.erb +11 -0
  168. data/app/views/attend/me/attendance_logs/_index_thead.html.erb +9 -0
  169. data/app/views/attend/me/attendance_logs/_new_form.html.erb +4 -0
  170. data/app/views/attend/me/attendance_logs/index.html.erb +42 -0
  171. data/app/views/attend/me/attendance_settings/_edit_form.html.erb +6 -0
  172. data/app/views/attend/me/attendance_settings/_index_tbody.html.erb +15 -0
  173. data/app/views/attend/me/attendance_settings/_index_thead.html.erb +12 -0
  174. data/app/views/attend/me/attendance_settings/_new_form.html.erb +8 -0
  175. data/app/views/attend/me/attendance_settings/index.html.erb +5 -0
  176. data/app/views/attend/me/attendance_stats/_form.html.erb +6 -0
  177. data/app/views/attend/me/attendance_stats/_search_form.html.erb +5 -0
  178. data/app/views/attend/me/attendance_stats/edit.html.erb +9 -0
  179. data/app/views/attend/me/attendance_stats/index.html.erb +49 -0
  180. data/app/views/attend/me/attendance_stats/new.html.erb +9 -0
  181. data/app/views/attend/me/attendance_stats/show.html.erb +28 -0
  182. data/app/views/attend/me/attendances/_button.html.erb +3 -0
  183. data/app/views/attend/me/attendances/_filter_form.html.erb +9 -0
  184. data/app/views/attend/me/attendances/_form.html.erb +22 -0
  185. data/app/views/attend/me/attendances/_index_tbody.html.erb +49 -0
  186. data/app/views/attend/me/attendances/_index_thead.html.erb +19 -0
  187. data/app/views/attend/me/attendances/index.html.erb +7 -0
  188. data/app/views/attend/me/base/_menu.html.erb +8 -0
  189. data/app/views/attend/me/base/_nav_absences.html.erb +9 -0
  190. data/app/views/attend/me/base/_nav_attendances.html.erb +10 -0
  191. data/app/views/attend/me/calendars/events.json.jbuilder +19 -0
  192. data/app/views/attend/me/calendars/show.html.erb +12 -0
  193. data/app/views/attend/me/overtimes/_edit_form.html.erb +4 -0
  194. data/app/views/attend/me/overtimes/_filter_form.html.erb +9 -0
  195. data/app/views/attend/me/overtimes/_form.html.erb +16 -0
  196. data/app/views/attend/me/overtimes/_index_tbody.html.erb +23 -0
  197. data/app/views/attend/me/overtimes/_index_thead.html.erb +6 -0
  198. data/app/views/attend/me/overtimes/_new_form.html.erb +4 -0
  199. data/app/views/attend/me/overtimes/index.html.erb +7 -0
  200. data/app/views/me/_attend_nav.html.erb +12 -0
  201. data/app/views/me/_nav_attendance_settings.html.erb +8 -0
  202. data/config/locales/en.attributes.yml +34 -0
  203. data/config/locales/en.controller.yml +18 -0
  204. data/config/locales/en.enum.yml +43 -0
  205. data/config/locales/en.notify.yml +38 -0
  206. data/config/locales/zh.attributes.yml +34 -0
  207. data/config/locales/zh.controller.yml +40 -0
  208. data/config/locales/zh.enum.yml +43 -0
  209. data/config/locales/zh.notify.yml +38 -0
  210. data/config/routes.rb +70 -0
  211. data/lib/rails_attend/config.rb +11 -0
  212. data/lib/rails_attend/engine.rb +25 -0
  213. data/lib/rails_attend.rb +2 -0
  214. 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