Adds the daily membership renewal cron (_cron_process_membership_renewals):
sends a "renewal upcoming" reminder at each non-final configured offset,
flips state to renewal_due and sends a "renewal due" notice at the final
(smallest) offset, and on expiry flips to 'expired', sends an expiry notice,
and creates a draft renewal invoice for the member's tier product.
Email templates (renewal_upcoming, renewal_due, membership_expired) use
Odoo 19's current mail.template syntax ({{ }} subject/QWeb t-out body) -
the plan referenced the older ${object...} syntax, which v19 no longer
uses. Org name is pulled via a new non-stored res.partner.membership_org_name
compute field backed by ir.config_parameter, never hardcoded.
Also had to adapt the ir.cron data record: Odoo 19 removed 'numbercall'
entirely (ir.cron now delegates most fields to a linked ir.actions.server
under the hood, though name/model_id/state/code are still settable
directly on the record, per the core mail module's own cron definitions).
Verified against a live Odoo 19 + Postgres 16 container: module upgrades
clean, all 8 tests pass (4 from 1-A + 4 new: non-final-offset reminder,
final-offset state flip to renewal_due, expiry creates a draft invoice +
notice, no action outside any offset window).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
70 lines
3.3 KiB
XML
70 lines
3.3 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<odoo>
|
|
<data noupdate="1">
|
|
|
|
<record id="mail_template_membership_renewal_upcoming" model="mail.template">
|
|
<field name="name">Membership: Renewal Upcoming</field>
|
|
<field name="model_id" ref="base.model_res_partner"/>
|
|
<field name="subject">{{ object.membership_org_name or 'Your organization' }} membership renewal reminder</field>
|
|
<field name="partner_to">{{ object.id }}</field>
|
|
<field name="lang">{{ object.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
<field name="body_html" type="html">
|
|
<div style="margin: 0px; padding: 0px; font-size: 13px;">
|
|
<p>Dear <t t-out="object.name or ''">Member</t>,</p>
|
|
<p>
|
|
Your membership
|
|
<t t-if="object.membership_member_id">(<t t-out="object.membership_member_id"/>)</t>
|
|
with <t t-out="object.membership_org_name or 'us'"/> is due for renewal on
|
|
<t t-out="format_date(object.membership_expiry)"/>.
|
|
</p>
|
|
<p>No action is needed yet - this is an early reminder. You will receive another notice closer to the date.</p>
|
|
</div>
|
|
</field>
|
|
</record>
|
|
|
|
<record id="mail_template_membership_renewal_due" model="mail.template">
|
|
<field name="name">Membership: Renewal Due</field>
|
|
<field name="model_id" ref="base.model_res_partner"/>
|
|
<field name="subject">{{ object.membership_org_name or 'Your organization' }} membership renewal is due</field>
|
|
<field name="partner_to">{{ object.id }}</field>
|
|
<field name="lang">{{ object.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
<field name="body_html" type="html">
|
|
<div style="margin: 0px; padding: 0px; font-size: 13px;">
|
|
<p>Dear <t t-out="object.name or ''">Member</t>,</p>
|
|
<p>
|
|
Your membership
|
|
<t t-if="object.membership_member_id">(<t t-out="object.membership_member_id"/>)</t>
|
|
with <t t-out="object.membership_org_name or 'us'"/> expires on
|
|
<t t-out="format_date(object.membership_expiry)"/>.
|
|
</p>
|
|
<p>Please renew soon to keep your membership active and avoid a lapse in benefits.</p>
|
|
</div>
|
|
</field>
|
|
</record>
|
|
|
|
<record id="mail_template_membership_expired" model="mail.template">
|
|
<field name="name">Membership: Expired</field>
|
|
<field name="model_id" ref="base.model_res_partner"/>
|
|
<field name="subject">Your {{ object.membership_org_name or 'organization' }} membership has expired</field>
|
|
<field name="partner_to">{{ object.id }}</field>
|
|
<field name="lang">{{ object.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
<field name="body_html" type="html">
|
|
<div style="margin: 0px; padding: 0px; font-size: 13px;">
|
|
<p>Dear <t t-out="object.name or ''">Member</t>,</p>
|
|
<p>
|
|
Your membership
|
|
<t t-if="object.membership_member_id">(<t t-out="object.membership_member_id"/>)</t>
|
|
with <t t-out="object.membership_org_name or 'us'"/> expired on
|
|
<t t-out="format_date(object.membership_expiry)"/>.
|
|
</p>
|
|
<p>A renewal invoice has been prepared for you. Please get in touch to reactivate your membership.</p>
|
|
</div>
|
|
</field>
|
|
</record>
|
|
|
|
</data>
|
|
</odoo>
|