SSP-2756 Fix program status calculation in CCM and DS reports

Mainly intended to correct problems with program status data exposed to
Counselor Case Management and Disability Services reports. The problem
there was that students with at least one program status transition
would be rendered with a program status effectively chosen at random
from among those statuses. (In some cases it appeared that no
status would be rendered; this I cannot fully explain.)

The randomized status calculation happened b/c the reports' main
Hibernate query would return a multiple records per student -- one for
each of a student's person_program_status records. But then the code
that tried to pick the "current" status just looked at the
person_program_status that happened to be read "last" for any given
student.

After that code was fixed to take the entire set of
person_program_statuses into account, more fixes were necessary b/c
the code that actually collected that set limited the set to at most
one record per program status ID. So in some cases we would have
student report records which did not contain the student's actually
current program status, i.e. the one with a null expired_date, so
we had no reliable way to find the "current" status.

After that was fixed for CCM and I started looking at the DS report.
It turned out there was quite a bit of duplicate code, plus a problem
with incorrect student counts in the final report (the count reflected
the total person_program_status records, not student records). So
the code for dealing with person_program_status records was
consolidated and abstracted to fix both reports, and while working on
that I went ahead and fixed the DS student count problem.

There was also a problem where if a student's person_program_status
records were all expired, an effectively random program status would
be output for that students. This patch fixes that by outputting no
status a student's statuses are all expired. And more importantly, it
defines "expired" as 'person_program_status.expired_date is not null'.
This is consistent with the way the rest of the app handles that field,
but may not be entirely expected by someone trying to set delayed
expiration dates directly in the database.