gestion 1.9.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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