#! /usr/bin/perl # Copyright 2001-2007 Leslie Richardson # This file is part of Open Admin for Schools. # Open Admin for Schools is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # Outline: 1) Get passed variables for start date, end date and withdrawn students # 2) Read each student from student table (ordered by homeroom/grade,lastname, # firstname), and check for matching records in the attend table. # 3) If no records, print perfect attendance report. # Updated: (March/06): Display withdrawn students and allow to print # selected profiles for those students. Also add subject specific # printing where there is subject based attendance. use DBI; use CGI; use Date::Business; my %lex = ('Attendance Profiles' => 'Attendance Profiles', 'Missing Start Date or End Date' => 'Missing Start Date or End Date', 'Attendance' => 'Attendance', 'Cannot open tex file' => 'Cannot open tex file', 'Enrollment Changes' => 'Enrollment Changes', 'Error: Missing grade for student' => 'Error: Missing grade for student', 'Periods Per Day not defined for Grade' => 'Periods Per Day not defined for Grade', 'in global configuration for student' => 'in global configuration for student', 'Perfect Attendance!' => 'Perfect Attendance!', 'View/Download' => 'View/Download', 'View Log File' => 'View Log File', 'Total' => 'Total', 'Absent' => 'Absent', 'Late' => 'Late', 'Excused' => 'Excused', 'Unexcused' => 'Unexcused', 'times' => 'times', 'day(s)' => 'day(s)', 'Main' => 'Main', 'Name' => 'Name', 'Select' => 'Select', 'Withdrawn Students' => 'Withdrawn Students', 'Date format Error' => 'Date format Error', 'Subject' => 'Subject', 'Lastname, Firstname' => 'Lastname, Firstname', 'Homeroom, Lastname, Firstname' => 'Homeroom, Lastname, Firstname', 'Grade, Lastname, Firstname' => 'Grade, Lastname, Firstname', 'Sort by' => 'Sort by', 'Select' => 'Select', 'Grade' => 'Grade', 'Homeroom' => 'Homeroom', 'Start Date' => 'Start Date', 'End Date' => 'End Date', 'Show' => 'Show', 'Continue' => 'Continue', 'No Students Found' => 'No Students Found', 'School Days' => 'School Days', 'Unknown reason' => 'Unknown reason', 'Period' => 'Period', ); my $self = "rptattprof.pl"; unless (require "../etc/admin.conf"){ print "Cannot read admin.conf!"; die "Cannot read admin.conf!"; } my $maxlines = 28; my $shortname = "attprofile$$"; my $filename = "$shortname.tex"; # Set AM/PM Hash for use converting 2 period day to AM/PM %ampm = ( 1 => 'AM', 2 => 'PM'); my $dsn = "DBI:$dbtype:dbname=$dbase"; my $dbh = DBI->connect($dsn,$user,$password); my @month = ('','January','February','March','April','May','June', 'July','August','September','October','November','December'); my $q = new CGI; print $q->header; my %arr = $q->Vars; my $wdselectflag; if ( $arr{wdselectflag} ) { # We have selected withdrawn students to print. $wdselectflag = 1; delete $arr{wdselectflag}; } my $sortorder = "homeroom, lastname, firstname"; if ( $arr{sortorder} eq $lex{'Lastname, Firstname'} ) { $sortorder = "lastname, firstname"; } elsif ( $arr{sortorder} eq $lex{'Grade, Lastname, Firstname'} ) { $sortorder = "grade,lastname, firstname"; } elsif ( $arr{sortorder} eq $lex{'Homeroom, Lastname, Firstname'} ) { $sortorder = "homeroom, lastname, firstname"; } if ( $arr{sortorder} ) { delete $arr{sortorder}; } my $select; if ( $arr{group} ) { if ( $arr{select} eq $lex{Grade} ) { # Find this grade; my $grp = $dbh->quote( $arr{group} ); $select = "where grade = $grp"; } elsif ( $arr{select} eq $lex{Homeroom} ) { my $grp = $dbh->quote( $arr{group} ); $select = "where homeroom = $grp"; } } # print page header print "$doctype\n",$lex{'Attendance Profiles'},"\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "$chartype\n[ ". $lex{Main}. " | \n"; print "". $lex{Attendance}," ]\n"; if ( not $arr{startflag} ) { printStartPage(); # die at end... } else { delete $arr{startflag}; } # Passed variables: start and end dates for reporting period. if ( not $arr{startdate} or not $arr{enddate} ) { print '

',$lex{'Missing Start Date or End Date'},"

