Enhancement Request for Rollover Process - Student Identification and Data Matching

During the rollover process (rollover.php?step=2), we face significant challenges in handling student enrollment for large cohorts (500+ students). Two critical improvements are needed to streamline this process:

1. Improved Student Identification

Problem:
The current interface displays only student names (legal + preferred) without unique identifiers. This makes it difficult to distinguish between Students with identical/similar names.

Request:
Add student ID and/or username fields as visible columns in the enrollment tables to enable unique identification.

2. Bulk Data Matching Mechanism

Problem:
The current manual selection process (grade/class assignment via dropdowns) is:

  • Error-prone for large cohorts
  • Time-intensive (500+ manual selections)
  • Inflexible for annual re-classing requirements

Proposed Solutions:
a) CSV Import Functionality

  • Add “Import Assignments” button at step 2
  • Accept CSV with columns: StudentID, YearGroup, FormGroup
  • Auto-match and pre-fill selections

b) Temporary Group Assignment + Re-classing

  • Option to assign all new students to a “Temporary Intake Group”
  • Post-rollover bulk assignment via existing class management tools

c) Preset Rules Engine
(e.g., “All G9 students move to G10”, with form groups randomized/assigned alphabetically)

Why This Matters

  1. Data Integrity: Manual processing causes enrollment errors affecting timetabling, reporting, and parent access
  2. Operational Efficiency: Current process requires 10+ hours of admin work annually
  3. Scalability: Essential for schools with large annual intake cohorts

Technical Context

Our current JavaScript workaround (DOM manipulation) is fragile and:

  • Requires technical expertise
  • Breaks with UI updates
  • Lacks validation mechanisms

±--------------------------------------------------+
| Enrol New Students (Full Status) |
±----------------±----------------±--------------+
| Name | Student ID | Username | ← NEW COLUMNS
±----------------±----------------±--------------+
| Zhang, Wei | S-2023-0451 | zhangw23 |
| Zhang, Wei | S-2024-1872 | zhangwei_new | ← Duplicates distinguishable
±----------------±----------------±--------------+
| [Import CSV] | [Apply Rules] | [Temp Group] | ← NEW CONTROLS
±--------------------------------------------------+

// Complete student data reference table (JSON format)
const studentData = {
    // Note: All names in this dataset are fictional
    "赵六, Liu Zhao": { grade: "G10", class: "高一G10一班" },
    "孙七, Qi Sun": { grade: "G10", class: "高一G10二班" },
    "周八, Ba Zhou": { grade: "G10", class: "高一G10三班" },
    "吴九, Jiu Wu": { grade: "G10", class: "高一G10二班" },
    "郑十, Shi Zhen": { grade: "G9", class: "初三G9" },
    // Note: All names in this dataset are fictional
    // ... (other student data remains unchanged) ...
    // Note: All names in this dataset are fictional
    "张三, San Zhang": { grade: "#N/A", class: "#N/A" },
    "李四, Li Si": { grade: "#N/A", class: "#N/A" },
    "王五, Wu Wang": { grade: "#N/A", class: "#N/A" }
    // Note: All names in this dataset are fictional
};

