schild.rb 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. require 'schild/version'
  2. require 'sequel'
  3. # Das Schild Modul, das alle Klassen für die Datenbankanbindung bereitstellt
  4. module Schild
  5. # ist die Datenbank-Verbindung. Alle Daten können über diese Konstante abgerufen werden
  6. DB = Sequel.connect("#{ENV['S_ADAPTER']}://#{ENV['S_HOST']}/#{ENV['S_DB']}?user=#{ENV['S_USER']}&password=#{ENV['S_PASSWORD']}&zeroDateTimeBehavior=convertToNull")
  7. # Stellt die Schüler-Tabelle samt Assoziationen bereit.
  8. class Schueler < Sequel::Model(:schueler)
  9. many_to_one :fachklasse, :class => :Fachklasse, :key => :Fachklasse_ID
  10. one_to_many :abschnitte, :class => :Abschnitt
  11. one_to_one :bk_abschluss, :class => :BKAbschluss
  12. one_to_many :bk_abschluss_leistungen, :class => :BKAbschlussFaecher
  13. one_to_one :abi_abschluss, :class => :AbiAbschluss
  14. one_to_many :abi_abschluss_leistungen, :class => :AbiAbschlussFaecher
  15. one_to_many :vermerke, :class => :Vermerke
  16. one_to_one :schuelerfoto, :class => :Schuelerfotos
  17. end
  18. # Dient als Assoziation für Schüler und deren Klassenbezeichnung etc.
  19. class Fachklasse < Sequel::Model(:eigeneschule_fachklassen)
  20. one_to_many :schueler
  21. end
  22. # Assoziation für Lehrer, hauptsächlich für Klassenlehrer
  23. class Klassenlehrer < Sequel::Model(:k_lehrer)
  24. one_to_one :abschnitt, :primary_key=>:Kuerzel, :key=>:KlassenLehrer
  25. end
  26. # Ist die Assoziation, die Halbjahre, sog. Abschnitte zurückgibt.
  27. class Abschnitt < Sequel::Model(:schuelerlernabschnittsdaten)
  28. many_to_one :schueler, :class => :Schueler, :key => :Schueler_ID
  29. one_to_many :noten, :class => :Noten
  30. many_to_one :klassenlehrer, :class => :Klassenlehrer, :primary_key=>:Kuerzel, :key=>:KlassenLehrer
  31. end
  32. # Assoziation für Noten
  33. class Noten < Sequel::Model(:schuelerleistungsdaten)
  34. many_to_one :abschnitt, :class => :Abschnitt, :key => :Abschnitt_ID
  35. many_to_one :fach, :class => :Faecher, :key => :Fach_ID
  36. end
  37. # Assoziation für Fächer
  38. class Faecher < Sequel::Model(:eigeneschule_faecher)
  39. #siehe abi_...
  40. one_to_one :noten
  41. one_to_many :abi_abschluss_leistungen
  42. one_to_one :sprachenfolge, :class => :Sprachenfolge, :key => :Fach_ID
  43. end
  44. # Assoziation für BK-Abschluss des Schülers
  45. class BKAbschluss < Sequel::Model(:schuelerbkabschluss)
  46. one_to_one :schueler
  47. end
  48. # Assoziation für die Prüfungsfächer des Schülers
  49. class BKAbschlussFaecher < Sequel::Model(:schuelerbkfaecher)
  50. many_to_one :schueler
  51. end
  52. # Assoziation für Abi-Abschluss des Schülers
  53. class AbiAbschluss < Sequel::Model(:schuelerabitur)
  54. one_to_one :schueler
  55. end
  56. # Assoziation für die Abifächer des Schülers
  57. class AbiAbschlussFaecher < Sequel::Model(:schuelerabifaecher)
  58. many_to_one :schueler
  59. many_to_one :fach, :class => :Faecher, :key => :Fach_ID
  60. end
  61. # Assoziation für die bisher erreichten Sprachniveaus
  62. class Sprachenfolge < Sequel::Model(:schuelersprachenfolge)
  63. one_to_one :Faecher
  64. end
  65. # Vermerke von Schülern
  66. class Vermerke < Sequel::Model(:schuelervermerke)
  67. many_to_one :Schueler
  68. end
  69. # Schülerfotos als jpg
  70. class Schuelerfotos < Sequel::Model(:schuelerfotos)
  71. one_to_one :schueler
  72. end
  73. # Schul-Tabelle
  74. class Schule < Sequel::Model(:eigeneschule)
  75. end
  76. # Tabelle für Schild-Nutzer
  77. class Nutzer < Sequel::Model(:users)
  78. end
  79. end
  80. module SchildErweitert
  81. # erst Ruby 2.1.0 macht include zu einer public-Methode
  82. if Module.private_method_defined? :include
  83. class Module
  84. public :include
  85. end
  86. end
  87. # String und Symbol werden um snake_case ergänzt, das die Schild-Tabellen umbenennt
  88. # Legacy-Methoden aus alten Schild-Versionen wird teilweise auch unterstützt.
  89. module CoreExtensions
  90. module String
  91. def snake_case
  92. return downcase if match(/\A[A-Z]+\z/)
  93. gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
  94. gsub(/([a-z])([A-Z])/, '\1_\2').
  95. downcase
  96. end
  97. end
  98. module Symbol
  99. def snake_case
  100. to_s.snake_case
  101. end
  102. end
  103. module Schueler
  104. def entlassart
  105. return self.entlass_art if self.respond_to?(:entlass_art)
  106. end
  107. end
  108. module Fachklasse
  109. def dqr_niveau
  110. return self.DQR_Niveau if self.respond_to?(:DQR_Niveau)
  111. "Alte Schild-Version ohne DQR-Niveau"
  112. end
  113. end
  114. end
  115. # Schild hat teilweise nil in DB-Feldern. SchildTypeSaver gibt entweder einen
  116. # Leer-String zurück ("") oder bei strftime das 1899 Datum zurück.
  117. module SchildTypeSaver
  118. Symbol.include SchildErweitert::CoreExtensions::Symbol
  119. String.include CoreExtensions::String
  120. # bei include wird für jede Spalte in der Schild-Tabelle eine Ersatzmethode
  121. # erstellt, die bei nil ein Null-Objekt erstellt.
  122. def self.included(klass)
  123. klass.columns.each do |column|
  124. name = column.snake_case
  125. MethodLogger::Methods.add(klass, name)
  126. # allow_nil ist als Argument optional und lässt bei +true+ alle Ergebnisse durch
  127. define_method(("_"+name.to_s).to_sym) {public_send(column)}
  128. define_method(name) do |allow_nil=false|
  129. ret = public_send(column)
  130. if allow_nil || ret
  131. ret = ret.strip if ret.class == String
  132. ret
  133. else
  134. create_null_object(klass, column)
  135. end
  136. end
  137. end
  138. end
  139. def create_null_object(klass, column)
  140. k = Schild::DB.schema_type_class(klass.db_schema[column][:type])
  141. if k.class == Array
  142. # Sequel stellt :datetime als [Time, DateTime] dar, deswegen die Abfrage nach Array
  143. # Schild verwendet Time Objekte, wir machen das auch
  144. Time.new(1899)
  145. elsif k == Integer
  146. 0
  147. elsif k == Float
  148. 0.0
  149. else
  150. # alle anderen types werden als Klasse zurückgegeben
  151. k.new
  152. end
  153. end
  154. end
  155. # Halten wir Protokoll zu den erstellten Methoden
  156. # Ist brauchbar, wenn man z.B. noch extremer als der SchildTypeSaver arbeiten möchte
  157. module MethodLogger
  158. class Methods
  159. @@accessor_methods = {}
  160. def self.add(klass, meth)
  161. @@accessor_methods[klass] ||= []
  162. @@accessor_methods[klass] << meth
  163. end
  164. def self.list(klass)
  165. @@accessor_methods[klass]
  166. end
  167. end
  168. end
  169. # Mixin für Notenbezeichnungen
  170. module NotenHelfer
  171. # Noten können als Punkte abgerufen werden:
  172. # note[5] => "4-"
  173. # oder auch andersherum: note.index("4-") => 5
  174. @note = %w[6 5- 5 5+ 4- 4 4+ 3- 3 3+ 2- 2 2+ 1- 1 1+]
  175. def self.punkte_aus_note(note)
  176. return if note.nil?
  177. @note.index(note)
  178. end
  179. def self.note_aus_punkten(punkte)
  180. return unless punkte && punkte.to_i.between?(0,15)
  181. return if (punkte.class == String) && punkte.empty?
  182. @note[punkte.to_i]
  183. end
  184. # Notenbezeichnung als String
  185. def note_s(ziffer)
  186. case ziffer
  187. when "1", "1+", "1-"
  188. "sehr gut"
  189. when "2", "2+", "2-"
  190. "gut"
  191. when "3", "3+", "3-"
  192. "befriedigend"
  193. when "4", "4+", "4-"
  194. "ausreichend"
  195. when "5", "5+", "5-"
  196. "mangelhaft"
  197. when "6"
  198. "ungenügend"
  199. when 'NB'
  200. "––––––"
  201. when "E1"
  202. "mit besonderem Erfolg teilgenommen"
  203. when "E2"
  204. "mit Erfolg teilgenommen"
  205. when 'E3'
  206. "teilgenommen"
  207. end
  208. end
  209. end
  210. # Klassen sind Konstanten. Deswegen alle auslesen, die Klassen behalten und
  211. # dynamisch neue Klassen mit gleichem Namen erstellen.
  212. # Automatisch SchildTypeSaver einbinden.
  213. #
  214. # Sollen zusätzliche Methoden eingebunden werden, muss - wie unten Schueler
  215. # und andere Klassen - die neu erstelle Klasse gepatcht werden.
  216. # Die alten Methoden bleiben erhalten, d.h. auch die TypeSaver-Methoden.
  217. Schild.constants.map {|name| Schild.const_get(name)}.select {|o| o.is_a?(Class)}.each do |klass|
  218. name = Schild.const_get(klass.to_s).name.split("::").last
  219. klass = Class.new(klass) do
  220. include SchildTypeSaver
  221. end
  222. name = const_set(name, klass)
  223. end
  224. Fachklasse.include CoreExtensions::Fachklasse
  225. Schueler.include CoreExtensions::Schueler
  226. # Stellt die Schüler-Tabelle samt Assoziationen bereit.
  227. class Schueler
  228. # gibt das z.Zt. aktuelle Halbjahr zurück.
  229. def akt_halbjahr
  230. abschnitte.last
  231. end
  232. # gibt aus +jahr+ das Halbjahr +1+ oder +2+ zurück.
  233. def halbjahr(jahr, abschnitt)
  234. abschnitte_dataset.where(:jahr => jahr, :abschnitt => abschnitt).first
  235. end
  236. # gibt +Herr+ oder +Frau+ als Anrede für Schüler zurück.
  237. def anrede
  238. self.geschlecht == 3 ? "Herr" : "Frau"
  239. end
  240. # gibt die passende Bezeichnung zurück Schüler
  241. def schueler_in
  242. self.geschlecht == 3 ? "Schüler" : "Schülerin"
  243. end
  244. # gibt die passende Bezeichnung zurück Studierende
  245. def studierende_r
  246. self.geschlecht == 3 ? "Studierender" : "Studierende"
  247. end
  248. # gibt die jeweilige Berufsbezeichnung nach Geschlecht zurück.
  249. def berufsbezeichnung_mw
  250. return "Keine Fachklasse zugeordnet" if self.fachklasse.nil?
  251. self.geschlecht == 3 ? self.fachklasse.bezeichnung : self.fachklasse.beschreibung_w
  252. end
  253. # gibt +true+ zurück, wenn Schüler volljährig.
  254. def volljaehrig?
  255. self.volljaehrig == "+"
  256. end
  257. # gibt an, ob der Schüler zu einem Zeitpunkt *datum* volljährig war.
  258. def volljaehrig_bei?(datum)
  259. return false if datum.nil? || self.Geburtsdatum.nil?
  260. geb, datum = self.Geburtsdatum.to_date, datum.to_date
  261. (datum.year - geb.year - ((datum.month > geb.month || (datum.month == geb.month && datum.day >= geb.day)) ? 0 : 1)) >= 18
  262. end
  263. # fragt ab, ob in Schild ein Foto als hinterlegt eingetragen ist.
  264. def foto_vorhanden?
  265. self.foto_vorhanden == "+"
  266. end
  267. # gibt, wenn vorhanden, ein Foto als jpg-String zurück, ansonsten nil.
  268. def foto
  269. self.foto_vorhanden? ? self.schuelerfoto.foto : nil
  270. end
  271. end
  272. # Ist die Assoziation, die Halbjahre, sog. Abschnitte zurückgibt.
  273. class Abschnitt
  274. dataset_module do
  275. # filtert den Datensatz nach Jahr
  276. def jahr(i)
  277. where(:Jahr => i)
  278. end
  279. # filtert den Datensatz nach Halbjahr
  280. def halbjahr(i,j)
  281. jahr(i).where(:Abschnitt => j)
  282. end
  283. # filtert und gibt den Datensatz als Abschnitt des aktuellen Halbjahrs zurück
  284. def akt_halbjahr
  285. halbjahr(Time.new.year-1, 1).first
  286. end
  287. end
  288. # Hilfsmethode für die folgenden Methoden
  289. def faecher_nach_id(id)
  290. noten.select{ |n| n.fach.Fachgruppe_ID == id && n.AufZeugnis == '+' }.sort_by{ |n| n.fach.SortierungS2 }
  291. end
  292. # wählt alle berufsübergreifenden Fächer des gewählten Schülers in angegeben Halbjahr.
  293. def berufsuebergreifend
  294. faecher_nach_id 10
  295. end
  296. # wählt alle berufsbezogenen Fächer des gewählten Schülers in angegeben Halbjahr.
  297. def berufsbezogen
  298. faecher_nach_id 20
  299. end
  300. # wählt alle Fächer des Differenzierungsbreichs des gewählten Schülers in angegeben Halbjahr.
  301. def differenzierungsbereich
  302. faecher_nach_id 30
  303. end
  304. # wählt alle Fächergruppen aus.
  305. def faechergruppen
  306. [berufsuebergreifend, berufsbezogen, differenzierungsbereich]
  307. end
  308. # gibt den Namen des Klassenlehrers mit gekürztem Vornamen.
  309. def v_name_klassenlehrer
  310. return "Kein Klassenlehrer angelegt" if klassenlehrer.nil?
  311. v = klassenlehrer.vorname
  312. n = klassenlehrer.nachname
  313. "#{v[0]}. #{n}"
  314. end
  315. # gibt "Klassenlehrer" entsprechend Geschlecht zurück
  316. def klassenlehrer_in
  317. return "Kein Klassenlehrer angelegt" if klassenlehrer.nil?
  318. klassenlehrer.geschlecht == "3" ? "Klassenlehrer" : "Klassenlehrerin"
  319. end
  320. # gibt das aktuelle Schuljahr als String im Format "2014/15" zurück.
  321. def schuljahr
  322. jahr = self.jahr
  323. "#{jahr}/#{jahr-1999}"
  324. end
  325. end
  326. # Assoziation für Noten
  327. class Noten
  328. include NotenHelfer
  329. # note in String umwandeln
  330. def note
  331. note_s self.noten_krz
  332. end
  333. # Bezeichnung des Fachs
  334. def bezeichnung
  335. fach.bezeichnung
  336. end
  337. # Die Fachgruppen ID des Fachs
  338. def fachgruppe_ID
  339. fach.fachgruppe_id
  340. end
  341. end
  342. # Assoziation für BK-Abschlussdaten
  343. class BKAbschluss
  344. # Ist der Schüler zugelassen?
  345. def zulassung?
  346. self.Zulassung == "+"
  347. end
  348. # Ist der Schüler für den Berufsabschluss zugelassen?
  349. def zulassung_ba?
  350. self.ZulassungBA == "+"
  351. end
  352. # Hat der Schüler den Berufsabschluss bestanden?
  353. def bestanden_ba?
  354. self.BestandenBA == "+"
  355. end
  356. end
  357. # Assoziation für die jeweiligen BK-Prüfungsfächer
  358. class BKAbschlussFaecher
  359. include NotenHelfer
  360. # Wurde das Fach schriftlich geprüft?
  361. def fach_schriftlich?
  362. self.FachSchriftlich == "+"
  363. end
  364. # Wurde das Fach mündlich geprüft?
  365. def fach_muendlich?
  366. self.MdlPruefung == "+"
  367. end
  368. def note(notenart=:note_abschluss_ba)
  369. note_s send(notenart)
  370. end
  371. end
  372. # Assoziation für die jeweiligen Abi-Prüfungsfächer
  373. class AbiAbschlussFaecher
  374. include NotenHelfer
  375. def note(notenart)
  376. note_s send(notenart)
  377. end
  378. end
  379. # Schul-Tabelle mit vereinfachtem Zugriff auf Datenfelder mittel class-Methoden
  380. class Schule
  381. # gibt die Schulnummer zurück
  382. def self.schulnummer
  383. self.first.schul_nr
  384. end
  385. # gibt den Namen des Schulleiters als V. Name zurück
  386. def self.v_name_schulleiter
  387. "#{self.first.schulleiter_vorname[0]}. #{self.first.schulleiter_name}"
  388. end
  389. # gibt die männliche bzw. weibliche Form des Schulleiters zurück
  390. def self.schulleiter_in
  391. self.first.schulleiter_geschlecht == 3 ? "Schulleiter" : "Schulleiterin"
  392. end
  393. # gibt den Ort der Schule zurück
  394. def self.ort
  395. self.first.ort
  396. end
  397. end
  398. # Tabelle der Schuld-Benutzer zum Abgleichen der Daten
  399. class Nutzer
  400. # der Nutzername
  401. def name
  402. self.us_name
  403. end
  404. # der Login-Name des Nutzers
  405. def login
  406. self.us_login_name
  407. end
  408. # das Passwort des Nutzers
  409. def passwort
  410. self.us_password
  411. end
  412. alias :password :passwort
  413. # prüft, ob das angegebene Passwort mit dem gespeicherten Passwort übereinstimmt
  414. def passwort?(passwort='')
  415. crypt(passwort) == self.passwort
  416. end
  417. alias :password? :passwort?
  418. # ver- bzw. entschlüsselt einen String mit dem Schild-Passwortalgorithmus
  419. def crypt(passwort)
  420. passwort.codepoints.map{|c| ((c/16)*32+15-c).chr}.join('')
  421. end
  422. end
  423. end