Serves Demo Scene 2 end to end: mc.applicant with the stage pipeline (Applied -> Document Verification -> Interview -> Offered -> Accepted -> Enrolled/Rejected) on mail.thread, a public admission page on the website, and a convert wizard that turns an accepted applicant into a real mc.student + mc.enrollment with zero re-typing. The public form uses Odoo's stock /website/form/<model> mechanism, not a custom controller (CLAUDE.md sec 1.3 - writing a custom version of stock infrastructure is a bug). Verified the real mechanism against core source first rather than assuming: website_hr_recruitment's own data/config_data.xml is the template this follows (ir.model. website_form_access + ir.model.fields.formbuilder_whitelist()). This is the module's actual security boundary, and it's worth being explicit about why it holds. The generic controller creates the record as SUPERUSER - normal ir.model.access rows do not apply to it at all. The only thing stopping a submitter from setting state, student_id, application_no or company_id is that those fields are not in the formbuilder_whitelist() call in data/mc_applicant_website_form_data.xml (every field defaults to website_form_blacklisted=True and stays that way unless explicitly opted in). Confirmed this isn't just theoretical: posted state=enrolled and application_no=HACKED-0001 directly at /website/form/mc.applicant on a live instance, and the resulting record came back with the model's own default state=applied and a server-generated APP20260004 - the injected values were silently dropped, exactly as the whitelist should do. Also exercised a real file upload (birth certificate) and the full convert-to-student path (guardian dedup by email, application_no -> student.application_no, enrollment, attachment reparenting) via odoo shell against the live container, not just read by inspection. mc.student gets a new application_no field (_inherit from this module, not O1 - it only makes sense where admission is installed) so "the application number persists on the student" is a stored fact, not just a claim in the demo script. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
101 lines
4.6 KiB
XML
101 lines
4.6 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<odoo>
|
|
<record id="view_mc_applicant_kanban" model="ir.ui.view">
|
|
<field name="name">mc.applicant.kanban</field>
|
|
<field name="model">mc.applicant</field>
|
|
<field name="arch" type="xml">
|
|
<kanban default_group_by="state" records_draggable="1">
|
|
<field name="name"/>
|
|
<field name="program_id"/>
|
|
<field name="guardian_name"/>
|
|
<field name="application_no"/>
|
|
<field name="state"/>
|
|
<templates>
|
|
<t t-name="card">
|
|
<div class="oe_kanban_card">
|
|
<strong><field name="name"/></strong>
|
|
<div><field name="program_id"/></div>
|
|
<div class="text-muted">
|
|
<field name="guardian_name"/>
|
|
</div>
|
|
<div class="text-muted">
|
|
<field name="application_no"/>
|
|
</div>
|
|
</div>
|
|
</t>
|
|
</templates>
|
|
</kanban>
|
|
</field>
|
|
</record>
|
|
|
|
<record id="view_mc_applicant_list" model="ir.ui.view">
|
|
<field name="name">mc.applicant.list</field>
|
|
<field name="model">mc.applicant</field>
|
|
<field name="arch" type="xml">
|
|
<list string="Applicants">
|
|
<field name="application_no"/>
|
|
<field name="name"/>
|
|
<field name="program_id"/>
|
|
<field name="guardian_name"/>
|
|
<field name="state" widget="badge"/>
|
|
</list>
|
|
</field>
|
|
</record>
|
|
|
|
<record id="view_mc_applicant_form" model="ir.ui.view">
|
|
<field name="name">mc.applicant.form</field>
|
|
<field name="model">mc.applicant</field>
|
|
<field name="arch" type="xml">
|
|
<form string="Applicant">
|
|
<header>
|
|
<button name="action_set_document_verification" string="Move to Document Verification"
|
|
type="object" class="oe_highlight" invisible="state != 'applied'"/>
|
|
<button name="action_set_interview" string="Move to Interview"
|
|
type="object" class="oe_highlight" invisible="state != 'document_verification'"/>
|
|
<button name="action_set_offered" string="Make Offer"
|
|
type="object" class="oe_highlight" invisible="state != 'interview'"/>
|
|
<button name="action_set_accepted" string="Mark Accepted"
|
|
type="object" class="oe_highlight" invisible="state != 'offered'"/>
|
|
<button name="action_open_convert_wizard" string="Enroll"
|
|
type="object" class="oe_highlight" invisible="state != 'accepted'"/>
|
|
<button name="action_reject" string="Reject"
|
|
type="object" invisible="state in ('enrolled', 'rejected')"/>
|
|
<field name="state" widget="statusbar"
|
|
statusbar_visible="applied,document_verification,interview,offered,accepted,enrolled"/>
|
|
</header>
|
|
<sheet>
|
|
<div class="oe_title">
|
|
<label for="name"/>
|
|
<h1><field name="name"/></h1>
|
|
</div>
|
|
<group>
|
|
<group>
|
|
<field name="application_no" readonly="1"/>
|
|
<field name="dob"/>
|
|
<field name="program_id"/>
|
|
</group>
|
|
<group>
|
|
<field name="guardian_name"/>
|
|
<field name="guardian_relationship"/>
|
|
<field name="guardian_phone"/>
|
|
<field name="guardian_email"/>
|
|
</group>
|
|
</group>
|
|
<field name="student_id" invisible="not student_id" readonly="1"/>
|
|
</sheet>
|
|
<chatter/>
|
|
</form>
|
|
</field>
|
|
</record>
|
|
|
|
<record id="action_mc_applicant" model="ir.actions.act_window">
|
|
<field name="name">Applicants</field>
|
|
<field name="res_model">mc.applicant</field>
|
|
<field name="view_mode">kanban,list,form</field>
|
|
</record>
|
|
|
|
<menuitem id="menu_mc_applicant" name="Admissions"
|
|
parent="mc_education_base.menu_school_root"
|
|
action="action_mc_applicant" sequence="5"/>
|
|
</odoo>
|