Adds the membership foundation: community.membership.tier (admin-configurable
tiers, each auto-creating a linked invoicing product), res.partner membership
fields (member ID, tier, state, family grouping, volunteer tracking), and a
res.config.settings page (org name, member-ID format, renewal offsets, portal
toggle) - all backed by ir.config_parameter so nothing is hardcoded.
Member IDs are generated on activation via a configurable format string
({year}/{seq}) resolved against an ir.sequence. Adds the Membership Manager
group/menu and views (tier list/form, partner form tab, list columns, search
filters, settings page).
Along the way, hit two real Odoo 19 API changes vs. older tutorials/docs:
res.groups.category_id was replaced by privilege_id -> res.groups.privilege,
and module settings pages now use the <app>/<block>/<setting> pattern
inherited from base.res_config_settings_view_form rather than raw divs.
Verified by upgrading the module against a live Odoo 19 + Postgres 16
container: installs clean, all 4 tests pass (tier->product creation, member
ID generation on activation, one-time/lifetime tiers never expire,
configurable ID format), and the module still installs cleanly for its
dependents (community_benefits, tncsc_deployment).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
72 lines
3.6 KiB
XML
72 lines
3.6 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<odoo>
|
|
<record id="view_partner_form_membership" model="ir.ui.view">
|
|
<field name="name">res.partner.form.membership</field>
|
|
<field name="model">res.partner</field>
|
|
<field name="inherit_id" ref="base.view_partner_form"/>
|
|
<field name="arch" type="xml">
|
|
<notebook position="inside">
|
|
<page string="Membership" name="membership">
|
|
<group>
|
|
<group>
|
|
<field name="membership_member_id"/>
|
|
<field name="membership_tier_id"/>
|
|
<field name="membership_state"/>
|
|
<field name="family_head_id"/>
|
|
</group>
|
|
<group>
|
|
<field name="membership_start"/>
|
|
<field name="membership_expiry"/>
|
|
<field name="is_volunteer"/>
|
|
<field name="volunteer_hours" invisible="not is_volunteer"/>
|
|
</group>
|
|
</group>
|
|
<button name="action_activate_membership" type="object"
|
|
string="Activate Membership" class="btn-primary"
|
|
invisible="membership_state == 'active'"/>
|
|
</page>
|
|
</notebook>
|
|
</field>
|
|
</record>
|
|
|
|
<record id="view_partner_list_membership" model="ir.ui.view">
|
|
<field name="name">res.partner.list.membership</field>
|
|
<field name="model">res.partner</field>
|
|
<field name="inherit_id" ref="base.view_partner_tree"/>
|
|
<field name="arch" type="xml">
|
|
<field name="display_name" position="after">
|
|
<field name="membership_member_id" optional="show"/>
|
|
<field name="membership_tier_id" optional="show"/>
|
|
<field name="membership_state" optional="show"/>
|
|
</field>
|
|
</field>
|
|
</record>
|
|
|
|
<record id="view_partner_kanban_membership" model="ir.ui.view">
|
|
<field name="name">res.partner.kanban.membership.search</field>
|
|
<field name="model">res.partner</field>
|
|
<field name="inherit_id" ref="base.view_res_partner_filter"/>
|
|
<field name="arch" type="xml">
|
|
<filter name="type_person" position="after">
|
|
<separator/>
|
|
<filter string="Members" name="is_member" domain="[('membership_state', '!=', 'none')]"/>
|
|
<filter string="Active Members" name="membership_active" domain="[('membership_state', '=', 'active')]"/>
|
|
<filter string="Renewal Due" name="membership_renewal_due" domain="[('membership_state', '=', 'renewal_due')]"/>
|
|
<filter string="Expired Members" name="membership_expired" domain="[('membership_state', '=', 'expired')]"/>
|
|
<group name="group_by" position="inside">
|
|
<filter string="Membership Tier" name="group_by_tier" context="{'group_by': 'membership_tier_id'}"/>
|
|
<filter string="Membership State" name="group_by_state" context="{'group_by': 'membership_state'}"/>
|
|
</group>
|
|
</filter>
|
|
</field>
|
|
</record>
|
|
|
|
<record id="action_membership_members" model="ir.actions.act_window">
|
|
<field name="name">Members</field>
|
|
<field name="res_model">res.partner</field>
|
|
<field name="view_mode">list,kanban,form</field>
|
|
<field name="domain">[('membership_state', '!=', 'none')]</field>
|
|
<field name="context">{'default_membership_state': 'invoiced'}</field>
|
|
</record>
|
|
</odoo>
|