gestion 1.9.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (233) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +48 -0
  3. data/.project +14 -0
  4. data/Binaries/adduser_to_buzz +15 -0
  5. data/Binaries/backup +7 -0
  6. data/Binaries/check_gestion +8 -0
  7. data/Binaries/gestion.gnumail +22 -0
  8. data/Binaries/gestion.logrotate +34 -0
  9. data/Binaries/gestion.service +12 -0
  10. data/Binaries/gestion_update.rb +183 -0
  11. data/Binaries/gestion_update.service +10 -0
  12. data/Binaries/get_compta +11 -0
  13. data/Binaries/kill_gestion +16 -0
  14. data/Binaries/ldap/add_indexes +51 -0
  15. data/Binaries/ldap/backup +2 -0
  16. data/Binaries/ldap/install_ldap +92 -0
  17. data/Binaries/ldap/restore +7 -0
  18. data/Binaries/lib_backup +5 -0
  19. data/Binaries/log_scan_errors +8 -0
  20. data/Binaries/loop_gestion +64 -0
  21. data/Binaries/onetimers/sync_courses_from_compta.rb +74 -0
  22. data/Binaries/onetimers/transfer_cash_from_ldap_to_csv +26 -0
  23. data/Binaries/reboot +5 -0
  24. data/Binaries/restore +3 -0
  25. data/Binaries/restore_do +22 -0
  26. data/Binaries/sort_events +31 -0
  27. data/Binaries/start_gestion +18 -0
  28. data/Binaries/swipe_gestion +18 -0
  29. data/Binaries/update_africompta +21 -0
  30. data/Binaries/update_users +3 -0
  31. data/Diplomas.src/accredited.odg +0 -0
  32. data/Diplomas.src/diploma.odg +0 -0
  33. data/Diplomas.src/label.odg +0 -0
  34. data/Diplomas.src/presence_sheet.ods +0 -0
  35. data/Diplomas.src/presence_sheet_small.ods +0 -0
  36. data/Diplomas.src/student_card.odg +0 -0
  37. data/Doc/130514-it-ideas.odt +0 -0
  38. data/Doc/Compta-cash.mm +179 -0
  39. data/Doc/General.odt +0 -0
  40. data/Entities/AccessGroups.rb +117 -0
  41. data/Entities/Activity.rb +178 -0
  42. data/Entities/ChatMsg.rb +142 -0
  43. data/Entities/Classroom.rb +11 -0
  44. data/Entities/Client.rb +19 -0
  45. data/Entities/Computer.rb +21 -0
  46. data/Entities/ConfigBase.rb +280 -0
  47. data/Entities/Course.rb +1588 -0
  48. data/Entities/CourseType.rb +171 -0
  49. data/Entities/DFiles.rb +466 -0
  50. data/Entities/FilesManage.rb +226 -0
  51. data/Entities/Grade.rb +186 -0
  52. data/Entities/Internet.rb +300 -0
  53. data/Entities/Netdev.rb +10 -0
  54. data/Entities/Person.rb +1175 -0
  55. data/Entities/Plug.rb +98 -0
  56. data/Entities/Quiz.rb +33 -0
  57. data/Entities/Recharges.rb +37 -0
  58. data/Entities/Report.rb +136 -0
  59. data/Entities/Room.rb +12 -0
  60. data/Entities/SMS.rb +30 -0
  61. data/Entities/ScheduleType.rb +33 -0
  62. data/Entities/Share.rb +120 -0
  63. data/Entities/Task.rb +51 -0
  64. data/Entities/Ticket.rb +72 -0
  65. data/Entities/Usage.rb +143 -0
  66. data/Entities/Worker.rb +29 -0
  67. data/Files/apache-profeda.conf +36 -0
  68. data/Files/label.erb +121 -0
  69. data/Files/label_notfound.erb +64 -0
  70. data/Files/label_notpassed.erb +84 -0
  71. data/Files/mobileinfo.erb +115 -0
  72. data/Files/smb.conf +333 -0
  73. data/Files/timetable.html +36 -0
  74. data/Files/timetable.js +239 -0
  75. data/Gemfile +12 -0
  76. data/Gemfile.dev +12 -0
  77. data/Gemfile.dev.lock +127 -0
  78. data/Gemfile.lock +127 -0
  79. data/Gemfile.prod +8 -0
  80. data/Gestion +35 -0
  81. data/Gestion.rb +220 -0
  82. data/INSTALL +40 -0
  83. data/Images/connection.xcf +0 -0
  84. data/Images/connection_no.png +0 -0
  85. data/Images/connection_wait.png +0 -0
  86. data/Images/connection_yes.png +0 -0
  87. data/Paths/Exas.rb +13 -0
  88. data/Paths/Files.rb +19 -0
  89. data/Paths/GetDiplomas.rb +20 -0
  90. data/Paths/Info.rb +114 -0
  91. data/Paths/Label.rb +187 -0
  92. data/Paths/MobileInfo.rb +19 -0
  93. data/Paths/internetCash.rb +34 -0
  94. data/Paths/internetWifi.rb +54 -0
  95. data/README.md +60 -0
  96. data/Rakefile +13 -0
  97. data/TODO +1391 -0
  98. data/Test/.gitignore +3 -0
  99. data/Test/Diplomas/base_gestion.odt +0 -0
  100. data/Test/Diplomas/base_report.odt +0 -0
  101. data/Test/Diplomas/carte_etudiant.odg +0 -0
  102. data/Test/Diplomas/exam_language.odt +0 -0
  103. data/Test/Diplomas/label.odg +0 -0
  104. data/Test/Diplomas/presence_sheet.ods +0 -0
  105. data/Test/Diplomas/presence_sheet_small.ods +0 -0
  106. data/Test/Diplomas/student_card.odg +0 -0
  107. data/Test/Manual/testMerge +18 -0
  108. data/Test/config_test.yaml +26 -0
  109. data/Test/db.testGestion +0 -0
  110. data/Test/dfiles/descs/avg-rescue.desc +10 -0
  111. data/Test/dfiles/descs/avg.desc +8 -0
  112. data/Test/dfiles/descs/driver.desc +8 -0
  113. data/Test/dfiles/descs/linuxmint.desc +7 -0
  114. data/Test/dfiles/files/avg-160203.exe +1 -0
  115. data/Test/dfiles/files/avg.iso +1 -0
  116. data/Test/dfiles/files/driver.exe +1 -0
  117. data/Test/dfiles/index_post.html +3 -0
  118. data/Test/dfiles/index_pre.html +8 -0
  119. data/Test/dfiles/priorities +5 -0
  120. data/Test/ge_activity.rb +124 -0
  121. data/Test/ge_chat.rb +106 -0
  122. data/Test/ge_compta.rb +67 -0
  123. data/Test/ge_configbase.rb +54 -0
  124. data/Test/ge_course.rb +1114 -0
  125. data/Test/ge_dfiles.rb +121 -0
  126. data/Test/ge_filesmanage.rb +180 -0
  127. data/Test/ge_info.rb +27 -0
  128. data/Test/ge_internet.rb +246 -0
  129. data/Test/ge_login.rb +55 -0
  130. data/Test/ge_person.rb +373 -0
  131. data/Test/ge_qvinfo.rb +28 -0
  132. data/Test/ge_report.rb +97 -0
  133. data/Test/ge_share.rb +27 -0
  134. data/Test/ge_sms.rb +34 -0
  135. data/Test/ge_tasks.rb +19 -0
  136. data/Test/ge_usage.rb +168 -0
  137. data/Test/ge_view.rb +46 -0
  138. data/Test/multiconf-captive +29 -0
  139. data/Test/test.conf +7 -0
  140. data/Test/test.rb +49 -0
  141. data/Test/test_bytes.png +0 -0
  142. data/VERSION +140 -0
  143. data/Views/Admin/Backup.rb +91 -0
  144. data/Views/Admin/Configuration.rb +44 -0
  145. data/Views/Admin/Credit.rb +32 -0
  146. data/Views/Admin/FilesManage.rb +219 -0
  147. data/Views/Admin/Function.rb +39 -0
  148. data/Views/Admin/Power.rb +49 -0
  149. data/Views/Admin/Printer.rb +37 -0
  150. data/Views/Admin/Server.rb +252 -0
  151. data/Views/Admin/Tabs.rb +5 -0
  152. data/Views/Admin/Update.rb +73 -0
  153. data/Views/Admin/UpdateSystem.rb +26 -0
  154. data/Views/Cashbox/Activity.rb +191 -0
  155. data/Views/Cashbox/Course.rb +141 -0
  156. data/Views/Cashbox/Credit.rb +79 -0
  157. data/Views/Cashbox/Report.rb +115 -0
  158. data/Views/Cashbox/Service.rb +105 -0
  159. data/Views/Cashbox/Tabs.rb +10 -0
  160. data/Views/Compta/Accounts.rb +36 -0
  161. data/Views/Compta/Course.rb +96 -0
  162. data/Views/Compta/Show.rb +6 -0
  163. data/Views/Compta/Transfer.rb +66 -0
  164. data/Views/Course/Diploma.rb +203 -0
  165. data/Views/Course/Grade.rb +401 -0
  166. data/Views/Course/Modify.rb +447 -0
  167. data/Views/Course/Print.rb +94 -0
  168. data/Views/Course/Responsible.rb +44 -0
  169. data/Views/Course/Stats.rb +76 -0
  170. data/Views/Course/Students.rb +92 -0
  171. data/Views/Course/Tabs.rb +220 -0
  172. data/Views/Internet/Access.rb +134 -0
  173. data/Views/Internet/ClassEdit.rb +24 -0
  174. data/Views/Internet/ClassUsers.rb +81 -0
  175. data/Views/Internet/Config.rb +32 -0
  176. data/Views/Internet/Mobile.rb +213 -0
  177. data/Views/Internet/Recharges.rb +49 -0
  178. data/Views/Internet/Tabs.rb +6 -0
  179. data/Views/Inventory/Computer.rb +24 -0
  180. data/Views/Inventory/Room.rb +18 -0
  181. data/Views/Inventory/Tabs.rb +9 -0
  182. data/Views/Inventory/TicketClosed.rb +7 -0
  183. data/Views/Inventory/TicketOpen.rb +23 -0
  184. data/Views/Library/Person.rb +36 -0
  185. data/Views/Library/Tabs.rb +7 -0
  186. data/Views/Network/Block.rb +87 -0
  187. data/Views/Network/Netdevs.rb +21 -0
  188. data/Views/Network/Restriction.rb +37 -0
  189. data/Views/Network/Share.rb +167 -0
  190. data/Views/Network/Tables.rb +28 -0
  191. data/Views/Network/Tabs.rb +6 -0
  192. data/Views/Person/Admin.rb +99 -0
  193. data/Views/Person/Center.rb +48 -0
  194. data/Views/Person/Course.rb +72 -0
  195. data/Views/Person/Modify.rb +153 -0
  196. data/Views/Person/Tabs.rb +162 -0
  197. data/Views/Report/ComptaExecutive.rb +221 -0
  198. data/Views/Report/ComptaFlat.rb +79 -0
  199. data/Views/Report/ReportCourse.rb +47 -0
  200. data/Views/Report/Tabs.rb +8 -0
  201. data/Views/Report/Usage.rb +52 -0
  202. data/Views/Report/UsageCases.rb +59 -0
  203. data/Views/Self/Cash.rb +67 -0
  204. data/Views/Self/Chat.rb +55 -0
  205. data/Views/Self/Concours.rb +109 -0
  206. data/Views/Self/Email.rb +34 -0
  207. data/Views/Self/Internet.rb +255 -0
  208. data/Views/Self/Results.rb +17 -0
  209. data/Views/Self/Services.rb +85 -0
  210. data/Views/Self/Show.rb +47 -0
  211. data/Views/Self/Tabs.rb +5 -0
  212. data/Views/Special/DFileEdit.rb +13 -0
  213. data/Views/Special/PlugEdit.rb +56 -0
  214. data/Views/Special/Tabs.rb +6 -0
  215. data/Views/Special/Vnc.rb +39 -0
  216. data/Views/Task/Client.rb +21 -0
  217. data/Views/Task/Edit.rb +33 -0
  218. data/Views/Task/List.rb +55 -0
  219. data/Views/Task/Tabs.rb +9 -0
  220. data/Views/Task/Worker.rb +30 -0
  221. data/Views/Template/Activity.rb +33 -0
  222. data/Views/Template/CourseType.rb +63 -0
  223. data/Views/Template/ScheduleType.rb +29 -0
  224. data/Views/Template/Tabs.rb +5 -0
  225. data/Views/Welcome.rb +121 -0
  226. data/config.yaml.default +36 -0
  227. data/po/Gestion-ar.po +2356 -0
  228. data/po/Gestion-en.mo +0 -0
  229. data/po/Gestion-en.po +4363 -0
  230. data/po/Gestion-fr.mo +0 -0
  231. data/po/Gestion-fr.po +4345 -0
  232. data/po/traduction-ar.rtf +76 -0
  233. metadata +381 -0