\n"; print "\n"; die; } my ($startdate, $enddate); # Parse startdate. if ( $arr{startdate} =~ /-/) { my ($yr, $mo, $da) = split /-/, $arr{startdate}; $startdate = "$yr$mo$da"; } elsif ( length( $arr{startdate} ) == 6 ) { $startdate = '20'. $arr{startdate}; } elsif ( length( $arr{startdate} ) == 8 ) { $startdate = $arr{startdate}; } else { print "$arr{startdate} - ". $lex{'Date format Error'}. "
\n"; print "\n"; die; } delete $arr{startdate}; # Parse enddate. if ( $arr{enddate} =~ /-/) { # if 2007-06-06 format my ($yr, $mo, $da) = split /-/, $arr{enddate}; $enddate = "$yr$mo$da"; } elsif ( length( $arr{enddate} ) == 6 ) { $enddate = '20'. $arr{enddate}; } elsif ( length( $arr{enddate} ) == 8 ) { $enddate = $arr{enddate}; } else { print "$arr{enddate} - ". $lex{'Date format Error'}. "
\n"; print "\n"; die; } delete $arr{enddate}; # If checkbox for withdrawn, allow selection of students to print. if ( $arr{withdrawn} ) { selectWithdrawn( $startdate, $enddate ); # die at end; } # Now create a hash to store student numbers of students to print (if # not selected from withdrawn). if (not $wdselectflag ) { # we are doing a normal print run of active students my $sth = $dbh->prepare("select studnum from student $select order by $sortorder"); $sth->execute; if ($DBI::errstr) { print $DBI::errstr; die $DBI::errstr; } while (my $studnum = $sth->fetchrow) { push @students, $studnum; } } else { # we have a selected withdrawn student group to print foreach my $sn (keys %arr) { push @students, $sn; } } if ( @students ) { # We have students to print; setup LaTeX file. open(TEX,">$filename") || die $lex{'Cannot open tex file'}; print TEX "\\documentclass[12pt,letterpaper]{article} \\usepackage{array, colortbl} \\usepackage{multicol} \\pagestyle{empty} \\setlength{\\textwidth}{7in} \\setlength{\\textheight}{10in} \\setlength{\\hoffset}{-1in} \\setlength{\\voffset}{-1.4in} \\setlength{\\extrarowheight}{2pt} \\setlength{\\parindent}{0pt}\n"; print TEX "\\begin{document}\n"; } else { print "". $lex{'No Students Found'}. "\n"; die; } my $sth = $dbh->prepare("select lastname, firstname, grade, homeroom from studentall where studnum = ?"); foreach my $studnum (@students) { # get rest of student data. $sth->execute($studnum); if ($DBI::errstr) { print $DBI::errstr; die $DBI::errstr; } my ($lastname, $firstname, $grade, $homeroom) = $sth->fetchrow; my $tardytot = 0; my $absenttot = 0; my $tardyu = 0; my $tardye = 0; my $absentu = 0; my $absente = 0; # Done once per loop to carry updated start date from later enrollment. my $loopstartdate = $startdate; my $loopenddate = $enddate; # Fetch Enrollment/Withdrawal Changes my $sth3 = $dbh->prepare("select * from transfer where studnum = '$studnum' and to_days(date) >= to_days('$loopstartdate') and to_days(date) <= to_days('$loopenddate') order by date desc"); $sth3->execute; if ($DBI::errstr) { print $DBI::errstr; die $DBI::errstr; } while (@entryrecord = $sth3->fetchrow){ foreach $rec (@entryrecord){ $rec =~ s/:/-/g; $rec =~ s/&/\\&/g; $rec =~ s/#//g; $rec =~ s/$//g; } push @entries, "$entryrecord[2]:$entryrecord[3]:$entryrecord[4]"; if ($entryrecord[3] eq "enrol" or $entryrecord[3] eq "re-enrol"){ # Change the start date, format in YYYY-MM-DD ($year,$mo,$day) = split /-/,$entryrecord[2]; if (length($day)<2) { $day = "0".$day;} if (length($mo)<2) { $mo = "0".$mo; } $loopstartdate = "$year$mo$day"; } } # End of Entry/Withdrawal Records # Now figure out the number of schools days for this student my $end = new Date::Business(DATE=>$loopenddate); my $start = new Date::Business(DATE=>$loopstartdate); $schooldays = $end->diffb($start,'prev','next'); $schooldays++; # Increment since we want number of days, not just diff. # These two settings are REQUIRED to get the elapsed day calculations right. # I did a lot of head scratching on this one...(used lots of paper too...) # Now find number of holidays during this same period. my $sth4 = $dbh->prepare("select * from dates where to_days(date) >= to_days('$loopstartdate') and to_days(date) <= to_days('$loopenddate')"); $sth4->execute; if ($DBI::errstr) { print $DBI::errstr; die; } my $holidays = $sth4->rows; my $schooldays = $schooldays - $holidays; # Now format start and end dates. $year = substr($loopstartdate,0,4); $mo = substr($loopstartdate,4,2); $day = substr($loopstartdate,-2,2); $fmtstartdate = "$month[$mo] $day, $year"; my $year = substr($loopenddate,0,4); my $mo = substr($loopenddate,4,2); my $day = substr($loopenddate,-2,2); my $fmtenddate = "$month[$mo] $day, $year"; # Setup to fetch Attendance Records my $sth1 = $dbh->prepare("select absdate, reason, period, subjsec from attend where studentid = '$studnum' and to_days(absdate) >= to_days('$startdate') and to_days(absdate) <= to_days('$enddate') order by absdate, period "); $sth1->execute; if ($DBI::errstr) { print $DBI::errstr; die $DBI::errstr; } my $absrows = $sth1->rows; # Get teacher and homeroom data (if any), print Enrollment changes. $sth2 = $dbh->prepare("select sal, firstname, lastname from staff where homeroom='$homeroom' and homeroom != ''"); $sth2->execute; if ($DBI::errstr) { print $DBI::errstr; die $DBI::errstr; } my ($tsal, $tfirstname, $tlastname) = $sth2->fetchrow; my $teacher; if ($tlastname){ $teacher = "Teacher:& $tsal $tfirstname $tlastname\\\\ \n"; } my $hroom; if ($homeroom) { $hroom = "Homeroom:& $homeroom\\\\ \n"; } print TEX "\\center {\\sf\\huge $schoolname $lex{'Attendance Profile'} } \\\\ \n\\bigskip"; print TEX "{\\Large $firstname $lastname} $withdrawn \\\\ \n \\bigskip\n"; print TEX "\\begin{tabular}{r|l} \n \\hline\n$teacher $hroom\n \\hline\n"; print TEX $lex{'Start Date'}. ": & $fmtstartdate \\\\ \n ". $lex{'End Date'}. ": & $fmtenddate \\\\ \n"; print TEX $lex{'School Days'}. ": & $schooldays \\\\\n\\hline\n\\end{tabular}\\\\\n\\bigskip\n"; if ($entryrecs > 0 ) { # then print entry/withdrawals print TEX "\\hrulefill\\\\\n{\\large\\sf $lex{'Enrollment Changes'} }\\\\ \n"; print TEX "\\begin{tabular}{lll}\n"; for ($i=1; $i<=$entryrecs; $i++) { $entryrec = pop @entries; ($edate,$etype,$edesc) = split(/:/,$entryrec); print TEX "$edate & $etype & $edesc \\\\ \n"; } print TEX "\\end{tabular}\\\\ \n\\medskip\n"; } # Now print the periods absent. # Calculate periods into days my $periods = $g_ppd{$grade}; if (not $grade) { print "

