Findings from the post-feature code review on commit 396170b4. Addresses
the two CRITICAL + one HIGH + two MEDIUM issues; rest are deferred.
CRITICAL #1 — magic-link token race:
Two near-simultaneous POSTs on the same /engagement/<token>/approve
could both SELECT state='pending' under READ COMMITTED, both post
chatter, and let the last writer flip the outcome. Now the POST path
does an atomic UPDATE helpdesk_ticket SET token=NULL WHERE token=%s
AND state='pending' RETURNING id — the loser gets no row back and
renders the friendly invalid-link page. Verified live: 2 concurrent
POSTs → 1 wins, 1 loses, exactly 1 chatter row.
CRITICAL #2 — reminder cron without per-row savepoint:
Per CLAUDE.md rule #14, a DB failure mid-loop aborts the whole
transaction and silently kills the rest of the batch. Wrap each row's
send_mail+write in `with self.env.cr.savepoint()`. Also corrected the
success-count log (was len(stale), now actual sent count).
HIGH #3 — turnaround pivot summed instead of averaged:
fields.Float defaults to SUM aggregator; meaningless for per-ticket
decision delays. Added aggregator='avg' so the pivot reads "avg
turnaround per ticket" not "summed wait time".
HIGH #4 — added test_concurrent_claim_only_one_wins regression test
that fires two real HTTP POSTs against the same token and asserts
exactly one wins + exactly one approval chatter row exists.
MEDIUM #6 — cron nextcall pinned to 09:00 tomorrow so reminders land
in business hours regardless of when the module was last upgraded.
MEDIUM #10 — escalate failed owner-partner-create from WARNING to
ERROR (via _logger.exception) since silent attribution to the bot
account is a real audit-trail confusion.
Deferred (follow-up commits): #5, #7 (executor cleanup), #8, #9,
#11–#14 — none are bugs, all spec-drift or hardening.
40 lines
1.7 KiB
XML
40 lines
1.7 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!--
|
|
Copyright 2026 Nexa Systems Inc.
|
|
License OPL-1
|
|
|
|
Single-shot reminder cron for the owner-approval engagement flow.
|
|
Runs once a day; for each pending engagement older than N days that
|
|
hasn't been reminded yet, re-sends the same template with
|
|
is_reminder=True (different subject + soft "still waiting" intro,
|
|
same magic links). Sets reminded_at so we never send a 2nd reminder.
|
|
|
|
Odoo 19: no `numbercall` field (dropped). The cron keeps running
|
|
while active=True; once-a-day cadence is enforced by interval_*.
|
|
-->
|
|
<odoo>
|
|
<data noupdate="1">
|
|
|
|
<record id="ir_cron_engagement_reminder" model="ir.cron">
|
|
<field name="name">Fusion Helpdesk — Owner Engagement Reminder</field>
|
|
<field name="model_id" ref="helpdesk.model_helpdesk_ticket"/>
|
|
<field name="state">code</field>
|
|
<field name="code">model._fc_send_engagement_reminders()</field>
|
|
<field name="user_id" ref="base.user_root"/>
|
|
<field name="interval_number">1</field>
|
|
<field name="interval_type">days</field>
|
|
<!--
|
|
Pin the first run to 09:00 tomorrow so subsequent daily
|
|
fires land in business hours. Without this, Odoo defaults
|
|
nextcall to install-time → reminders go out at 2am if you
|
|
happened to deploy at 2am, which is wrong (owners shouldn't
|
|
get pinged in the middle of the night).
|
|
-->
|
|
<field name="nextcall"
|
|
eval="(DateTime.now().replace(hour=9, minute=0, second=0, microsecond=0) + timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
<field name="active" eval="True"/>
|
|
</record>
|
|
|
|
</data>
|
|
</odoo>
|