feat: department display

This commit is contained in:
2026-10-08 02:10:26 +03:00
parent c7298c0c84
commit 57e5bd9af8
5 changed files with 62 additions and 20 deletions
@@ -0,0 +1,10 @@
package agzam.vyatsu.data.local
import androidx.room.Embedded
/** Занятие преподавателя + кафедра-источник. Проекция LEFT JOIN, схема Room не меняется. */
data class TeacherLessonWithDepartment(
@Embedded val lesson: TeacherLessonEntity,
val departmentId: String,
val departmentName: String?
)
@@ -61,13 +61,15 @@ interface TeacherScheduleDao {
fun observeDaysForPeriod(season: String, weekStart: Long): Flow<List<TeacherDayEntity>> fun observeDaysForPeriod(season: String, weekStart: Long): Flow<List<TeacherDayEntity>>
@Query( @Query(
"SELECT teacher_lessons.* FROM teacher_lessons INNER JOIN teacher_days ON teacher_days.id = teacher_lessons.dayId " + "SELECT teacher_lessons.*, teacher_schedules.departmentId AS departmentId, departments.name AS departmentName " +
"FROM teacher_lessons INNER JOIN teacher_days ON teacher_days.id = teacher_lessons.dayId " +
"INNER JOIN teacher_schedules ON teacher_schedules.id = teacher_days.scheduleId " + "INNER JOIN teacher_schedules ON teacher_schedules.id = teacher_days.scheduleId " +
"LEFT JOIN departments ON departments.departmentId = teacher_schedules.departmentId " +
"WHERE teacher_schedules.season = :season AND teacher_schedules.weekStart <= :weekStart " + "WHERE teacher_schedules.season = :season AND teacher_schedules.weekStart <= :weekStart " +
"AND teacher_schedules.weekEnd >= :weekStart " + "AND teacher_schedules.weekEnd >= :weekStart " +
"ORDER BY teacher_lessons.date, teacher_lessons.slotIndex, teacher_lessons.lessonIndex" "ORDER BY teacher_lessons.date, teacher_lessons.slotIndex, teacher_lessons.lessonIndex"
) )
fun observeLessonsForPeriod(season: String, weekStart: Long): Flow<List<TeacherLessonEntity>> fun observeLessonsForPeriod(season: String, weekStart: Long): Flow<List<TeacherLessonWithDepartment>>
@Query( @Query(
"SELECT DISTINCT teacher FROM teacher_lessons INNER JOIN teacher_days ON teacher_days.id = teacher_lessons.dayId " + "SELECT DISTINCT teacher FROM teacher_lessons INNER JOIN teacher_days ON teacher_days.id = teacher_lessons.dayId " +
@@ -4,6 +4,7 @@ import agzam.vyatsu.data.local.DepartmentPeriodEntity
import agzam.vyatsu.data.local.TeacherDao import agzam.vyatsu.data.local.TeacherDao
import agzam.vyatsu.data.local.TeacherDayEntity import agzam.vyatsu.data.local.TeacherDayEntity
import agzam.vyatsu.data.local.TeacherLessonEntity import agzam.vyatsu.data.local.TeacherLessonEntity
import agzam.vyatsu.data.local.TeacherLessonWithDepartment
import agzam.vyatsu.data.local.TeacherScheduleDao import agzam.vyatsu.data.local.TeacherScheduleDao
import agzam.vyatsu.data.local.TeacherScheduleEntity import agzam.vyatsu.data.local.TeacherScheduleEntity
import agzam.vyatsu.data.remote.TeacherScheduleParser import agzam.vyatsu.data.remote.TeacherScheduleParser
@@ -238,30 +239,30 @@ class TeacherScheduleRepositoryImpl(
private fun buildTeacherSchedule( private fun buildTeacherSchedule(
period: SchedulePeriod, period: SchedulePeriod,
dayEntities: List<TeacherDayEntity>, dayEntities: List<TeacherDayEntity>,
lessonEntities: List<TeacherLessonEntity> lessons: List<TeacherLessonWithDepartment>
): TeacherSchedule? { ): TeacherSchedule? {
val weekDays = dayEntities val weekDays = dayEntities
.filter { .filter {
it.date >= period.weekStart.toEpochDay() && it.date <= period.weekEnd.toEpochDay() it.date >= period.weekStart.toEpochDay() && it.date <= period.weekEnd.toEpochDay()
} }
if (weekDays.isEmpty()) return null if (weekDays.isEmpty()) return null
val lessonsByDate = lessonEntities.groupBy { it.date } val lessonsByDate = lessons.groupBy { it.lesson.date }
val days = weekDays val days = weekDays
.sortedBy { it.date } .sortedBy { it.date }
.distinctBy { it.date } .distinctBy { it.date }
.map { day -> .map { day ->
val lessons = lessonsByDate[day.date].orEmpty() val dayLessons = lessonsByDate[day.date].orEmpty()
Log.d( Log.d(
TAG, TAG,
"build: date=${day.date} totalLessons=${lessons.size} " + "build: date=${day.date} totalLessons=${dayLessons.size} " +
"groupedByStart=${lessons.groupBy { it.start }.keys.sorted()}" "groupedByStart=${dayLessons.groupBy { it.lesson.start }.keys.sorted()}"
) )
val slots = lessons.groupBy { it.start } val slots = dayLessons.groupBy { it.lesson.start }
.toSortedMap() .toSortedMap()
.map { (startMin, slotLessons) -> .map { (startMin, slotLessons) ->
val first = slotLessons.first() val first = slotLessons.first().lesson
val times = slotLessons.map { "${it.start}-${it.end}" }.distinct() val times = slotLessons.map { "${it.lesson.start}-${it.lesson.end}" }.distinct()
val teachers = slotLessons.map { it.teacher }.distinct() val teachers = slotLessons.map { it.lesson.teacher }.distinct()
Log.d( Log.d(
TAG, TAG,
"build: date=${day.date} start=$startMin firstEnd=$first.end " + "build: date=${day.date} start=$startMin firstEnd=$first.end " +
@@ -271,7 +272,7 @@ class TeacherScheduleRepositoryImpl(
start = LocalTime.of(startMin / 60, startMin % 60), start = LocalTime.of(startMin / 60, startMin % 60),
end = LocalTime.of(first.end / 60, first.end % 60), end = LocalTime.of(first.end / 60, first.end % 60),
lessons = slotLessons lessons = slotLessons
.sortedBy { it.lessonIndex } .sortedBy { it.lesson.lessonIndex }
.map { it.toDomain() } .map { it.toDomain() }
) )
} }
@@ -280,16 +281,33 @@ class TeacherScheduleRepositoryImpl(
return TeacherSchedule(period = period, days = days) return TeacherSchedule(period = period, days = days)
} }
private fun TeacherLessonEntity.toDomain(): Lesson = Lesson( private fun TeacherLessonWithDepartment.toDomain(): Lesson = Lesson(
raw = raw, raw = lesson.raw,
room = room, room = lesson.room,
group = groupName, group = lesson.groupName,
subgroup = subgroup, subgroup = lesson.subgroup,
subject = subject, subject = lesson.subject,
type = type, type = lesson.type,
teacher = teacher teacher = lesson.teacher,
department = departmentLabel(departmentId, departmentName)
) )
// Названия, не выводимые акронимом из слов (например «топ-уровня в сфере…»)
private val DEPARTMENT_LABELS = mapOf(
"974" to "Топ ИИ"
)
private fun departmentLabel(departmentId: String, departmentName: String?): String {
DEPARTMENT_LABELS[departmentId]?.let { return it }
val name = departmentName ?: return "каф. $departmentId"
val words = name.removePrefix("Кафедра ").removePrefix("кафедра ")
.substringBefore(" (")
.split(" ")
.filter { it.isNotBlank() && it != "и" }
val acronym = words.joinToString("") { it.first().uppercase().toString() }
return acronym.ifBlank { "каф. $departmentId" }
}
private fun LocalTime.toMinutesOfDay(): Int = hour * 60 + minute private fun LocalTime.toMinutesOfDay(): Int = hour * 60 + minute
private fun now(): Long = System.currentTimeMillis() private fun now(): Long = System.currentTimeMillis()
@@ -26,4 +26,6 @@ data class Lesson(
/** Преподаватель **/ /** Преподаватель **/
val teacher: String? = null, val teacher: String? = null,
/** Кафедра-источник (только в расписании преподавателей) **/
val department: String? = null,
) )
@@ -70,6 +70,16 @@ internal fun LessonSlot(slotIndex: Int, slot: TimeSlot, showTeacher: Boolean = t
) { ) {
var any = false var any = false
if (lesson.department != null) {
Text(
lesson.department,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(horizontal = hPadding)
)
any = true
}
val meta = listOfNotNull( val meta = listOfNotNull(
if (lesson.type == LessonType.OTHER) { if (lesson.type == LessonType.OTHER) {
null null