",$lex{'Error: Missing grade for student'}; print " $firstname $lastname

\n"; die; } elsif (not $periods){ # We don't have this grade's periods defined: fatal error print $lex{'Periods Per Day not defined for Grade'}. " $grade} "; print $lex{'in global configuration for student'}; print "\n
$firstname $lastname\n"; die; } my (%subjectslate, %subjectsabs); # hold subject information, if any... if ($absrows > 0 ) { # then we have absences; loop through doing calcs. print TEX "\\hrulefill\\\\ \n\\setlength{\\premulticols}{5pt}\n"; print TEX "\\setlength{\\postmulticols}{5pt}\n"; print TEX "\\begin{multicols}{2}\n\\raggedright\n"; for ($i=1; $i<=$absrows; $i++) { my ($absdate, $reason, $period, $subjsec) = $sth1->fetchrow; # Convert to AM/PM format if 2 periods per day if ($periods == 2){ # $periods defined on line 226 above $period = $ampm{$period}; # ampm hash defined at top. } # Fixes for crud in reasons for attendance. $reason =~ s/[^A-Za-z\s]//g; #$reason =~ s/(\w+)/\u\L$1/g; # Do the calcs for reasons if ($reason eq $lateUnexcused){ $tardyu++; if ($subjsec) { $subjectslate{$subjsec}++; } # only for unexcused. } elsif ($reason =~ m/$lateString/) { $tardye++; } elsif ($reason eq $absentUnexcused){ $absentu++; if ($subjsec) { $subjectsabs{$subjsec}++; } # only for unexcused. } elsif ($reason =~ m/$absentString/){ $absente++; } else { # unknown reason... print "". $lex{'Unknown reason'}. ": $reason - $absdate - ". $lex{Period}. " $period
\n"; print "$firstname $lastname ($studnum)
\n"; } print TEX "$absdate-$period $reason\\\\ \n"; } my $daysabsentu = sprintf("%3.2f",$absentu/$periods); my $daysabsente = sprintf("%3.2f",$absente/$periods); my $daysabsenttot = sprintf("%3.2f",$daysabsente + $daysabsentu); $tardytot = $tardye + $tardyu; print TEX "\\end{multicols}\n\n\\hrulefill\\\\ \n\\bigskip\n"; print TEX "\\begin{tabular}{r|l}\\hline\n"; print TEX $lex{Late}. ' '. $lex{Unexcused}," & $tardyu ",$lex{times},"\\\\\n"; print TEX $lex{Late}. ' '. $lex{Excused}, "& $tardye ", $lex{times}, " \\\\\n"; print TEX $lex{Total}. ' '. $lex{Late}, "& $tardytot ", $lex{times}, "\\\\\n"; print TEX "\\hline\n", $lex{Absent}. ' '. $lex{Unexcused}, "& $daysabsentu "; print TEX $lex{'day(s)'}, "\\\\\n"; print TEX $lex{Absent}. ' '. $lex{Excused}, "& $daysabsente ", $lex{'day(s)'}, "\\\\\n"; print TEX $lex{Total}. ' ', $lex{Absent}, "& $daysabsenttot ", $lex{'day(s)'}, "\\\\\n"; print TEX "\\hline\n\\end{tabular}\n"; #print "Subjects:",%subjectsabs,"
\n",%subjectslate,"
\n"; # subject printing... if (%subjectslate or %subjectsabs) { # if any values in either late or absent. my $sth5 = $dbh->prepare("select description from subject where subjsec = ?"); print TEX "\n\\bigskip\n\n"; print TEX "\\begin{tabular}{|l|c|c|}\\hline\n"; print TEX "\\rowcolor[gray]{0.90}",$lex{Subject},'&',$lex{Absent},'&',$lex{Late}," \\\\ \\hline\n"; foreach my $subjsec (keys %subjectsabs) { $sth5->execute($subjsec); my $desc = $sth5->fetchrow; print TEX "$desc ($subjsec) & $subjectsabs{$subjsec} & $subjectslate{$subjsec} \\\\ \\hline\n"; } foreach my $subjsec (keys %subjectslate) { $sth5->execute($subjsec); my $desc = $sth5->fetchrow; print TEX "$desc ($subjsec)& $subjectsabs{$subjsec} & $subjectslate{$subjsec} \\\\ \\hline\n"; } print TEX "\\end{tabular}\n"; } print TEX "\\newpage\n\n"; } else { # Perfect Attendance print TEX "\\bigskip {\\huge ", $lex{'Perfect Attendance!'}, "} \\newpage \n\n"; } } # End of Student Loop print TEX "\\end{document}"; close TEX; system("$pdflatex $filename >pdflog$$.txt"); system("mv $shortname.pdf $downloaddir"); system("mv pdflog$$.txt $downloaddir"); system("rm $shortname.*"); print "

