gestion 1.9.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +48 -0
- data/.project +14 -0
- data/Binaries/adduser_to_buzz +15 -0
- data/Binaries/backup +7 -0
- data/Binaries/check_gestion +8 -0
- data/Binaries/gestion.gnumail +22 -0
- data/Binaries/gestion.logrotate +34 -0
- data/Binaries/gestion.service +12 -0
- data/Binaries/gestion_update.rb +183 -0
- data/Binaries/gestion_update.service +10 -0
- data/Binaries/get_compta +11 -0
- data/Binaries/kill_gestion +16 -0
- data/Binaries/ldap/add_indexes +51 -0
- data/Binaries/ldap/backup +2 -0
- data/Binaries/ldap/install_ldap +92 -0
- data/Binaries/ldap/restore +7 -0
- data/Binaries/lib_backup +5 -0
- data/Binaries/log_scan_errors +8 -0
- data/Binaries/loop_gestion +64 -0
- data/Binaries/onetimers/sync_courses_from_compta.rb +74 -0
- data/Binaries/onetimers/transfer_cash_from_ldap_to_csv +26 -0
- data/Binaries/reboot +5 -0
- data/Binaries/restore +3 -0
- data/Binaries/restore_do +22 -0
- data/Binaries/sort_events +31 -0
- data/Binaries/start_gestion +18 -0
- data/Binaries/swipe_gestion +18 -0
- data/Binaries/update_africompta +21 -0
- data/Binaries/update_users +3 -0
- data/Diplomas.src/accredited.odg +0 -0
- data/Diplomas.src/diploma.odg +0 -0
- data/Diplomas.src/label.odg +0 -0
- data/Diplomas.src/presence_sheet.ods +0 -0
- data/Diplomas.src/presence_sheet_small.ods +0 -0
- data/Diplomas.src/student_card.odg +0 -0
- data/Doc/130514-it-ideas.odt +0 -0
- data/Doc/Compta-cash.mm +179 -0
- data/Doc/General.odt +0 -0
- data/Entities/AccessGroups.rb +117 -0
- data/Entities/Activity.rb +178 -0
- data/Entities/ChatMsg.rb +142 -0
- data/Entities/Classroom.rb +11 -0
- data/Entities/Client.rb +19 -0
- data/Entities/Computer.rb +21 -0
- data/Entities/ConfigBase.rb +280 -0
- data/Entities/Course.rb +1588 -0
- data/Entities/CourseType.rb +171 -0
- data/Entities/DFiles.rb +466 -0
- data/Entities/FilesManage.rb +226 -0
- data/Entities/Grade.rb +186 -0
- data/Entities/Internet.rb +300 -0
- data/Entities/Netdev.rb +10 -0
- data/Entities/Person.rb +1175 -0
- data/Entities/Plug.rb +98 -0
- data/Entities/Quiz.rb +33 -0
- data/Entities/Recharges.rb +37 -0
- data/Entities/Report.rb +136 -0
- data/Entities/Room.rb +12 -0
- data/Entities/SMS.rb +30 -0
- data/Entities/ScheduleType.rb +33 -0
- data/Entities/Share.rb +120 -0
- data/Entities/Task.rb +51 -0
- data/Entities/Ticket.rb +72 -0
- data/Entities/Usage.rb +143 -0
- data/Entities/Worker.rb +29 -0
- data/Files/apache-profeda.conf +36 -0
- data/Files/label.erb +121 -0
- data/Files/label_notfound.erb +64 -0
- data/Files/label_notpassed.erb +84 -0
- data/Files/mobileinfo.erb +115 -0
- data/Files/smb.conf +333 -0
- data/Files/timetable.html +36 -0
- data/Files/timetable.js +239 -0
- data/Gemfile +12 -0
- data/Gemfile.dev +12 -0
- data/Gemfile.dev.lock +127 -0
- data/Gemfile.lock +127 -0
- data/Gemfile.prod +8 -0
- data/Gestion +35 -0
- data/Gestion.rb +220 -0
- data/INSTALL +40 -0
- data/Images/connection.xcf +0 -0
- data/Images/connection_no.png +0 -0
- data/Images/connection_wait.png +0 -0
- data/Images/connection_yes.png +0 -0
- data/Paths/Exas.rb +13 -0
- data/Paths/Files.rb +19 -0
- data/Paths/GetDiplomas.rb +20 -0
- data/Paths/Info.rb +114 -0
- data/Paths/Label.rb +187 -0
- data/Paths/MobileInfo.rb +19 -0
- data/Paths/internetCash.rb +34 -0
- data/Paths/internetWifi.rb +54 -0
- data/README.md +60 -0
- data/Rakefile +13 -0
- data/TODO +1391 -0
- data/Test/.gitignore +3 -0
- data/Test/Diplomas/base_gestion.odt +0 -0
- data/Test/Diplomas/base_report.odt +0 -0
- data/Test/Diplomas/carte_etudiant.odg +0 -0
- data/Test/Diplomas/exam_language.odt +0 -0
- data/Test/Diplomas/label.odg +0 -0
- data/Test/Diplomas/presence_sheet.ods +0 -0
- data/Test/Diplomas/presence_sheet_small.ods +0 -0
- data/Test/Diplomas/student_card.odg +0 -0
- data/Test/Manual/testMerge +18 -0
- data/Test/config_test.yaml +26 -0
- data/Test/db.testGestion +0 -0
- data/Test/dfiles/descs/avg-rescue.desc +10 -0
- data/Test/dfiles/descs/avg.desc +8 -0
- data/Test/dfiles/descs/driver.desc +8 -0
- data/Test/dfiles/descs/linuxmint.desc +7 -0
- data/Test/dfiles/files/avg-160203.exe +1 -0
- data/Test/dfiles/files/avg.iso +1 -0
- data/Test/dfiles/files/driver.exe +1 -0
- data/Test/dfiles/index_post.html +3 -0
- data/Test/dfiles/index_pre.html +8 -0
- data/Test/dfiles/priorities +5 -0
- data/Test/ge_activity.rb +124 -0
- data/Test/ge_chat.rb +106 -0
- data/Test/ge_compta.rb +67 -0
- data/Test/ge_configbase.rb +54 -0
- data/Test/ge_course.rb +1114 -0
- data/Test/ge_dfiles.rb +121 -0
- data/Test/ge_filesmanage.rb +180 -0
- data/Test/ge_info.rb +27 -0
- data/Test/ge_internet.rb +246 -0
- data/Test/ge_login.rb +55 -0
- data/Test/ge_person.rb +373 -0
- data/Test/ge_qvinfo.rb +28 -0
- data/Test/ge_report.rb +97 -0
- data/Test/ge_share.rb +27 -0
- data/Test/ge_sms.rb +34 -0
- data/Test/ge_tasks.rb +19 -0
- data/Test/ge_usage.rb +168 -0
- data/Test/ge_view.rb +46 -0
- data/Test/multiconf-captive +29 -0
- data/Test/test.conf +7 -0
- data/Test/test.rb +49 -0
- data/Test/test_bytes.png +0 -0
- data/VERSION +140 -0
- data/Views/Admin/Backup.rb +91 -0
- data/Views/Admin/Configuration.rb +44 -0
- data/Views/Admin/Credit.rb +32 -0
- data/Views/Admin/FilesManage.rb +219 -0
- data/Views/Admin/Function.rb +39 -0
- data/Views/Admin/Power.rb +49 -0
- data/Views/Admin/Printer.rb +37 -0
- data/Views/Admin/Server.rb +252 -0
- data/Views/Admin/Tabs.rb +5 -0
- data/Views/Admin/Update.rb +73 -0
- data/Views/Admin/UpdateSystem.rb +26 -0
- data/Views/Cashbox/Activity.rb +191 -0
- data/Views/Cashbox/Course.rb +141 -0
- data/Views/Cashbox/Credit.rb +79 -0
- data/Views/Cashbox/Report.rb +115 -0
- data/Views/Cashbox/Service.rb +105 -0
- data/Views/Cashbox/Tabs.rb +10 -0
- data/Views/Compta/Accounts.rb +36 -0
- data/Views/Compta/Course.rb +96 -0
- data/Views/Compta/Show.rb +6 -0
- data/Views/Compta/Transfer.rb +66 -0
- data/Views/Course/Diploma.rb +203 -0
- data/Views/Course/Grade.rb +401 -0
- data/Views/Course/Modify.rb +447 -0
- data/Views/Course/Print.rb +94 -0
- data/Views/Course/Responsible.rb +44 -0
- data/Views/Course/Stats.rb +76 -0
- data/Views/Course/Students.rb +92 -0
- data/Views/Course/Tabs.rb +220 -0
- data/Views/Internet/Access.rb +134 -0
- data/Views/Internet/ClassEdit.rb +24 -0
- data/Views/Internet/ClassUsers.rb +81 -0
- data/Views/Internet/Config.rb +32 -0
- data/Views/Internet/Mobile.rb +213 -0
- data/Views/Internet/Recharges.rb +49 -0
- data/Views/Internet/Tabs.rb +6 -0
- data/Views/Inventory/Computer.rb +24 -0
- data/Views/Inventory/Room.rb +18 -0
- data/Views/Inventory/Tabs.rb +9 -0
- data/Views/Inventory/TicketClosed.rb +7 -0
- data/Views/Inventory/TicketOpen.rb +23 -0
- data/Views/Library/Person.rb +36 -0
- data/Views/Library/Tabs.rb +7 -0
- data/Views/Network/Block.rb +87 -0
- data/Views/Network/Netdevs.rb +21 -0
- data/Views/Network/Restriction.rb +37 -0
- data/Views/Network/Share.rb +167 -0
- data/Views/Network/Tables.rb +28 -0
- data/Views/Network/Tabs.rb +6 -0
- data/Views/Person/Admin.rb +99 -0
- data/Views/Person/Center.rb +48 -0
- data/Views/Person/Course.rb +72 -0
- data/Views/Person/Modify.rb +153 -0
- data/Views/Person/Tabs.rb +162 -0
- data/Views/Report/ComptaExecutive.rb +221 -0
- data/Views/Report/ComptaFlat.rb +79 -0
- data/Views/Report/ReportCourse.rb +47 -0
- data/Views/Report/Tabs.rb +8 -0
- data/Views/Report/Usage.rb +52 -0
- data/Views/Report/UsageCases.rb +59 -0
- data/Views/Self/Cash.rb +67 -0
- data/Views/Self/Chat.rb +55 -0
- data/Views/Self/Concours.rb +109 -0
- data/Views/Self/Email.rb +34 -0
- data/Views/Self/Internet.rb +255 -0
- data/Views/Self/Results.rb +17 -0
- data/Views/Self/Services.rb +85 -0
- data/Views/Self/Show.rb +47 -0
- data/Views/Self/Tabs.rb +5 -0
- data/Views/Special/DFileEdit.rb +13 -0
- data/Views/Special/PlugEdit.rb +56 -0
- data/Views/Special/Tabs.rb +6 -0
- data/Views/Special/Vnc.rb +39 -0
- data/Views/Task/Client.rb +21 -0
- data/Views/Task/Edit.rb +33 -0
- data/Views/Task/List.rb +55 -0
- data/Views/Task/Tabs.rb +9 -0
- data/Views/Task/Worker.rb +30 -0
- data/Views/Template/Activity.rb +33 -0
- data/Views/Template/CourseType.rb +63 -0
- data/Views/Template/ScheduleType.rb +29 -0
- data/Views/Template/Tabs.rb +5 -0
- data/Views/Welcome.rb +121 -0
- data/config.yaml.default +36 -0
- data/po/Gestion-ar.po +2356 -0
- data/po/Gestion-en.mo +0 -0
- data/po/Gestion-en.po +4363 -0
- data/po/Gestion-fr.mo +0 -0
- data/po/Gestion-fr.po +4345 -0
- data/po/traduction-ar.rtf +76 -0
- metadata +381 -0
data/Entities/Course.rb
ADDED
@@ -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
|