schild.rb 15 KB

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