EMS

Contact data update requests (ems.contact.data.request)

Issue #507. Students and families review and complete their own contact details from the portal, on request, so that family communications and portal access work: a minor’s notices go to their family contacts (res.partner._ems_notification_recipients()), and the portal login is the recipient’s email. The request is sent from the backend (individually, or to groups, studies or levels), answered on /my/dades-contacte, and applied only once a reviewer approves it.

Files

File Content
models/contacts/contact_data_request.py ems.contact.data.request, ems.contact.data.request.line, ems.contact.data.request.reject.wizard, and the res.partner side (_ems_contact_data(), _ems_contact_data_missing(), smart button, bulk action)
models/contacts/contact_data_request_send_wizard.py ems.contact.data.request.send.wizard (+ preview line)
static/src/js/backend/contact_data_request_send_form.js The wizard form’s js_class (ems_contact_data_request_send_form): blocks the screen with a “Processing the requests…” overlay while Send runs (blocking_action_form.js)
models/shared/student_scope_mixin.py ems.student.scope.mixin, the student picking shared with the authorization send wizard
models/contacts/family_contact.py Recognising, creating and linking family contacts (_ems_find_family(), _ems_create_family_contact(), _ems_link_family())
controllers/portal_contact_data.py /my/dades-contacte (GET shows, POST validates and stages); a view-only account is sent home (ems_portal_manage_required)
views/portal/portal_contact_data.xml The portal page (portal_contact_data_new_family_extras: the repeated-contact question and the other-children checkboxes); the home banner (only while a request is pending) is in portal_main.xml, the Update contact details button of the Profile tab in portal_account_readonly.xml
views/community/contact_data_request/ Send wizard, bindings (students list/form, groups list/form), request list/form/search, return wizard, menu
mails/contacts/contact_data_request.xml ems.email_template_contact_data_request (first request, reminder, returned)

The menu is Educational Community > Students > Data request (menu_contact_data_requests, groups: academic admin, secretary, head of studies, tutor). Students is a section (menu_students_root, no action) holding the Students list (menu_students, which keeps its action: the cog-menu scripts read it by xmlid) and this menu, so clicking Educational Community still opens the Students list.

Model

erDiagram
    RES_PARTNER ||--o{ EMS_CONTACT_DATA_REQUEST : "student_id"
    EMS_COURSE ||--o{ EMS_CONTACT_DATA_REQUEST : "course_id"
    EMS_CONTACT_DATA_REQUEST ||--o{ EMS_CONTACT_DATA_REQUEST_LINE : "line_ids"
    EMS_CONTACT_DATA_REQUEST_LINE }o--|| RES_PARTNER : "partner_id (contact changed / removed)"
    EMS_CONTACT_DATA_REQUEST_LINE }o--o| RES_PARTNER_RELATION_TYPE : "relation_type_id (new family contact)"
    EMS_CONTACT_DATA_REQUEST_LINE }o--o| RES_PARTNER : "matched_partner_id / possible_duplicate_id"

Mandatory fields

Checked by one method, ems.contact.data.request._ems_contact_data_problems(data, is_adult, formats, current), on the shape returned by res.partner._ems_contact_data(). The portal form uses it with format checks (so each problem lands under its input); res.partner._ems_contact_data_missing() uses it without them, for the send wizard’s “only incomplete” filter, the preview and the email.

Who Required Optional
Student Street, ZIP, city; DNI/NIE or passport; personal email if adult (portal login) Mobile, phone, TIS, NUSS, email of a minor
Family (minor: at least one legal tutor; adult: optional) First name, last name, mobile, relation (new contacts); at least one family contact with email (minor); no two family contacts sharing an email (one portal login each) DNI/NIE, passport, address (defaults to the student’s)

Formats: email_normalize, phonenumbers.is_possible_number (region ES), DNI/NIE check letter (_ems_valid_dni_nie), NUSS 12 digits (same rule as res.partner._check_nuss). Name and birth date are official data: read-only on the portal, corrected through the secretariat.

Personal emails are never corporate (issue #514). With the format checks, _ems_personal_email_problems(data, current) refuses every email the answer changes to an address of the centre’s own domain, before anything is staged, by asking res.company._ems_check_personal_email() (the same check, and message, as the res.partner constraint) for each person: the student, every family contact on file and every new one (not the ones marked as removed). An address already on file is left alone, as the constraint only fires when the email is written: that is what current (the data on file) is for. The constraint stays the last word, since approval writes the emails through the ORM. Like the constraint, the check is off on a dev database or without a configured domain.

Recognising a family contact

res.partner._ems_find_family(document, mobile, firstname) returns (family, possible_duplicate), both with sudo:

flowchart TD
    A["_ems_find_family(document, mobile, firstname)"] --> B{"document given and a family<br/>contact has it (DNI/NIE or passport)?"}
    B -- yes --> R1["family = that contact"]
    B -- no --> C["family contacts whose mobile or phone<br/>has the same E.164 key"]
    C --> D{"exactly one, and first names compatible?<br/>('Raquel' in 'Raquel Salguero')"}
    D -- yes --> R2["family = that contact"]
    D -- no, none --> R3["nothing: create a new one"]
    D -- no, several or another name --> R4["possible_duplicate = first candidate:<br/>create a new one and flag it"]

The same lookup is used by the approval of a request, the relation wizard (ems.contact.relation.wizard, which now links an existing contact instead of creating a duplicate) and the Esfera import (ems.student_import_wizard._get_or_create_family, which reports possible duplicates in its warnings).

Several children and repeated contacts

A family answers for one child at a time (get_portal_student()), but a family contact it adds often belongs to its other children too, and is often already on file as a contact of one of them.

Flow

sequenceDiagram
    participant S as Sender (tutor / secretary / head of studies)
    participant W as send wizard
    participant F as Family (portal)
    participant R as ems.contact.data.request
    participant P as res.partner

    S->>W: groups / studies / levels, or students
    W->>R: create or reopen, _ems_mark_sent() (missing_fields snapshot)
    W->>F: grant portal access if lacking (portal access wizard, sudo)
    W->>F: email with link to /my/dades-contacte
    F->>R: POST form: _ems_contact_data_problems() then _ems_submit()
    Note over R: lines = diff against the data on file<br/>no changes: done directly
    S->>R: action_approve() (bulk) or return with reason
    R->>P: write with the reviewer's own rights<br/>new family contact: _ems_find_family() then link or create (sudo)

Who may answer on the portal

res.partner._ems_portal_contact_data_student() returns the student or applicant whose data a portal partner may review and send: the one it is looking at (get_portal_student()) when it acts for them (_ems_portal_can_act_for()), otherwise nobody. A view-only account (a minor on their own account, a family looking at an adult child who shares with it, a family with no child left to see) therefore has:

The single entry point of the review is that Profile button; the home only shows the banner while a request is pending (_ems_contact_data_requested()), and the email links to the page directly.

Access

Group Requests / lines Send wizard Rule
group_academic_admin, group_secretary CRUD yes all
group_head_of_studies read, write, create (lines also unlink) yes all
group_tutor read, write, create (lines also unlink) yes, own groups only student_id.tutor_id.tutor_scope_user_ids = user (the tutor and the chiefs above them, issue #483)
group_teacher (not tutor) none no -
portal none: everything through the controller, with sudo - only the student returned by get_portal_student(), and only the family contacts already related to it

Tests