// Automated data filling function
function autoFillStudentData() {
    // Get all student rows
    const rows = document.querySelectorAll('tr');
    let processedCount = 0;
    let skippedCount = 0;
    let unenrolledCount = 0;
    let missingInDataCount = 0;
    const foundStudents = new Set(); // Track students found in the form
    
    // Step 1: Process students present in reference data
    rows.forEach(row => {
        const nameCell = row.querySelector('td.column.flex-grow');
        if (!nameCell) return;
        
        const studentName = nameCell.textContent.trim();
        foundStudents.add(studentName);
        const studentInfo = studentData[studentName];
        
        console.groupCollapsed(`Processing student: ${studentName}`);
        
        // Get form elements
        const checkbox = row.querySelector('input[type="checkbox"][name$="-enrol"]');
        const gradeSelect = row.querySelector('select[name$="-gibbonYearGroupID"]');
        const classSelect = row.querySelector('select[name$="-gibbonFormGroupID"]');
        
        if (!checkbox || !gradeSelect || !classSelect) {
            console.warn('❌ Form elements not found, skipping');
            skippedCount++;
            console.groupEnd();
            return;
        }
        
        if (studentInfo) {
            // Handle withdrawn students (with #N/A)
            if (studentInfo.grade === "#N/A" || studentInfo.class === "#N/A") {
                checkbox.checked = false;
                console.log('✅ Withdrawn student, enrollment unchecked');
                unenrolledCount++;
            } 
            // Process active students
            else {
                // Set grade level
                const gradeOption = [...gradeSelect.options].find(
                    opt => opt.text.trim() === studentInfo.grade
                );
                if (gradeOption) {
                    gradeSelect.value = gradeOption.value;
                    console.log(`📊 Grade set to: ${studentInfo.grade}`);
                } else {
                    console.warn(`⚠️ Grade option not found: ${studentInfo.grade}`);
                }
                
                // Set class group
                const classOption = [...classSelect.options].find(
                    opt => opt.text.trim() === studentInfo.class
                );
                if (classOption) {
                    classSelect.value = classOption.value;
                    console.log(`🏫 Class set to: ${studentInfo.class}`);
                } else {
                    console.warn(`⚠️ Class option not found: ${studentInfo.class}`);
                }
            }
            
            processedCount++;
        } else {
            console.warn('❌ Student not found in reference data');
            skippedCount++;
        }
        
        console.groupEnd();
    });
    
    // Step 2: Check for students in reference data missing from form
    const allStudents = new Set(Object.keys(studentData));
    const missingInForm = [...allStudents].filter(name => !foundStudents.has(name));
    
    if (missingInForm.length > 0) {
        console.group('⚠️ Students missing in form');
        console.log('These students are in reference data but missing from form:');
        missingInForm.forEach(name => console.log(`- ${name}`));
        console.groupEnd();
    }
    
    // Step 3: Process students in form missing from reference data (withdraw)
    rows.forEach(row => {
        const nameCell = row.querySelector('td.column.flex-grow');
        if (!nameCell) return;
        
        const studentName = nameCell.textContent.trim();
        const checkbox = row.querySelector('input[type="checkbox"][name$="-enrol"]');
        
        // If student not in reference data
        if (!studentData[studentName] && checkbox) {
            console.group(`Processing student missing from reference: ${studentName}`);
            checkbox.checked = false;
            unenrolledCount++;
            missingInDataCount++;
            console.log('✅ Student not in reference, enrollment unchecked');
            console.groupEnd();
        }
    });
    
    // Final statistics report
    console.log(`
    ==============================
      AUTOMATION COMPLETE!
      ✅ Processed students: ${processedCount}
      🚫 Unenrolled students: ${unenrolledCount}
        - Withdrawn in reference: ${unenrolledCount - missingInDataCount}
        - Missing from reference: ${missingInDataCount}
      ⚠️ Skipped entries: ${skippedCount}
      ⚠️ Students missing in form: ${missingInForm.length}
    ==============================
    `);
    
    // Return statistics (optional)
    return {
        processed: processedCount,
        unenrolled: unenrolledCount,
        skipped: skippedCount,
        missingInForm: missingInForm.length
    };
}

// Execute automation
autoFillStudentData();

@AndroidOL you could be doing rollover the wrong wayc, because before rollover you’re supposed to map formgroups under: Admin>>School Admin>> Groupings Form Group
on this page you should copy formgroups to next year and map current formgroups with upcoming year, from there you should now rollover and the system will automatically batch move students as per your “Next Formgroup” mapping.
I have skipped other steps but the above is more of the core

1 Like

Hi Kelvinmw,

Thank you again for your detailed explanation. I really appreciate you taking the time to help.

I understand that process, and I have used the mapping functionality for our continuing students. However, the specific issue we’re encountering is with newly enrolled students (who have no previous class to map from) and students who need to be completely reassigned to new classes (e.g., all G6 students being redistributed into new G7 classes, not just moved up).

Please allow me to further clarify: when students move from G6 to G7, they are not simply promoted as intact classes. Instead, they are completely reassigned—though I hesitate to put it this way—based on factors such as learning ability and academic performance. Similarly, the transition from G9 to G10 involves reorganizing classes according to students’ preferred academic tracks, taking into account their individual interests and aspirations.

Looking at your message again:

Proposed Solutions:
a) CSV Import Functionality
• Add “Import Assignments” button at step 2
• Accept CSV with columns: StudentID, YearGroup, FormGroup
• Auto-match and pre-fill selections

This functionality already exists under Data Import. While

Temporary Group Assignment + Re-classing
could be you rollover first(to temporary formgroups) and update.

Here’s what your school could do:

  1. Complete a normal rollover.
  2. Export the student list.
  3. Adjust the CSV file (e.g., map YearGroup and FormGroup for G7 and G10 according to your manual selections).
  4. Re-import the file via System Admin → Import from File.

The proposal, adding username/studentID in step 2 would be a straightforward enhancement.
Randomization, however, would require clear criteria—which can vary between schools.

In summary: Do the normal rollover, export G7 and G10 with Query Builder, adjust assignments according to your rules, and then import the updated file back into the system.

1 Like

Thank you again for your response. However, for teachers without a technical background, I am opposed to the use of data import/export tools. Since student-class mapping is already provided, adding an automated processing tool would not disrupt the overall workflow.

My suggestion to include usernames and student IDs stems from the fact that we have many students with identical names. Currently, the only way to verify whether class assignments are correct is through manual confirmation after the process is complete.

In summary: Do the normal rollover, export G7 and G10 with Query Builder, adjust assignments according to your rules, and then import the updated file back into the system.

I appreciate your reply, but from the perspective of teachers with limited computer skills, I believe a simpler solution is needed.