\n"; print $lex{'View/Download'}. ' '. $lex{'Attendance Profiles'}, "

\n"; print "[ ". $lex{Attendance}. " |\n"; print "". $lex{'View Log File'}. "\n"; print " ]\n
\n"; #------------------ sub selectWithdrawn { # select withdrawn students to print. #------------------ my ($startdate, $enddate) = @_; # Get count of withdrawn students in studentwd table my $sth = $dbh->prepare("select count(*) from studentwd"); $sth->execute; if ($DBI::errstr) { print $DBI::errstr; die $DBI::errstr; } my $studcount = $sth->fetchrow; print "

$studcount $lex{'Withdrawn Students'}

\n"; print "
"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; my $sth = $dbh->prepare("select studid,lastname, firstname, studnum from studentwd order by lastname, firstname"); $sth->execute; if ($DBI::errstr) { print $DBI::errstr; die $DBI::errstr; } for (1..$studcount) { my ($studid, $lastname, $firstname, $studnum) = $sth->fetchrow; print "\n"; } print "\n"; print "
",$lex{Name},"",$lex{Select},"
"; print "
$lastname, $firstname ($studnum)"; print "
"; print "
\n"; exit; } #----------------- sub printStartPage { #----------------- # print sortorder and selection input form. print "

". $lex{'Attendance Profiles'}. "

\n"; print "
\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "
\n"; print "
\n"; print "\n"; print "
". $lex{'Start Date'}. "\n"; print "
". $lex{'End Date'}. "
". $lex{Show}. q{ }. $lex{'Withdrawn Students'}. "\n"; print "
\n"; print "\n"; print "\n"; exit; }