EMS

Technical Reference: ems.strike

Overview

ems.strike lets a teacher flag a disciplinary incident (“strike”) against a student from the roll-call/attendance-taking view (a button to the left of the notes pencil). Each strike is independent of the attendance session — just student, issuing teacher, date, reason, optional notes.

Every strike notifies the student’s own email always, the family (subject to the same minor/auth_share rule used for attendance-issue notifications), and the group tutor. Every time the student’s cumulative strike count is a multiple of a configurable threshold (ems.strike_escalation_threshold, default 3), the coexistence coordinator(s) sharing the issuing teacher’s ascendant Head of Studies / Deputy Head of Studies are also notified.

Module files: models/coexistence/strike.py, models/coexistence/strike_reason.py


Data Model

ems.strike.reason

Field Type Description
name Char (translate) Reason label shown in the roll-call dropdown
sequence Integer Ordering; ems.strike_reason_other (sequence 1) is the default, generic reason
active Boolean Archivable without deleting historical references

ems.strike (_inherit = ['ems.base'] — gives mail.thread/chatter for free)

Field Type Required Description
student_id Many2one → res.partner Yes Domain contact_type = student, ondelete='cascade'
teacher_id Many2one → hr.employee Yes Issuer; defaults to the current user’s employee
reason_id Many2one → ems.strike.reason Yes Defaults to ems.strike_reason_other
date Datetime Yes Defaults to now (date and time of the incident)
notes Text No Free-text details
send_to Char (readonly) Resolved recipient addresses, semicolon-separated (bookkeeping)
strike_count Integer (computed, not stored) Student’s cumulative strike count up to and including this one; not shown in any view (redundant with the list itself) — used internally by _check_escalation() and by the escalation email’s “Total strikes” line
attendance_session_line_id Many2one → ems.attendance_session_line No Optional; set only when issued from the roll-call view, ondelete='set null' — the strike record is never deleted just because its session/line is

display_name is computed as "{student} | {date} | {reason}".

Per-session strike count (UI only): ems.attendance_session_line carries the inverse strike_ids (One2many, inverse of attendance_session_line_id) purely so the roll-call view can show, per student row, how many strikes were issued during that specific session — the strike button turns solid red and displays the count instead of the icon once strike_ids.length > 0 (static/src/xml/backend/attendance_session_view.xml, .ems-av-strike-btn--has-strikes in the matching CSS). Date/time-window matching was deliberately rejected for this: the school runs parallel sessions (e.g. a scheduled class and a guard-duty session covering the same room/time), so only an explicit link captured at creation time is unambiguous. ems.strike’s core identity and independence from the attendance model are otherwise unchanged — the field is optional and a strike created outside the roll-call flow (e.g. directly in the backend) is still perfectly valid with no session line at all.


CRUD Flow

flowchart TD
    A[Teacher clicks the strike button in the roll-call view] --> B[Dialog: reason dropdown defaults to 'Other', optional notes]
    B --> C["orm.create('ems.strike', vals)"]
    C --> D[ems.strike.create override]
    D --> E[strike_count computed on read via search_count]
    D --> F["_notify(): one template per recipient kind (student / family if minor-auth_share / tutor)"]
    D --> G{"count % threshold == 0 ?"}
    G -- yes --> H["_check_escalation(): notify matching coexistence coordinator(s)"]
    G -- no --> I[Done]

Access Control

Role Create Read Write Delete Group/Rule
Administrator ✓ (all) ems.group_academic_admin
Coexistence Manager/Administrator ✓ (all, centre-wide) ems.group_coexistence / ems.group_coexistence_admin
Tutor ✓ (own issued + tutees) ems.group_tutor (implies group_teacher)
Teacher ✓ (own issued only) ems.group_teacher

Record rules: security/rules/coexistence.xml. ems.group_coexistence is a new, independent security group family (its own ir.module.category, category_coexistence), not nested under the teacher/tutor/HoS hierarchy — coexistence coordinators can read every strike centre-wide regardless of branch (the HoS/DHoS branch matching logic is only used to route the escalation email, not to gate read access). ems.role_coexistence.group_id is wired to ems.group_coexistence, and ems.role_coexistence.unipersonal is false — multiple coexistence coordinators (one per HoS/DHoS branch) are expected.


Settings

ems.strike_escalation_threshold (res.company, default 3) — “Strikes Settings” block in Settings, same pattern as attendance_issue_status_delay.


Frontend


Views

View File
List/Form (strikes) views/coexistence/strike/{list,form}.xml
Menu (top-level “Convivencia”) views/coexistence/strike/menu.xml
List/Form (reasons, admin config) views/coexistence/strike_reason/{list,form}.xml
Menu (Convivencia → Configuration) views/coexistence/strike_reason/menu.xml
Student form smart button views/community/contact/form.xml (button_box, strike_countaction_view_strikes())

Mail Templates

Template File Purpose
ems.mail_strike_notification_student mails/coexistence/strike_notification.xml Sent to the student, on every strike
ems.mail_strike_notification_family mails/coexistence/strike_notification.xml Sent to the family (if minor or auth_share), on every strike
ems.mail_strike_notification_tutor mails/coexistence/strike_notification.xml Sent to the group tutor, on every strike
ems.mail_strike_escalation mails/coexistence/strike_escalation.xml Coexistence coordinator escalation, sent every time the threshold multiple is reached

All three notification templates live in the same file since they’re the same underlying notification, just addressed and worded differently per recipient (not three unrelated features) — ems.strike._notify() picks the matching template per recipient kind returned by _collect_recipients_by_kind().


Data Files

File Purpose
data/main/ems.strike.reason.csv Seeds ems.strike_reason_other (default) plus a few sample reasons

Testing note

ems.strike._notify()/_check_escalation() send email synchronously (force_send=True) on every create(). Any test that creates a strike must patch odoo.addons.base.models.ir_mail_server.IrMailServer.send_email (see tests/test_strike.py/tests/test_strike_tour.py), since this environment’s ir.mail_server records point to real, credentialed outgoing servers — Odoo’s test runner does not suppress real SMTP delivery on its own.

Follow-ups (out of scope for v1)