@@ -0,0 +1,1588 @@
1
+ # A course has:
2
+ # - Beginning, End and days of week
3
+ # - Person.Students
4
+ # - Teacher and Assistant
5
+ # - Classroom
6
+
7
+ require 'zip'
8
+ require 'zip/filesystem'
9
+ require 'docsplit'
10
+ require 'rqrcode'
11
+ require 'rqrcode/export/png'
12
+ require 'base64'
13
+
14
+
15
+ class Courses < Entities
16
+ attr_reader :dir_diplomas, :dir_exams, :dir_exams_share,
17
+ :print_presence, :print_presence_small, :print_exa, :print_exa_long
18
+
19
+ def setup_data
20
+
21
+ value_block :name
22
+ value_entity_courseType_ro_all :ctype, :drop, :name
23
+ value_str :name
24
+
25
+ value_block :calendar
26
+ value_date :start
27
+ value_date :end
28
+ value_date :sign
29
+ value_int :duration
30
+ value_list_drop :dow, '%w( lu-me-ve ma-je-sa lu-ve ma-sa )'
31
+ value_list_drop :hours, '%w( 9-12 16-18 9-11 )'
32
+ value_entity_room_all :classroom, :drop, :name
33
+
34
+ value_block :students
35
+ value_list :students
36
+
37
+ value_block :teacher
38
+ value_entity_person :teacher, :drop, :full_name
39
+ value_entity_person_empty :assistant, :drop, :full_name
40
+ value_entity_person :responsible, :drop, :full_name
41
+
42
+ value_block :center
43
+ value_entity_person_empty_all :center, :drop, :full_name
44
+
45
+ value_block :content
46
+ value_str :description
47
+ value_text :contents
48
+
49
+ value_block :accounting
50
+ value_int :salary_teacher
51
+ value_int :salary_assistant
52
+ value_int :students_start
53
+ value_int :students_finish
54
+ value_int :cost_student
55
+ value_int :entry_total
56
+
57
+ value_block :account
58
+ value_entity_account_empty :entries, :drop, :path
59
+
60
+ @dir_diplomas = ConfigBase.diploma_dir
61
+ @dir_exams = ConfigBase.exam_dir
62
+ @dir_exams_share = "#{@dir_exams}/Share"
63
+
64
+ [@dir_diplomas, @dir_exams, @dir_exams_share].each { |d|
65
+ File.exists? d or FileUtils.mkdir d
66
+ }
67
+
68
+ @thread = nil
69
+ @presence_sheet ||= 'presence_sheet.ods'
70
+ @presence_sheet_small ||= 'presence_sheet_small.ods'
71
+ @print_presence = OpenPrint.new("#{@dir_diplomas}/#{@presence_sheet}")
72
+ @print_presence_small = OpenPrint.new("#{@dir_diplomas}/#{@presence_sheet_small}")
73
+ end
74
+
75
+ def set_entry(id, field, value)
76
+ case field.to_s
77
+ when 'name'
78
+ value.gsub!(/[^a-zA-Z0-9_-]/, '_')
79
+ end
80
+ super(id, field, value)
81
+ end
82
+
83
+ def sort_courses(c)
84
+ c.collect { |d| [d.course_id, d.name] }.sort { |a, b|
85
+ a[1].gsub(/.*([0-9]{4}.*)/, '\1') <=> b[1].gsub(/.*([0-9]{4}.*)/, '\1')
86
+ }.reverse
87
+ end
88
+
89
+ def list_courses_raw(session=nil)
90
+ ret = search_all_
91
+ if session != nil
92
+ user = session.owner
93
+ if not session.can_view('FlagCourseGradeAll')
94
+ ret = ret.select { |d|
95
+ dputs(4) { "teacher is #{d.teacher.inspect}, user is #{user.inspect}" }
96
+ (d.teacher and d.teacher.login_name == user.login_name) or
97
+ (d.responsible and d.responsible.login_name == user.login_name) or
98
+ ((d.name =~ /^#{session.owner.login_name}_/) and
99
+ session.owner.permissions.index('center'))
100
+ }
101
+ end
102
+ end
103
+ ret
104
+ end
105
+
106
+ def list_courses(session=nil)
107
+ sort_courses(list_courses_raw(session))
108
+ end
109
+
110
+ def list_courses_entries(session=nil)
111
+ sort_courses(list_courses_raw(session).select { |c| c.entries })
112
+ end
113
+
114
+ def list_courses_for_person(person)
115
+ ln = person.class == String ? person : person.login_name
116
+ dputs(3) { "Searching courses for person #{ln}" }
117
+ ret = @data.values.select { |d|
118
+ dputs(3) { "Searching #{ln} in #{d.inspect} - #{d[:students].index(ln)}" }
119
+ d[:students] and d[:students].index(ln)
120
+ }.collect { |c| Courses.match_by_course_id(c._course_id) }
121
+ dputs(3) { "Found courses #{ret.inspect}" }
122
+ sort_courses(ret)
123
+ end
124
+
125
+ def self.create_ctype(ctype, date, creator = nil)
126
+ #dputs_func
127
+ needs_center = (ConfigBase.has_function?(:course_server) and
128
+ (creator and creator.has_permission?(:center)))
129
+ dputs(4) { "needs_center is #{needs_center.inspect}" }
130
+
131
+ # Prepare correct name
132
+ name = if needs_center
133
+ if creator.permissions.index 'center'
134
+ creator.login_name
135
+ else
136
+ Persons.find_by_permissions(:center).login_name
137
+ end + '_'
138
+ else
139
+ ''
140
+ end + "#{ctype.name}_#{date}"
141
+
142
+ # Check for double names
143
+ suffix = ''
144
+ counter = 1
145
+ while Courses.match_by_name(name + suffix)
146
+ counter += 1
147
+ suffix = "-#{counter}"
148
+ end
149
+ name += suffix
150
+
151
+ course = self.create(:name => name)
152
+ course.data_set_hash(ctype.to_hash.except(:name), true).ctype = ctype
153
+
154
+ if needs_center
155
+ dputs(3) { "Got center of #{creator.inspect}" }
156
+ course.center = creator
157
+ end
158
+
159
+ if ConfigBase.get_functions.index :accounting_courses
160
+ course.create_account
161
+ end
162
+
163
+ course.salary_teacher = ctype.salary_teacher
164
+ course.cost_student = ctype.cost_student
165
+
166
+ log_msg :course, "Created new course #{course.inspect}"
167
+ return course
168
+ end
169
+
170
+ def self.from_date_fr(str)
171
+ day, month, year = str.split(' ')
172
+ day = day.gsub(/^0/, '')
173
+ if day == '1er'
174
+ day = '1'
175
+ end
176
+ month = %w( janvier février mars avril mai juin juillet août
177
+ septembre octobre novembre décembre ).index(month) + 1
178
+ "#{day.to_s.rjust(2, '0')}.#{month.to_s.rjust(2, '0')}.#{year.to_s.rjust(4, '2000')}"
179
+ end
180
+
181
+ def self.from_diploma(course_name, course_str)
182
+ dputs(1) { "Importing #{course_name}: #{course_str.gsub(/\n/, '*')}" }
183
+ course = Entities.Courses.match_by_name(course_name) or
184
+ Entities.Courses.create(:name => course_name)
185
+
186
+ lines = course_str.split("\n")
187
+ template = lines.shift
188
+ dputs(2) { "Template is: #{template}" }
189
+ dputs(2) { "lines are: #{lines.inspect}" }
190
+ case template
191
+ when /base_gestion/ then
192
+ course.teacher, course.responsible = lines.shift(2).collect { |p|
193
+ Entities.Persons.find_full_name(p)
194
+ }
195
+ if not course.teacher or not course.responsible then
196
+ return nil
197
+ end
198
+ course.teacher, course.responsible = course.teacher.login_name, course.responsible.login_name
199
+ course.duration, course.description = lines.shift(2)
200
+ course.contents = ''
201
+ while lines[0].size > 0
202
+ course.contents += lines.shift
203
+ end
204
+ dputs(2) { "Course contents: #{course.contents}" }
205
+ lines.shift
206
+ course.start, course.end, course.sign =
207
+ lines.shift(3).collect { |d| self.from_date_fr(d) }
208
+ lines.shift if lines[0].size == 0
209
+
210
+ course.students = []
211
+ while lines.size > 0
212
+ grade, name = lines.shift.split(' ', 2)
213
+ student = Entities.Persons.find_name_or_create(name)
214
+ course.students_add student
215
+ g = Grades.match_by_course_person(course, student)
216
+ if g then
217
+ g.mean, g.remark = Grades.grade_to_mean(grade), lines.shift
218
+ else
219
+ Grades.create(:course => course, :student => student,
220
+ :mean => Grades.grade_to_mean(grade), :remark => lines.shift)
221
+ end
222
+ end
223
+ dputs(3) { "#{course.inspect}" }
224
+ else
225
+ import_old(lines)
226
+ end
227
+ course
228
+ end
229
+
230
+ def migration_1(c)
231
+ name = c.classroom
232
+ if name.class == Array
233
+ name = name.join
234
+ end
235
+ dputs(4) { "Converting for name #{name} with #{Rooms.data.inspect}" }
236
+ r = Rooms.match_by_name(name)
237
+ if (not r) and (not (r = Rooms.match_by_name('')))
238
+ r = nil
239
+ end
240
+ c.classroom = r
241
+ dputs(4) { "New room is #{c.classroom.inspect}" }
242
+ end
243
+
244
+ def migration_2_raw(c)
245
+ %w( teacher assistant responsible ).each { |p|
246
+ person = c[p.to_sym]
247
+ dputs(4) { "#{p} is before #{person.inspect}" }
248
+ if p == 'assistant' and person == ['none']
249
+ person = nil
250
+ else
251
+ begin
252
+ person = Persons.match_by_login_name(person.join).person_id
253
+ rescue NoMethodError
254
+ person = Persons.match_by_login_name('admin').person_id
255
+ end
256
+ end
257
+ dputs(4) { "#{p} is after #{person.inspect}" }
258
+ c[p.to_sym] = person
259
+ }
260
+ end
261
+
262
+ def icc_users(tr)
263
+ users = tr._data
264
+ dputs(3) { "users are #{users.inspect}" }
265
+ users.each { |s|
266
+ s.to_sym!
267
+ if s._login_name != tr._user
268
+ s._login_name = "#{tr._user}_#{s._login_name}"
269
+ else
270
+ s.delete :password
271
+ end
272
+ %w( person_id groups ).each { |f|
273
+ s.delete f.to_sym
274
+ }
275
+ s._permissions = if s._permissions
276
+ s._permissions & %w( teacher center )
277
+ else
278
+ dputs(0) { "User #{s._login_name} has no permissions!" }
279
+ []
280
+ end
281
+ dputs(3) { "Person is #{s.inspect}" }
282
+ dputs(4) { "Looking for #{s._login_name}" }
283
+ if stud = Persons.match_by_login_name(s._login_name)
284
+ dputs(3) { "Updating person #{stud.login_name} with #{s._login_name}" }
285
+ stud.data_set_hash(s)
286
+ else
287
+ dputs(3) { "Creating person #{s.inspect}" }
288
+ Persons.create(s)
289
+ end
290
+ }
291
+ "Got users #{users.collect { |u| u._login_name }.join(':')}"
292
+ end
293
+
294
+ def center_course_name(course, user)
295
+ course =~ /^#{user}_/ ? course : "#{user}_#{course}"
296
+ end
297
+
298
+ def icc_grades_get(tr)
299
+ # dputs_func
300
+ dputs(3) { "Data is #{tr.inspect}" }
301
+ course = Courses.match_by_name(tr._course)
302
+ return "Course #{tr._course} not found" unless course
303
+ course._students.collect{|s|
304
+ if grade = Grades.match_by_course_person(course, s)
305
+ g = grade.to_hash
306
+ g._student = grade.student.login_name.sub(/^#{tr._center}_/, '')
307
+ g
308
+ else
309
+ nil
310
+ end
311
+ }.select{|g| g}
312
+ end
313
+
314
+ def icc_courses(tr)
315
+ c = tr._center
316
+ return "Error: Didn't find center #{c.inspect}}" unless center = Persons.match_by_login_name(c._login_name)
317
+ return "Error: Passwords do not match for #{c.inspect}" unless center.password_plain == c._password_plain
318
+ courses = Courses.search_by_name("^#{center.login_name}_").collect { |c|
319
+ ret = c.to_hash
320
+ ret._students = c.students.collect { |s| no_center(s, center) }
321
+ ret._ctype = c.ctype.name
322
+ ret._teacher = no_center(c.teacher.login_name, center)
323
+ if c.assistant
324
+ ret._assistant = no_center(c.assistant.login_name, center)
325
+ end
326
+ ret._responsible = no_center(c.responsible.login_name, center)
327
+ ret
328
+ }
329
+ log_msg :ICC_courses, "Returning #{courses}"
330
+ return courses
331
+ end
332
+
333
+ def icc_course(tr)
334
+ course = tr._data.to_sym
335
+ dputs(3) { "Course is #{course.inspect}" }
336
+ course.delete :course_id
337
+ course._name = center_course_name(course._name, tr._user)
338
+ course._responsible = Persons.match_by_login_name(
339
+ "#{tr._user}_#{course._responsible}")
340
+ course._teacher = Persons.match_by_login_name(
341
+ "#{tr._user}_#{course._teacher}")
342
+ course._assistant = Persons.match_by_login_name(
343
+ "#{tr._user}_#{course._assistant}")
344
+ course._students = course._students.collect { |s| "#{tr._user}_#{s}" }
345
+ course._ctype = CourseTypes.match_by_name(course._ctype)
346
+ return "Error: couldn't make course of type #{course._ctype}" unless course._ctype
347
+ course._center = Persons.match_by_login_name(tr._user)
348
+ course._room = Rooms.find_by_name('')
349
+ dputs(3) { "Course is now #{course.inspect}" }
350
+ if c = Courses.match_by_name(course._name)
351
+ dputs(3) { "Updating course #{course._name}" }
352
+ c.data_set_hash(course)
353
+ else
354
+ dputs(3) { "Creating course #{course._name}" }
355
+ Courses.create(course)
356
+ end
357
+ "Updated course #{course._name}"
358
+ end
359
+
360
+ def icc_grades(tr)
361
+ tr._data.collect { |grade|
362
+ grade.to_sym!
363
+ ret = [grade._course, grade._student]
364
+ dputs(3) { "Grades is #{grade.inspect}" }
365
+ grade._course =
366
+ Courses.match_by_name("#{tr._user}_#{grade._course}")
367
+ grade._student =
368
+ Persons.match_by_login_name("#{tr._user}_#{grade._student}")
369
+ grade.delete :grade_id
370
+ grade.delete :random
371
+ if g = Grades.match_by_course_person(grade._course,
372
+ grade._student)
373
+ dputs(3) { "Updating grade #{g.inspect} with #{grade.inspect}" }
374
+ g.data_set_hash(grade)
375
+ else
376
+ g = Grades.create(grade)
377
+ dputs(3) { "Creating grade #{g.inspect} with #{grade.inspect}" }
378
+ end
379
+ dputs(3) { Grades.match_by_course_person(grade._course,
380
+ grade._student).inspect }
381
+ ret.push g.random
382
+ }
383
+ end
384
+
385
+ def icc_exams(tr)
386
+ tr._tid.gsub!(/[^a-zA-Z0-9_-]/, '')
387
+ file = "/tmp/#{tr._tid}.zip"
388
+ File.open(file, 'w') { |f| f.write Base64::decode64(tr._data._zip) }
389
+ if course = Courses.match_by_name(center_course_name(tr._data._course, tr._user))
390
+ dputs(3) { 'Updating exams' }
391
+ course.zip_read(file)
392
+ end
393
+ "Read file #{file}"
394
+ end
395
+
396
+ def icc_exams_here(tr)
397
+ course_name = center_course_name(tr._data, tr._user)
398
+ if course = Courses.match_by_name(course_name)
399
+ dputs(3) { "Sending md5 of #{course_name}" }
400
+ course.md5_exams
401
+ else
402
+ dputs(3) { "Didn't find #{course_name}" }
403
+ {}
404
+ end
405
+ end
406
+
407
+ def no_center(str, center)
408
+ str.sub(/^#{center.login_name}_/, '')
409
+ end
410
+
411
+ end
412
+
413
+
414
+ class Course < Entity
415
+ attr_reader :sync_state, :make_pdfs_state
416
+ attr :thread
417
+
418
+ def setup_instance
419
+ if not self.students.class == Array
420
+ self.students = []
421
+ end
422
+
423
+ check_students_dir
424
+ @make_pdfs_state = {'0' => 'done'}
425
+ @only_psnup = false
426
+ end
427
+
428
+ def update_exam_file
429
+ if ctype.file_exam.class == Array
430
+ @print_exam_file = OpenPrint.new("#{ConfigBase.template_dir}/#{ctype.file_exam.first}")
431
+ end
432
+ end
433
+
434
+ def ctype=(ct)
435
+ self._ctype = ct
436
+ if ctype.file_exam and ctype.file_exam.length > 0
437
+ update_exam_file
438
+ end
439
+ end
440
+
441
+ def update_state(force = false)
442
+ if @make_pdfs_state[0] == 'done' || force
443
+ students.collect { |s|
444
+ dputs(3) { "Working on #{s}" }
445
+ Persons.match_by_login_name(s)
446
+ }.compact.each { |s|
447
+ get_grade_args(s, true)
448
+ }
449
+ end
450
+ end
451
+
452
+ def check_dir
453
+ [dir_diplomas, dir_exams, dir_exams_share].each { |d|
454
+ (!File.exists? d) and FileUtils.mkdir_p(d)
455
+ }
456
+ end
457
+
458
+ def check_students_dir
459
+ check_dir
460
+ students and students.each { |s|
461
+ [dir_exams, dir_exams_share].each { |d|
462
+ (!File.exists? "#{d}/#{s}") and FileUtils.mkdir("#{d}/#{s}")
463
+ }
464
+ }
465
+ end
466
+
467
+ def dir_diplomas
468
+ @proxy.dir_diplomas + "/#{self.name}"
469
+ end
470
+
471
+ def dir_exams
472
+ @proxy.dir_exams + "/#{self.name}"
473
+ end
474
+
475
+ def dir_exams_share
476
+ @proxy.dir_exams_share + "/#{self.name}"
477
+ end
478
+
479
+ def list_students(by_id = false)
480
+ dputs(3) { "Students for #{self.name} are: #{self.students.inspect}" }
481
+ ret = []
482
+ if self.students
483
+ ret = self.students.collect { |s|
484
+ if person = Persons.match_by_login_name(s)
485
+ [(by_id ? person.person_id : s),
486
+ "#{person.full_name} - #{person.login_name}:#{person.password_plain}"]
487
+ end
488
+ }.compact.sort { |a, b| a[1] <=> b[1] }
489
+ end
490
+ ret
491
+ end
492
+
493
+ def to_hash(unique_ids = false)
494
+ ret = super(unique_ids).clone
495
+ ret.delete :students
496
+ ret.merge :students => list_students
497
+ end
498
+
499
+ # Tests if we have everything necessary handy
500
+ def export_check
501
+ missing_data = []
502
+ %w( start end sign duration teacher responsible description contents ).each { |s|
503
+ d = data_get s
504
+ if not d
505
+ dputs(1) { "Failed checking #{s}: #{d}" }
506
+ missing_data.push s
507
+ end
508
+ }
509
+ return missing_data.size == 0 ? nil : missing_data
510
+ end
511
+
512
+ def date_fr(d, show_year = true)
513
+ day, month, year = d.split('.')
514
+ day = day.gsub(/^0/, '')
515
+ if day == '1'
516
+ day = '1er'
517
+ end
518
+ month = %w( janvier février mars avril mai juin juillet août septembre octobre novembre décembre )[month.to_i-1]
519
+ if show_year
520
+ [day, month, year].join(' ')
521
+ else
522
+ [day, month].join(' ')
523
+ end
524
+ end
525
+
526
+ def date_en(d, show_year = true)
527
+ day, month, year = d.split('.')
528
+ day = day.gsub(/^0/, '')
529
+ day_str = case day.to_i
530
+ when 1, 21, 31
531
+ "#{day}st"
532
+ when 2, 22
533
+ "#{day}nd"
534
+ when 3, 23
535
+ "#{day}rd"
536
+ when 4..20, 24..30
537
+ "#{day}th"
538
+ end
539
+ month = %w( January February March April May June July August September October November December )[month.to_i-1]
540
+ if show_year
541
+ "#{day_str} of #{month}, #{year}"
542
+ else
543
+ "#{day_str} of #{month}"
544
+ end
545
+ end
546
+
547
+ def date_i18n(d, show_year = true)
548
+ return unless ctype.diploma_lang
549
+ case ctype.diploma_lang.first
550
+ when /en/
551
+ date_en(d, show_year)
552
+ when /fr/
553
+ date_fr(d, show_year)
554
+ else
555
+ dputs(0) { "Unknown date type #{ctype.diploma_lang.inspect}" }
556
+ end
557
+ end
558
+
559
+ def export_diploma
560
+ return if export_check
561
+
562
+ d_start, d_end, d_sign = data_get(%w( start end sign ))
563
+ same_year = 0
564
+ [d_start, d_end, d_sign].each { |d|
565
+ year = d.gsub(/.*\./, '')
566
+ if same_year == 0
567
+ same_year = year
568
+ elsif same_year != year
569
+ same_year = false
570
+ end
571
+ }
572
+ txt = <<-END
573
+ base_gestion
574
+ #{teacher.full_name}
575
+ #{responsible.full_name}
576
+ #{duration}
577
+ #{description}
578
+ #{contents}
579
+
580
+ #{date_fr(d_start, same_year)}
581
+ #{date_fr(d_end, same_year)}
582
+ #{date_fr(d_sign)}
583
+ END
584
+ students.each { |s|
585
+ grade = Grades.match_by_course_person(course_id, s)
586
+ if grade
587
+ txt += "#{grade} #{grade.student.full_name}\n" +
588
+ "#{grade.remark}\n"
589
+ end
590
+ }
591
+ dputs(2) { "Text is: #{txt.gsub(/\n/, '*')}" }
592
+ txt
593
+ end
594
+
595
+ def get_files
596
+ if File::directory?(dir_diplomas)
597
+ files = if ctype.output[0] == 'certificate'
598
+ Dir::glob("#{dir_diplomas}/*pdf")
599
+ else
600
+ Dir::glob("#{dir_diplomas}/*png") +
601
+ Dir::glob("#{dir_diplomas}/*zip")
602
+ end
603
+ files.collect { |f|
604
+ File::basename(f)
605
+ }.sort
606
+ else
607
+ []
608
+ end
609
+ end
610
+
611
+ def dstart
612
+ Date.strptime(start, '%d.%m.%Y')
613
+ end
614
+
615
+ def dend
616
+ Date.strptime(data_get(:end), '%d.%m.%Y')
617
+ end
618
+
619
+ def get_duration_adds
620
+ return [0, 0] if not start or not data_get(:end)
621
+ dputs(4) { "start is: #{start} - end is #{data_get(:end)} - dow is #{dow}" }
622
+ days_per_week, adds = case dow.to_s
623
+ when /lu-me-ve/, /ma-je-sa/
624
+ [3, [0, 2, 4]]
625
+ when /lu-ve/, /ma-sa/
626
+ [5, [0, 1, 2, 3, 4]]
627
+ end
628
+ weeks = ((dend - dstart) / 7).ceil
629
+ day = 0
630
+ dow_adds = (0...weeks).collect { |w| adds.collect { |a|
631
+ day += 1
632
+ [/#{1000 + day}/, a + w * 7]
633
+ }
634
+ }.flatten(1)
635
+ dputs(4) { "dow_adds is now #{dow_adds.inspect}" }
636
+
637
+ [days_per_week * weeks, dow_adds]
638
+ end
639
+
640
+ def print_presence(lp_cmd = nil)
641
+ return false if not teacher
642
+ return false if not start or not data_get(:end) or students.count == 0
643
+ stud_nr = 1
644
+ studs = students.sort { |a, b|
645
+ Persons.match_by_login_name(a).full_name <=>
646
+ Persons.match_by_login_name(b).full_name }.collect { |s|
647
+ stud = Entities.Persons.match_by_login_name(s)
648
+ stud_str = stud_nr.to_s.rjust(2, '0')
649
+ stud_nr += 1
650
+ [[/Nom#{stud_str}/, stud.full_name],
651
+ [/Login#{stud_str}/, stud.login_name],
652
+ [/Passe#{stud_str}/, stud.password_plain]]
653
+ }
654
+ dputs(3) { "Students are: #{studs.inspect}" }
655
+ duration, dow_adds = get_duration_adds
656
+
657
+ pp = if duration <= 25 and stud_nr <= 16
658
+ @proxy.print_presence_small
659
+ else
660
+ @proxy.print_presence
661
+ end
662
+ pp.lp_cmd = lp_cmd
663
+ pp.print(studs.flatten(1) + dow_adds + [
664
+ [/Teacher/, teacher.full_name],
665
+ [/Course_name/, name],
666
+ [/2010-08-20/, dstart.to_s],
667
+ [/20.08.10/, dstart.strftime('%d/%m/%y')],
668
+ [/2010-10-20/, dend.to_s],
669
+ [/20.10.10/, dend.strftime('%d/%m/%y')],
670
+ [/123/, students.count],
671
+ [/321/, duration],
672
+ ])
673
+ end
674
+
675
+ def print_exam_file(lp_cmd = nil)
676
+ #dputs_func
677
+ if !self.start || !self.end || !teacher || !center || !students ||
678
+ students.length == 0
679
+ return false
680
+ end
681
+
682
+ stud_nr = 0
683
+ studs = students.collect { |s|
684
+ Entities.Persons.match_by_login_name(s)
685
+ }.sort_by { |s| s.full_name }.collect { |stud|
686
+ stud_str = (stud_nr%12+1).to_s.rjust(2, '0')
687
+ stud_nr += 1
688
+ [/-NAME#{stud_str}-/, stud.full_name]
689
+ }
690
+ (0..11).each { |i|
691
+ studs.push [/-NAME#{(12-i).to_s.rjust(2, '0')}-/, '']
692
+ }
693
+ dputs(3) { "#{stud_nr}: Students are: #{studs.inspect}" }
694
+
695
+ tests = (1..9).zip(ctype.tests_arr).collect { |i, t|
696
+ [/-TEST#{i}-/, t.to_s]
697
+ }
698
+
699
+ @print_exam_file.lp_cmd = lp_cmd
700
+
701
+ pdfs = (0..(stud_nr-1)/12).collect { |i|
702
+ @print_exam_file.make_pdf(studs.shift(12) + tests + [
703
+ [/-TEACHER-/, teacher.full_name],
704
+ [/-COURSE_ID-/, name],
705
+ [/-FROM-/, date_i18n(self.start)],
706
+ [/-TO-/, date_i18n(self.end)],
707
+ [/-RESP-/, responsible.full_name],
708
+ [/-CENTER-/, center.full_name]
709
+ ])
710
+ }
711
+ dputs(3) { "Pdf-files are #{pdfs.inspect}" }
712
+ @print_exam_file.print_join(pdfs)
713
+ end
714
+
715
+ def get_grade_args(student, update = false)
716
+ grade = Grades.match_by_course_person(course_id, student)
717
+ dputs(3) { "Course is #{name} - student is #{student} - ctype is #{ctype.inspect} and grade is " +
718
+ "#{grade.inspect} - #{grade.to_s}" }
719
+
720
+ state = if (ctype.diploma_type[0] == 'accredited') && grade &&
721
+ (!grade.random)
722
+ 'not synched'
723
+ elsif exam_files(student).count < ctype.files_nbr.to_i
724
+ 'incomplete'
725
+ elsif !grade
726
+ 'no grade'
727
+ # Reports are created even for those who failed
728
+ elsif grade.to_s == 'NP' && ctype.diploma_type != %w(report)
729
+ 'not passed'
730
+ elsif update
731
+ if get_files.find { |f| f =~ /^[0-9]+-#{student.login_name}\./ }
732
+ 'done'
733
+ else
734
+ 'not created'
735
+ end
736
+ else
737
+ 'queued'
738
+ end
739
+ mean = if grade and grade.mean
740
+ grade.mean
741
+ else
742
+ '-'
743
+ end
744
+ dputs(4) { "State is #{state}" }
745
+ ln = student.login_name
746
+ @make_pdfs_state[ln] = [mean, state, get_diploma_filename(ln, 'pdf', false)]
747
+
748
+ [grade, state]
749
+ end
750
+
751
+ def update_student_diploma(file, student)
752
+ # dputs_func
753
+ grade, state = get_grade_args(student)
754
+
755
+ #if grade and grade.to_s != "NP" and
756
+ # ( ( ctype.diploma_type[0] == "simple" ) or
757
+ # ( exam_files( student ).count >= ctype.files_nbr.to_i ) ) and
758
+ # ( ( ctype.diploma_type[0] == "accredited" ) and grade.random )
759
+ # @make_pdfs_state[student.login_name] = [ grade.mean, "queued" ]
760
+ if state != 'queued'
761
+ dputs(2) {"File #{file} is not queued, but #{state} - removing"}
762
+ FileUtils.rm(file)
763
+ else
764
+ dputs(3) { "New diploma for: #{course_id} - #{student.login_name} - #{grade.to_hash.inspect}" }
765
+ Zip::File.open(file) { |z|
766
+ dputs(5) { "Cours is #{self.inspect}" }
767
+ doc = z.read('content.xml')
768
+ doc.force_encoding(Encoding::UTF_8)
769
+ dputs(5) { doc.inspect }
770
+ dputs(5) { "Contents is: #{contents.inspect}" }
771
+ if qrcode = /draw:image.*xlink:href="([^"]*).*QRcode.*\/draw:frame/.match(doc)
772
+ dputs(2) { "QRcode-image is #{qrcode[1]}" }
773
+ qr = RQRCode::QRCode.new(grade.get_url_label)
774
+ png = qr.as_png(:border_modules => 0)
775
+ z.get_output_stream(qrcode[1]) { |f|
776
+ png.write(f)
777
+ }
778
+ end
779
+
780
+ cont = contents +
781
+ (grade.remark.to_s.length > 0 ? "\n#{grade.remark}" : '')
782
+ if desc_p_match = /-DESC1-(.*)-DESC2-/.match(doc)
783
+ desc_p = desc_p_match[1]
784
+ dputs(3) { "desc_p is #{desc_p}" }
785
+ doc.gsub!(/-DESC1-.*-DESC2-/,
786
+ cont.split("\n").join(desc_p))
787
+ end
788
+ doc.gsub!(/-TEACHER-/, teacher.full_name)
789
+ role_diploma = 'Enseignant informatique'
790
+ if teacher.role_diploma.to_s.length > 0
791
+ role_diploma = teacher.role_diploma
792
+ end
793
+ doc.gsub!(/-TEACHER_ROLE-/, role_diploma)
794
+ role_diploma = 'Responsable informatique'
795
+ if responsible.role_diploma.to_s.length > 0
796
+ role_diploma = responsible.role_diploma
797
+ end
798
+ show_year = start.gsub(/.*\./, '') != self.end.gsub(/.*\./, '')
799
+ c = center
800
+ OpenPrint.replace(
801
+ doc,
802
+ [[/-RESP_ROLE-/, role_diploma],
803
+ [/-RESP-/, responsible.full_name],
804
+ [/-NAME-/, student.full_name],
805
+ [/-DURATION-/, duration.to_s],
806
+ [/-COURSE-/, description],
807
+ [/-COURSE_ID-/, name],
808
+ [/-SPECIAL-/, ''],
809
+ [/-GENDER-/, student.gender_i18n(ctype.diploma_lang.first)],
810
+ [/-GRADE-/, grade.mention],
811
+ [/-DATE-/, date_i18n(sign)],
812
+ [/-FROM-/, date_i18n(start, show_year)],
813
+ [/-TO-/, date_i18n(self.end)],
814
+ [/-COURSE_TYPE-/, ctype.name],
815
+ [/-URL_LABEL-/, grade.get_url_label],
816
+ [/-CENTER_NAME-/, c.full_name],
817
+ [/-CENTER_ADDRESS-/, c.address || ''],
818
+ [/-CENTER_PLACE-/, c.town || ''],
819
+ [/-CENTER_PHONE-/, c.phone || ''],
820
+ [/-CENTER_EMAIL-/, c.email || ''],
821
+ [/-MEAN-/, sprintf('%2.2f', grade.mean)]])
822
+
823
+ dputs(3) { "ctype is #{ctype.inspect}" }
824
+ if ctype.diploma_type.first =~ /report/
825
+ dputs(3) { 'Adding report-lines' }
826
+ if test_p_match = /-TEST1-(.*)-MEAN1-(.*)-TEST2-/.match(doc)
827
+ test_p = test_p_match[1..2]
828
+ dputs(3) { "test_p is #{test_p.inspect}" }
829
+ if grade.means.count == ctype.tests_arr.count
830
+ tests = ctype.tests_arr.zip(grade.means).collect { |t, m|
831
+ t + test_p[0] + sprintf('%2.1f', m)
832
+ }.join(test_p[1])
833
+ doc.gsub!(/-TEST1-.*-MEAN2-/, tests)
834
+ else
835
+ dputs(1) { "Incomplete tests for #{student.login_name}" }
836
+ end
837
+ end
838
+ end
839
+ doc.gsub!(/(number-rows-spanned=)\"3\"/, "\\1\"#{ctype.tests_nbr.to_i+1}\"")
840
+ z.get_output_stream('content.xml') { |f|
841
+ f.write(doc)
842
+ }
843
+ z.commit
844
+ }
845
+ end
846
+ end
847
+
848
+ def get_diploma_filename(student, ext = 'odt', diplomadir = true)
849
+ digits = students.size.to_s.size
850
+ counter = students.index(student) + 1
851
+ str = diplomadir ? dir_diplomas : name
852
+ "#{str}/#{counter.to_s.rjust(digits, '0')}-#{student}.#{ext}"
853
+ end
854
+
855
+ def make_pdfs(convert)
856
+ # dputs_func
857
+ if @thread
858
+ dputs(2) { 'Thread is here, killing' }
859
+ begin
860
+ abort_pdfs
861
+ rescue Exception => e
862
+ dputs(0) { "Error while killing: #{e.message}" }
863
+ dputs(0) { "#{e.inspect}" }
864
+ dputs(0) { "#{e.to_s}" }
865
+ puts e.backtrace
866
+ end
867
+ end
868
+ @make_pdfs_state = {'0' => 'collecting'}
869
+
870
+ dputs(2) { 'Starting new thread' }
871
+ @thread = Thread.new {
872
+ begin
873
+ counter = 1
874
+ dputs(2) { "Preparing students: #{students.inspect}" }
875
+ if !@only_psnup
876
+ students.sort.each { |s|
877
+ student = Persons.match_by_login_name(s)
878
+ if student
879
+ dputs(4) { "Is #{s} == #{student.login_name}?" }
880
+ student_file = get_diploma_filename(s)
881
+ dputs(2) { "Doing #{counter}: #{s} - file: #{student_file}" }
882
+ FileUtils.cp("#{Courses.dir_diplomas}/#{ctype.file_diploma.join}",
883
+ student_file)
884
+ update_student_diploma(student_file, student)
885
+ end
886
+ counter += 1
887
+ }
888
+ end
889
+
890
+ dputs(3) { "Convert is #{convert.inspect}" }
891
+ if convert
892
+ @make_pdfs_state['0'] = 'converting'
893
+ old = Dir.glob(dir_diplomas + '/content.xml*')
894
+ list = Dir.glob(dir_diplomas + '/*odt')
895
+ format = ctype.output[0].to_sym
896
+
897
+ dputs(4) { "old is #{old.inspect}" }
898
+ dputs(4) { "list is #{list.inspect}" }
899
+
900
+ FileUtils.rm(old)
901
+ if list.size == 0
902
+ dputs(2) { 'No files here, quitting' }
903
+ @make_pdfs_state['0'] = 'done'
904
+ @thread.kill
905
+ end
906
+ dputs(2) { "Creating -#{format.inspect}-#{list.inspect}-" }
907
+ System.run_bool('date >> /tmp/cp')
908
+ System.run_bool("ls -l #{dir_diplomas} >> /tmp/cp")
909
+ outfiles = []
910
+ dir = File::dirname(list.first)
911
+ @only_psnup and list = []
912
+ list.sort.each { |p|
913
+ dputs(3) { "Started thread for file #{p} in directory #{dir}" }
914
+ student_name = p.sub(/.*[0-9]+-/, '').sub(/\.odt/, '')
915
+ dputs(3) { "Student name is #{student_name}" }
916
+ @make_pdfs_state[student_name][1] = 'working'
917
+
918
+ if format == :certificate
919
+ Docsplit.extract_pdf p, :output => dir
920
+ else
921
+ Docsplit.extract_images p, :output => dir,
922
+ :density => 300, :format => :png
923
+ FileUtils.mv(p.sub(/.odt$/, '_1.png'), p.sub(/.odt$/, '.png'))
924
+ end
925
+ dputs(5) { 'Finished docsplit' }
926
+ FileUtils.rm(p)
927
+ dputs(5) { 'Finished rm' }
928
+ outfile = p.sub(/\.[^\.]*$/, format == :certificate ? '.pdf' : '.png')
929
+ outfiles.push outfile
930
+ @make_pdfs_state[student_name][1] = 'done'
931
+ @make_pdfs_state[student_name][2] = outfile.sub(/^#{@proxy.dir_diplomas}./, '')
932
+ }
933
+ @make_pdfs_state['0'] = 'collecting'
934
+ if format == :certificate
935
+ dputs(3) { "Getting #{outfiles.inspect} out of #{dir}" }
936
+ all = "#{dir}/000-all.pdf"
937
+ psn = "#{dir}/000-4pp.pdf"
938
+ #cmd = "pdftk #{outfiles.join( ' ' )} cat output #{all}"
939
+ if outfiles.length > 1
940
+ cmd = "pdfunite #{outfiles.join(' ')} #{all}"
941
+ dputs(3) { "Putting it all in one file: #{cmd}" }
942
+ System.run_bool(cmd)
943
+ else
944
+ dputs(3) { "#{outfiles.first} - #{all}" }
945
+ FileUtils.cp(outfiles.first, all)
946
+ end
947
+ dputs(3) { "Putting 4 pages of #{all} into #{psn}" }
948
+ pf = ctype.data_get(:page_format, true)[0]
949
+ format = ['', '-f', '-l', '-r'][pf - 1]
950
+ dputs(3) { "Page-format is #{pf.inspect}: #{format}" }
951
+ System.run_bool("pdftops #{all} - | psnup -4 #{format} | ps2pdf -sPAPERSIZE=a4 - #{psn}.tmp")
952
+ FileUtils.mv("#{psn}.tmp", psn)
953
+ dputs(2) { 'Finished' }
954
+ else
955
+ dputs(3) { 'Making a zip-file' }
956
+ Zip::File.open("#{dir}/all.zip", Zip::File::CREATE) { |z|
957
+ Dir.glob("#{dir}/*").each { |image|
958
+ z.get_output_stream(image.sub('.*/', '')) { |f|
959
+ File.open(image) { |fi|
960
+ f.write fi.read
961
+ }
962
+ }
963
+ }
964
+ }
965
+ end
966
+ end
967
+ rescue Exception => e
968
+ dputs(0) { "Error in thread: #{e.message}" }
969
+ dputs(0) { "#{e.inspect}" }
970
+ dputs(0) { "#{e.to_s}" }
971
+ puts e.backtrace
972
+ end
973
+ @make_pdfs_state['0'] = 'done'
974
+ }
975
+ end
976
+
977
+ def prepare_diplomas(convert = true)
978
+ dputs(2) { "dir_diplomas is: #{dir_diplomas}" }
979
+ if not File::directory? dir_diplomas
980
+ FileUtils.mkdir(dir_diplomas)
981
+ else
982
+ if !@only_psnup
983
+ FileUtils.rm_rf(Dir.glob(dir_diplomas + '/*'))
984
+ end
985
+ end
986
+ #@make_pdfs_state = {}
987
+ make_pdfs(convert)
988
+ end
989
+
990
+ # This prepares a zip-file as a skeleton for the center to copy the
991
+ # files over.
992
+ # The name of the zip-file is different from the directory-name, so that the
993
+ # upload is less error-prone.
994
+ # @param [Object] for_server
995
+ # @param [Object] include_files
996
+ # @param [Object] md5sums
997
+ # @param [Object] users
998
+ def zip_create(for_server: true, include_files: true, md5sums: {},
999
+ size_exams: -1, files_added: nil)
1000
+ pre = for_server ? center.login_name + '_' : ''
1001
+ dir = "exa-#{pre}#{name}"
1002
+ file = "#{pre}#{name}.zip"
1003
+ tmp_file = "/tmp/#{file}"
1004
+ dputs(2) { "for_server:#{for_server} - include_files:#{include_files} " +
1005
+ "md5sums:#{md5sums.inspect} - size_exams:#{size_exams.inspect}" }
1006
+
1007
+ if students and students.size > 0
1008
+ File.exists?(tmp_file) and FileUtils.rm(tmp_file)
1009
+ Zip::File.open(tmp_file, Zip::File::CREATE) { |z|
1010
+ z.mkdir dir
1011
+ dputs(3) { "Students is #{students.inspect}" }
1012
+ files_excluded = []
1013
+ students.sort.each { |s|
1014
+ p = "#{dir}/#{pre}#{s}"
1015
+ dputs(3) { "Creating #{p}" }
1016
+ z.mkdir(p)
1017
+ if include_files
1018
+ dputs(3) { "Searching in #{dir_exams}/#{s}" }
1019
+ Dir.glob("#{dir_exams}/#{s}/*").sort.each { |exa_f|
1020
+ exa_md5 = Digest::MD5.file(exa_f).hexdigest
1021
+ file_add = true
1022
+ filename = exa_f.sub(/.*\//, '')
1023
+ if md5sums.has_key?(s)
1024
+ md5sums[s].each { |f, md5|
1025
+ if (f == filename) && (md5 == exa_md5)
1026
+ dputs(3) { "Found file #{filename} to be excluded" }
1027
+ file_add = false
1028
+ files_excluded.push exa_f.sub(/^#{dir_exams}\//, '')
1029
+ end
1030
+ }
1031
+ end
1032
+ if file_add and size_exams != 0
1033
+ dputs(3) { "Adding file #{exa_f} with size #{size_exams}" }
1034
+ files_added and files_added.push [s, File.basename(exa_f), exa_md5]
1035
+ z.file.open("#{p}/#{exa_f.sub(/.*\//, '')}", 'w') { |f|
1036
+ f.write File.open(exa_f) { |ef|
1037
+ content = ef.read
1038
+ dputs(3) { "Size of file is #{content.size}" }
1039
+ size_exams -= [content.size, size_exams.abs].min
1040
+ content
1041
+ }
1042
+ }
1043
+ end
1044
+ }
1045
+ end
1046
+ }
1047
+ dputs(3) { "Files_excluded are #{files_excluded.inspect}" }
1048
+ z.file.open("#{dir}/files_excluded", 'w') { |f|
1049
+ f.write files_excluded.to_json
1050
+ }
1051
+ }
1052
+ return file
1053
+ end
1054
+ return nil
1055
+ end
1056
+
1057
+ def zip_read(f = nil)
1058
+ name.length == 0 and return
1059
+
1060
+ dir_zip = "exa-#{name.sub(/^#{center}_/, '')}"
1061
+ dir_exams = @proxy.dir_exams + "/#{name}"
1062
+ dir_exams_tmp = "/tmp/#{name}"
1063
+ file = f || "/tmp/#{dir_zip}.zip"
1064
+ dputs(3) { "dir_zip: #{dir_zip}, dir_exams: #{dir_exams}, dir_exams_tmp: #{dir_exams_tmp}, " +
1065
+ "file: #{file}" }
1066
+
1067
+ if File.exists?(file) && students
1068
+ # Save existing exams in /tmp
1069
+ FileUtils.rm_rf dir_exams_tmp
1070
+ if File.exists? dir_exams
1071
+ dputs(3) { "Moving #{dir_exams} to /tmp" }
1072
+ dputs(3) { "#{dir_exams} is " + Dir.glob("#{dir_exams}/**/*").join(' ') }
1073
+ FileUtils.mv dir_exams, dir_exams_tmp
1074
+ end
1075
+ FileUtils.mkdir dir_exams
1076
+
1077
+ dputs(3) { "Opening zip-file #{file}" }
1078
+ Zip::File.open(file) { |z|
1079
+ students.each { |s|
1080
+ dir_zip_student = "#{dir_zip}/#{s}"
1081
+ dir_exams_student = "#{dir_exams}/#{s}"
1082
+
1083
+ begin
1084
+ FileUtils.mkdir(dir_exams_student)
1085
+ if (files_student = z.dir.entries(dir_zip_student)).size > 0
1086
+ files_student.each { |fs|
1087
+ dputs(3) { "Extracting #{dir_exams_student}/#{fs}" }
1088
+ z.extract("#{dir_zip_student}/#{fs}", "#{dir_exams_student}/#{fs}")
1089
+ }
1090
+ end
1091
+ rescue Errno::ENOENT => e
1092
+ dputs(3) { "Directory for student #{s} doesn't exist" }
1093
+ end
1094
+ }
1095
+ begin
1096
+ center_pre = ConfigBase.has_function?(:course_server) ?
1097
+ "#{center.login_name}_" : ''
1098
+ JSON.parse(z.read("#{dir_zip}/files_excluded")).each { |f|
1099
+ dputs(3) { "Transferring file #{f} from old to new directory" }
1100
+ FileUtils.cp "#{dir_exams_tmp}/#{center_pre + f}",
1101
+ "#{dir_exams}/#{center_pre + f}"
1102
+ }
1103
+ rescue Errno::ENOENT => e
1104
+ dputs(3) { 'No files_excluded here' }
1105
+ end
1106
+ }
1107
+ FileUtils.rm file
1108
+ end
1109
+ end
1110
+
1111
+ def exam_files(student)
1112
+ student_name = student.class == Person ? student.login_name : student
1113
+ dputs(4) { "Student-name is #{student_name.inspect}" }
1114
+ dir_exams = @proxy.dir_exams + "/#{name}"
1115
+ dir_student = "#{dir_exams}/#{student_name}"
1116
+ File.exists?(dir_student) ?
1117
+ Dir.entries(dir_student).select { |f| !(f =~ /^\./) } : []
1118
+ end
1119
+
1120
+ def exas_prepare_files
1121
+ name.length == 0 and return
1122
+ if File.exists? dir_exams_share
1123
+ FileUtils.rm_rf dir_exams_share
1124
+ end
1125
+
1126
+ FileUtils.mkdir dir_exams_share
1127
+ students.each { |s|
1128
+ dir_s_exas = "#{dir_exams}/#{s}"
1129
+ if File.exists? dir_s_exas
1130
+ FileUtils.mv dir_s_exas, dir_exams_share
1131
+ else
1132
+ FileUtils.mkdir "#{dir_exams_share}/#{s}"
1133
+ end
1134
+ }
1135
+ FileUtils.rm_rf(dir_exams)
1136
+ end
1137
+
1138
+ def exas_fetch_files
1139
+ name.length == 0 and return
1140
+ dputs(3) { "Starting to fetch files for #{name}" }
1141
+ if File.exists? dir_exams_share
1142
+ dputs(3) { "#{dir_exams_share} exists" }
1143
+ File.exists? dir_exams or FileUtils.mkdir dir_exams
1144
+ students.each { |s|
1145
+ dputs(3) { "Checking on student #{s}" }
1146
+ dir_student = "#{dir_exams_share}/#{s}"
1147
+ if File.exists? dir_student
1148
+ dputs(3) { "Moving student-dir of #{s}" }
1149
+ FileUtils.move dir_student, "#{dir_exams}"
1150
+ end
1151
+ }
1152
+ end
1153
+ FileUtils.rm_rf dir_exams_share
1154
+ end
1155
+
1156
+ def sync_transfer(field, transfer = '', json = true)
1157
+ ss = @sync_state
1158
+ ret = ICC.transfer(Persons.center, "Courses.#{field}", transfer,
1159
+ url: ConfigBase.get_url(:server_url), json: json) { |s|
1160
+ @sync_state = "#{ss} #{s}" }
1161
+ @sync_state = ss
1162
+ ret
1163
+ end
1164
+
1165
+ def sync_do
1166
+ @sync_state = sync_s = ''
1167
+ dputs(3) { @sync_state }
1168
+
1169
+ dputs(4) { 'Responsibles' }
1170
+ @sync_state = sync_s += '<li>Transferring responsibles: '
1171
+ users = [teacher, responsible, center, assistant].compact.collect { |n| n.login_name }
1172
+ ret = sync_transfer(:users, users.collect { |s|
1173
+ Persons.match_by_login_name(s)
1174
+ }.compact)
1175
+ if ret._code == 'Error'
1176
+ @sync_state += "Error: #{ret._msg}"
1177
+ dputs(2) { "Error is #{ret._msg}" }
1178
+ return false
1179
+ end
1180
+ @sync_state = sync_s += 'OK</li>'
1181
+
1182
+ dputs(4) { 'Students' }
1183
+ if students.length > 0
1184
+ dputs(4) { 'Students - go' }
1185
+ @sync_state = sync_s += '<li>Transferring users: '
1186
+ users = students + [teacher.login_name, responsible.login_name]
1187
+ ret = sync_transfer(:users, users.collect { |s|
1188
+ Persons.match_by_login_name(s)
1189
+ })
1190
+ if ret._code == 'Error'
1191
+ @sync_state += "Error: #{ret._msg}"
1192
+ return false
1193
+ end
1194
+ @sync_state = sync_s += 'OK</li>'
1195
+ end
1196
+
1197
+ dputs(4) { 'Courses' }
1198
+ @sync_state = sync_s += '<li>Transferring course: '
1199
+ myself = self.to_hash(true)
1200
+ myself._students = students
1201
+ ret = sync_transfer(:course, myself)
1202
+ if ret._code == 'Error'
1203
+ @sync_state += "Error: #{ret._msg}"
1204
+ return false
1205
+ end
1206
+ @sync_state = sync_s += 'OK</li>'
1207
+
1208
+ dputs(4) { 'Exams' }
1209
+ remote_exams = {}
1210
+ if true
1211
+ dputs(4) { 'Fetching remote exams' }
1212
+ @sync_state = sync_s += '<li>Demander ce qui existe déjà: '
1213
+ ret = sync_transfer(:exams_here, self.name)
1214
+ if ret._code == 'Error'
1215
+ @sync_state += "Error: #{ret._msg}"
1216
+ return false
1217
+ end
1218
+ remote_exams = ret._msg
1219
+ @sync_state = sync_s += 'OK</li>'
1220
+ end
1221
+
1222
+ local_exams = md5_exams
1223
+ files = zip_create_chunks(local_exams, remote_exams)
1224
+ files.each { |file|
1225
+ dputs(4) { 'Exams - go' }
1226
+ @sync_state = sync_s + '<li>Transferring exams ' +
1227
+ "#{files.index(file) + 1}/#{files.count}: "
1228
+ file = "/tmp/#{file}"
1229
+ dputs(3) { "Exa-file is #{file}" }
1230
+ file_64 = Base64::encode64(File.open(file) { |f| f.read }.
1231
+ force_encoding(Encoding::ASCII_8BIT))
1232
+ ret = sync_transfer(:exams, {zip: file_64, course: name})
1233
+ if ret._code == 'Error'
1234
+ @sync_state += "Error: #{ret._msg}"
1235
+ return false
1236
+ end
1237
+ }
1238
+ @sync_state = sync_s += '<li>Transferring exams: OK</li>'
1239
+
1240
+ dputs(4) { 'Grades' }
1241
+ if (grades = Grades.matches_by_course(self.course_id)).length > 0
1242
+ dputs(4) { 'Grades - go' }
1243
+ @sync_state = sync_s += '<li>Transferring grades: '
1244
+ ret = sync_transfer(:grades, grades.select { |g|
1245
+ g.course and g.student
1246
+ }.collect { |g|
1247
+ dputs(4) { "Found grade with #{g.course.inspect} and #{g.student.inspect}" }
1248
+ g.to_hash(true).merge(:course => g.course.name,
1249
+ :person => g.student.login_name)
1250
+ })
1251
+ if ret._code == 'Error'
1252
+ @sync_state += "Error: #{ret._msg}"
1253
+ return false
1254
+ end
1255
+ grades = ret._msg
1256
+ #grades = JSON.parse(ret.sub(/^OK: /, ''))
1257
+ dputs(3) { "Return is #{grades.inspect}" }
1258
+ grades.each { |g|
1259
+ course_name, student, random = g
1260
+ course = Courses.match_by_name(course_name)
1261
+ if grade = Grades.match_by_course_person(course, student)
1262
+ dputs(4) { "Setting grade-random of #{grade.grade_id} to #{random}" }
1263
+ grade.random = random
1264
+ else
1265
+ dputs(0) { "Error: Can't find grade for #{course}-#{student}!" }
1266
+ end
1267
+ }
1268
+ @sync_state = sync_s += 'OK</li>'
1269
+ end
1270
+
1271
+ @sync_state = sync_s += 'It is finished!'
1272
+ dputs(3) { @sync_state }
1273
+ return true
1274
+ end
1275
+
1276
+ def sort_md5s(m)
1277
+ m.map { |k, v| {k => v.sort { |a, b| a[0] <=> b[0] }} }
1278
+ end
1279
+
1280
+ def zip_create_chunks(local, remote)
1281
+ files = []
1282
+ loop {
1283
+ fa = []
1284
+ dputs(3) { "Remote: #{remote.inspect}" }
1285
+ dputs(3) { "Local: #{local.inspect}" }
1286
+ zipfile = zip_create(md5sums: remote, size_exams: ConfigBase.max_upload_size.to_i,
1287
+ files_added: fa)
1288
+ fa.length == 0 and return files
1289
+
1290
+ zipfile_cnt = "#{zipfile.chomp('.zip')}-#{files.size}.zip"
1291
+ FileUtils.mv "/tmp/#{zipfile}", "/tmp/#{zipfile_cnt}"
1292
+ files.push zipfile_cnt
1293
+ dputs(3) { "Zip-file #{files.last} has files added #{fa.inspect}" }
1294
+ fa.each { |s, f, md5|
1295
+ dputs(3) { "Found student #{s} with file #{f} and md5 #{md5} in zip" }
1296
+ remote[s] ||= []
1297
+ remote[s].push [f, md5]
1298
+ }
1299
+ }
1300
+ []
1301
+ end
1302
+
1303
+ def sync_start
1304
+ if @thread
1305
+ dputs(2) { 'Thread is here, killing' }
1306
+ begin
1307
+ abort_pdfs
1308
+ rescue Exception => e
1309
+ dputs(0) { "Error while killing: #{e.message}" }
1310
+ dputs(0) { "#{e.inspect}" }
1311
+ dputs(0) { "#{e.to_s}" }
1312
+ puts e.backtrace
1313
+ end
1314
+ end
1315
+ dputs(2) { 'Starting new thread' }
1316
+ @sync_state = 'Starting'
1317
+ @thread = Thread.new {
1318
+ begin
1319
+ sync_do
1320
+ rescue Exception => e
1321
+ dputs(0) { "Error in thread: #{e.message}" }
1322
+ dputs(0) { "#{e.inspect}" }
1323
+ dputs(0) { "#{e.to_s}" }
1324
+ puts e.backtrace
1325
+ @sync_state += "Error: thread reported #{e.to_s}"
1326
+ end
1327
+ }
1328
+ end
1329
+
1330
+ def get_unique
1331
+ name
1332
+ end
1333
+
1334
+ def center
1335
+ return _center || Persons.find_by_permissions(:center)
1336
+ end
1337
+
1338
+ def abort_pdfs
1339
+ if @thread
1340
+ dputs(2) { "Killing thread #{@thread}" }
1341
+ @thread.kill
1342
+ @thread.join
1343
+ @thread = nil
1344
+ @make_pdfs_state = {'0' => 'done'}
1345
+ dputs(3) { 'Joined thread' }
1346
+ end
1347
+ end
1348
+
1349
+ def delete
1350
+ abort_pdfs
1351
+
1352
+ [dir_diplomas, dir_exams, dir_exams_share].each { |d|
1353
+ FileUtils.remove_entry_secure(d, true)
1354
+ }
1355
+ super
1356
+ end
1357
+
1358
+ def students=(s)
1359
+ self._students = s
1360
+ @pre_init or log_msg :course, "Students for #{name} are: #{students.inspect}"
1361
+ end
1362
+
1363
+ def students_add(studs)
1364
+ [studs].flatten.each { |s|
1365
+ s.class == Person and s = s.login_name
1366
+ log_msg :course, "Adding student #{s} to course #{name}"
1367
+ self.students = (students || []) + [s]
1368
+ }
1369
+ end
1370
+
1371
+ def students_del(studs)
1372
+ [studs].flatten.each { |s|
1373
+ s.class == Person and s = s.login_name
1374
+ log_msg :course, "Deleting student #{s} to course #{name}"
1375
+ self.students = (students || []) - [s]
1376
+ }
1377
+ end
1378
+
1379
+ def report_pdf
1380
+ file = "/tmp/course_#{name}.pdf"
1381
+ Prawn::Document.generate(file,
1382
+ :page_size => 'A4',
1383
+ :page_layout => :portrait,
1384
+ :bottom_margin => 2.cm) do |pdf|
1385
+
1386
+ sum = 0
1387
+ pdf.text "Report for #{name} (#{ctype.name})",
1388
+ :align => :center, :size => 20
1389
+ pdf.font_size 10
1390
+ pdf.text "Duration: #{start}-#{self.end} - - Teacher: #{teacher.full_name}" +
1391
+ " - - Hours: #{hours}"
1392
+ pdf.text "Cost per student: #{Account.total_form(cost_student.to_i / 1000)} - - " +
1393
+ "Cost per teacher: #{Account.total_form(salary_teacher.to_i / 1000)}"
1394
+ pdf.text "Account: #{entries.path}"
1395
+ pdf.move_down 1.cm
1396
+
1397
+ if students.length > 0
1398
+ pdf.table([['Description', 'Value', 'Sum'].collect { |ch|
1399
+ {:content => ch, :align => :center} }] +
1400
+ report_list.collect { |id, t|
1401
+ [t[0] == 'Reste' ? t[1] : t[0],
1402
+ t[2],
1403
+ t[3]]
1404
+ },
1405
+ :header => true, :column_widths => [300, 75, 75])
1406
+ pdf.move_down(2.cm)
1407
+ end
1408
+
1409
+ pdf.repeat(:all, :dynamic => true) do
1410
+ pdf.draw_text "#{Date.today} - #{entries.path}",
1411
+ :at => [0, -20], :size => 10
1412
+ pdf.draw_text pdf.page_number, :at => [18.cm, -20]
1413
+ end
1414
+ end
1415
+ file
1416
+ end
1417
+
1418
+ def student_paid(student)
1419
+ movs = entries.movements
1420
+ if archives = entries.get_archives
1421
+ movs.concat archives.collect { |a| a.movements }.flatten
1422
+ end
1423
+
1424
+ movs.select { |e| e.desc =~ / #{student}:/ }
1425
+ end
1426
+
1427
+ def student_payments(student)
1428
+ total = 0
1429
+ movs = entries.movements
1430
+ if archives = entries.get_archives
1431
+ movs.concat archives.collect { |a| a.movements }.flatten
1432
+ end
1433
+
1434
+ movs.reverse.select { |e| e.desc =~ / #{student}:/ }.collect { |e|
1435
+ total += e.value
1436
+ [e.global_id,
1437
+ [e.date, e.value_form, '']]
1438
+ } + [[nil,
1439
+ ['Reste', Account.total_form(total),
1440
+ Account.total_form(cost_student.to_f / 1000 - total)]]]
1441
+ end
1442
+
1443
+ def report_list
1444
+ entries or return []
1445
+ movs = entries.movements
1446
+ if archives = entries.get_archives
1447
+ movs.concat archives.collect { |a| a.movements }.flatten
1448
+ end
1449
+
1450
+ students.sort { |a, b|
1451
+ Persons.match_by_login_name(a).full_name <=>
1452
+ Persons.match_by_login_name(b).full_name }.collect { |s|
1453
+ total = 0
1454
+ (movs.select { |e| e.desc =~ / #{s}:/ }.collect { |e|
1455
+ total += e.value
1456
+ remark = (e.desc =~ / -- /) ? e.desc.sub(/.*-- /, '') : ''
1457
+ [e.global_id,
1458
+ [e.date,
1459
+ remark,
1460
+ e.value_form,
1461
+ ''
1462
+ ]] } +
1463
+ [[nil,
1464
+ ['Reste',
1465
+ "#{Persons.match_by_login_name(s).full_name} (#{s})",
1466
+ Account.total_form(total),
1467
+ Account.total_form(cost_student.to_f / 1000 - total)
1468
+ ]]]).reverse
1469
+ }.flatten(1)
1470
+ end
1471
+
1472
+ def create_account
1473
+ if ctype.account_base
1474
+ self.entries = Accounts.create_path(
1475
+ "#{ctype.account_base.path}::#{name}")
1476
+ else
1477
+ dputs(1) { "Trying to create account for #{name} but " +
1478
+ " #{ctype.name} has no base-account" }
1479
+ end
1480
+ end
1481
+
1482
+ def payment(secretary, student, amount, date = Date.today, oldcash = false,
1483
+ remark: '')
1484
+ log_msg :course_payment, "#{secretary.full_login} got #{amount} " +
1485
+ "of #{student.full_name} in #{name}"
1486
+ Movements.create("For student #{student.login_name}:" +
1487
+ "#{student.full_name} -- #{remark}",
1488
+ date.strftime('%Y-%m-%d'), amount.to_f / 1000,
1489
+ secretary.account_due, entries)
1490
+ if secretary.has_permission?(:admin) && oldcash
1491
+ log_msg 'course-payment', 'Oldcash - doing reverse, too'
1492
+ Movements.create("old_cash for #{student.login_name}",
1493
+ date.strftime('%Y-%m-%d'), amount.to_f / 1000,
1494
+ entries, secretary.account_due)
1495
+ end
1496
+ end
1497
+
1498
+ def transfer_student(student, new_course)
1499
+ return if !students.index(student)
1500
+ return if !new_course.entries
1501
+ return if new_course.students.index(student)
1502
+
1503
+ log_msg 'course', "Transferring #{student} from #{name} to #{new_course.name}"
1504
+ self.students = students - [student]
1505
+ new_course.students_add student
1506
+
1507
+ entries.movements.select { |m|
1508
+ m.desc =~ / #{student}:/
1509
+ }.each { |m|
1510
+ other = m.get_other_account(entries)
1511
+ if other.get_path =~ /::Paid$/
1512
+ Movements.create("Transfert of student #{student}:",
1513
+ m.date, m.get_value(entries), entries, new_course.entries)
1514
+ else
1515
+ value = m.get_value(entries)
1516
+ m.value = 0
1517
+ m.account_dst_id = new_course.entries
1518
+ m.value = value
1519
+ end
1520
+ }
1521
+ end
1522
+
1523
+ def move_payment(src, dst)
1524
+ return if !students.index(src)
1525
+ return if !students.index(dst)
1526
+
1527
+ log_msg :Course, "Transferring payments from #{src} to #{dst}"
1528
+
1529
+ movs = entries.movements
1530
+ if archives = entries.get_archives
1531
+ movs.concat archives.collect { |a| a.movements }.flatten
1532
+ end
1533
+
1534
+ p_dst = Persons.match_by_login_name(dst)
1535
+ p_src = Persons.match_by_login_name(src)
1536
+ src_dst = "#{p_src.login_name}-#{p_src.full_name} to " +
1537
+ "#{p_dst.login_name}-#{p_dst.full_name}"
1538
+ movs.select { |m|
1539
+ m.desc =~ / #{src}:/
1540
+ }.each { |m|
1541
+ other = m.get_other_account(entries)
1542
+ if other.get_path =~ /::Paid$/ ||
1543
+ other.get_path =~ /^Archive::/
1544
+ value = m.get_value(entries)
1545
+ m.desc = "Moved payment from #{src_dst}"
1546
+ Movements.create("Moved payment from #{src_dst}",
1547
+ m.date, value, entries, other)
1548
+ Movements.create("For student #{dst}:#{p_dst.full_name}",
1549
+ m.date, value, other, entries)
1550
+ else
1551
+ m.desc = "For student #{dst}:#{p_dst.full_name}"
1552
+ end
1553
+ }
1554
+ end
1555
+
1556
+ def md5_exams
1557
+ center_pre = ConfigBase.has_function?(:course_server) ?
1558
+ "#{center.login_name}_" : ''
1559
+ dputs(3) { "Fetching existing files with center -#{center_pre}-" }
1560
+ Hash[students.map { |s|
1561
+ [s.sub(/^#{center_pre}/, ''),
1562
+ Dir.glob("#{dir_exams}/#{s}/*").map { |exa_f|
1563
+ md5 = Digest::MD5.file(exa_f).hexdigest
1564
+ exa_rel = exa_f.sub(/^.*\//, '')
1565
+ dputs(3) { "Adding file #{exa_rel} with md5 #{md5}" }
1566
+ [exa_rel, md5]
1567
+ }]
1568
+ }]
1569
+ end
1570
+
1571
+ def rename(new_name)
1572
+ if new_name == name
1573
+ log_msg :Courses, "Renaming #{name} with #{new_name} is useless"
1574
+ return
1575
+ end
1576
+ log_msg :Courses, "Renaming #{name} to #{new_name}"
1577
+ dir = "#{@proxy.dir_exams}/#{name}"
1578
+ if File.exists?(dir)
1579
+ log_msg :Courses, "Moving files of #{name} to #{new_name}"
1580
+ FileUtils.mv dir, "#{@proxy.dir_exams}/#{new_name}"
1581
+ end
1582
+ self.name = new_name
1583
+ if entries
1584
+ entries.name = new_name
1585
+ end
1586
+ end
1587
+
1588
+ end