Files
Odoo-Modules/docs/superpowers/plans/2026-06-02-fusion-maintenance-foundation.md
gsinghpal cc568b0ec8 docs(fusion_maintenance): Plan 1 (Foundation) implementation plan + Plans 2-5 roadmap
TDD plan for the enrollment+pricing foundation: maintenance policy fields on the equipment category (+ product fee override), maintenance-contract extensions, fix+wire the dead _spawn_maintenance_contracts into the existing action_confirm (delivery-date anchor, two-regime serial dedup, fee snapshot), fee line in the reminder email, category UI, version 19.0.2.3.0. Grounded in real source. Plans 2-5 (booking on fusion_tasks, visit log + checklist, two-regime backfill, office crons) roadmapped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 01:27:38 -04:00

507 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# fusion_maintenance Foundation — Implementation Plan (Plan 1 of 5)
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Confirming a sale of a maintainable product auto-creates a *priced* maintenance contract, and the due-reminder email shows the maintenance cost.
**Architecture:** Extend `fusion_repairs`. A maintenance **policy** (enabled / interval / flat fee) lives on `fusion.repair.product.category`, with a per-product fee/interval override on `product.template`. We fix the dead `_spawn_maintenance_contracts()` (anchor on delivery date, capture serial + fee + provenance, dedup) and call it from the **existing** `action_confirm()` override. The branded reminder email gains a fee line.
**Tech Stack:** Odoo 19 **Community**, Python, `TransactionCase`. Local dev: `docker odoo-modsdev-app`, DB `fusion-dev`.
**Spec:** [`2026-06-02-fusion-maintenance-design.md`](../specs/2026-06-02-fusion-maintenance-design.md). This is **Plan 1 of 5**; see the Roadmap at the bottom for Plans 25 (booking, visit log, backfill, office crons) — each is written when reached because it needs its own live-source reads (spec §15).
**Conventions (from CLAUDE.md):** new fields `x_fc_` prefix; Canadian English; Monetary = `$` + `currency_id`; declarative `models.Constraint` / `models.Index` (no `_sql_constraints`); `message_post` HTML wrapped in `Markup()`; `res.users` group field is `group_ids`.
**Run tests:**
```bash
docker exec odoo-modsdev-app odoo -d fusion-dev --test-enable --test-tags /fusion_repairs \
-u fusion_repairs --stop-after-init --http-port=0 --gevent-port=0 2>&1 | tail -60
```
**Grounding (verified source, 2026-06-02):**
- [`maintenance_contract.py`](../../../fusion_repairs/models/maintenance_contract.py) — contract model (fields end at `company_id`, line 81; `_booking_token_unique` constraint line 83); dead `_spawn_maintenance_contracts()` (line 198, anchors on `today`, dedups by partner/product/SO, no fee/serial/source).
- [`repair_product_category.py`](../../../fusion_repairs/models/repair_product_category.py) — category model; `safety_critical`, `equipment_class`; `_code_unique` constraint line 56.
- [`product_template.py`](../../../fusion_repairs/models/product_template.py) — `x_fc_repair_category_id` (line 11), `x_fc_maintenance_interval_months` (line 23, default 0).
- [`repair_service_plan.py`](../../../fusion_repairs/models/repair_service_plan.py) — **existing** `action_confirm()` override (line 229) ending `return res` (line 250); wire the maintenance spawn here.
---
## File Structure
- **Modify** `fusion_repairs/models/repair_product_category.py` — add maintenance-policy fields + `currency_id`.
- **Modify** `fusion_repairs/models/product_template.py` — add `x_fc_maintenance_fee` override.
- **Modify** `fusion_repairs/models/maintenance_contract.py` — add contract fields + indexes; add `_fc_maintenance_anchor_date`; rewrite `_spawn_maintenance_contracts`.
- **Modify** `fusion_repairs/models/repair_service_plan.py` — call `self._spawn_maintenance_contracts()` inside `action_confirm`.
- **Modify** `fusion_repairs/data/mail_template_data.xml` — add a fee row to the reminder template.
- **Modify** `fusion_repairs/views/repair_product_category_views.xml` — expose the policy fields.
- **Create** `fusion_repairs/tests/__init__.py`, `fusion_repairs/tests/test_maintenance_foundation.py`.
- **Modify** `fusion_repairs/__manifest__.py` — bump `version` to `19.0.2.3.0`.
> **Scope note:** the technician-skill field (`x_fc_maintenance_skill_id`) is deferred to **Plan 2 (booking)** because skill matching is a booking concern and the exact skills representation is an open item (spec §15). Plan 1 is enrollment + pricing only.
---
## Task 1: Maintenance policy fields on the equipment category
**Files:**
- Modify: `fusion_repairs/models/repair_product_category.py` (insert after `intake_template_id`, before `_code_unique` at line 56)
- Test: `fusion_repairs/tests/test_maintenance_foundation.py`
- [ ] **Step 1: Create the tests package + write the failing test**
Create `fusion_repairs/tests/__init__.py`:
```python
from . import test_maintenance_foundation
```
Create `fusion_repairs/tests/test_maintenance_foundation.py`:
```python
# -*- coding: utf-8 -*-
from odoo.tests import TransactionCase, tagged
@tagged('post_install', '-at_install')
class TestMaintenanceFoundation(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner = cls.env['res.partner'].create({'name': 'Mrs. Test Client'})
cls.category = cls.env['fusion.repair.product.category'].create({
'name': 'Stair Lift', 'code': 'stairlift',
'equipment_class': 'lift_elevating', 'safety_critical': True,
'x_fc_maintenance_enabled': True,
'x_fc_maintenance_interval_months': 6,
'x_fc_maintenance_fee': 149.0,
})
def test_category_policy_fields_exist(self):
self.assertTrue(self.category.x_fc_maintenance_enabled)
self.assertEqual(self.category.x_fc_maintenance_interval_months, 6)
self.assertEqual(self.category.x_fc_maintenance_fee, 149.0)
self.assertTrue(self.category.currency_id)
```
- [ ] **Step 2: Run the test to verify it fails**
Run:
```bash
docker exec odoo-modsdev-app odoo -d fusion-dev --test-enable --test-tags /fusion_repairs -u fusion_repairs --stop-after-init --http-port=0 --gevent-port=0 2>&1 | tail -40
```
Expected: FAIL — `Invalid field 'x_fc_maintenance_enabled' on model 'fusion.repair.product.category'`.
- [ ] **Step 3: Add the policy fields**
In `repair_product_category.py`, insert before the `_code_unique = models.Constraint(...)` line:
```python
# ── Maintenance policy (per equipment type) ──────────────────────────
x_fc_maintenance_enabled = fields.Boolean(
string='Offer Maintenance',
help='If set, units in this category are enrolled in recurring preventive '
'maintenance on sale (and via the backfill wizard).',
)
x_fc_maintenance_interval_months = fields.Integer(
string='Maintenance Interval (Months)', default=6,
help='Default months between preventive maintenance visits for this category. '
'Overridden by the product field of the same name when that is > 0.',
)
currency_id = fields.Many2one(
'res.currency', string='Currency',
default=lambda self: self.env.company.currency_id,
)
x_fc_maintenance_fee = fields.Monetary(
string='Maintenance Fee', currency_field='currency_id',
help='Flat fee shown to the client for a maintenance visit of this equipment type.',
)
x_fc_maintenance_service_product_id = fields.Many2one(
'product.product', string='Maintenance Service Product',
help='Optional product used when drafting the priced visit line (Plan 2). '
'Falls back to a generic visit product.',
)
```
- [ ] **Step 4: Run the test to verify it passes**
Run the same command as Step 2. Expected: `test_category_policy_fields_exist` PASS.
- [ ] **Step 5: Commit**
```bash
git add fusion_repairs/models/repair_product_category.py fusion_repairs/tests/
git commit -m "feat(fusion_repairs): maintenance policy fields on equipment category"
```
---
## Task 2: Per-product fee override
**Files:**
- Modify: `fusion_repairs/models/product_template.py` (after `x_fc_maintenance_interval_months`, line 28)
- Test: `fusion_repairs/tests/test_maintenance_foundation.py`
- [ ] **Step 1: Write the failing test** (append to the test class)
```python
def test_product_fee_override_field_exists(self):
tmpl = self.env['product.template'].create({
'name': 'Handicare Freecurve Stairlift',
'x_fc_repair_category_id': self.category.id,
'x_fc_maintenance_fee': 199.0,
})
self.assertEqual(tmpl.x_fc_maintenance_fee, 199.0)
```
- [ ] **Step 2: Run to verify it fails**
Run the test command. Expected: FAIL — `Invalid field 'x_fc_maintenance_fee' on model 'product.template'`.
- [ ] **Step 3: Add the field**
In `product_template.py`, after the `x_fc_maintenance_interval_months` field (line 28):
```python
x_fc_maintenance_fee = fields.Monetary(
string='Maintenance Fee (override)', currency_field='currency_id',
help='Per-product override of the category maintenance fee. 0 = use the category fee.',
)
```
(`product.template` already provides `currency_id`.)
- [ ] **Step 4: Run to verify it passes**`test_product_fee_override_field_exists` PASS.
- [ ] **Step 5: Commit**
```bash
git add fusion_repairs/models/product_template.py fusion_repairs/tests/test_maintenance_foundation.py
git commit -m "feat(fusion_repairs): per-product maintenance fee override"
```
---
## Task 3: Contract model extensions (fee, source, serial, policy)
**Files:**
- Modify: `fusion_repairs/models/maintenance_contract.py` (add fields after `company_id`, line 81; add indexes near `_booking_token_unique`, line 83)
- Test: `fusion_repairs/tests/test_maintenance_foundation.py`
- [ ] **Step 1: Write the failing test**
```python
def test_contract_extension_fields_exist(self):
c = self.env['fusion.repair.maintenance.contract'].create({
'partner_id': self.partner.id,
'product_id': self.env['product.product'].create({'name': 'Unit'}).id,
'next_due_date': '2026-12-01',
'x_fc_source': 'sale',
'x_fc_device_serial': 'SN-123',
'x_fc_maintenance_fee': 149.0,
})
self.assertEqual(c.x_fc_source, 'sale')
self.assertEqual(c.x_fc_device_serial, 'SN-123')
self.assertEqual(c.x_fc_maintenance_fee, 149.0)
```
- [ ] **Step 2: Run to verify it fails**`Invalid field 'x_fc_source' ...`.
- [ ] **Step 3: Add the fields + indexes**
In `maintenance_contract.py`, after the `company_id` field (line 81), before `_booking_token_unique`:
```python
currency_id = fields.Many2one(
'res.currency', default=lambda self: self.env.company.currency_id,
)
x_fc_maintenance_fee = fields.Monetary(
string='Maintenance Fee', currency_field='currency_id',
help='Flat fee shown to the client for this maintenance visit.',
)
x_fc_source = fields.Selection(
[('sale', 'New Sale'), ('backfill', 'Backfill'),
('claims', 'Claims Bridge'), ('manual', 'Manual')],
string='Source', default='manual', index=True,
)
x_fc_source_sale_line_id = fields.Many2one(
'sale.order.line', string='Source Sale Line', index=True, copy=False,
)
x_fc_device_serial = fields.Char(string='Serial (text)', index=True, copy=False)
x_fc_policy_category_id = fields.Many2one(
'fusion.repair.product.category', string='Maintenance Policy',
)
```
(Idempotency is enforced in Python — Task 4 — to support the two-regime dedup in spec §6.2; the `index=True` above covers lookups.)
- [ ] **Step 4: Run to verify it passes**`test_contract_extension_fields_exist` PASS.
- [ ] **Step 5: Commit**
```bash
git add fusion_repairs/models/maintenance_contract.py fusion_repairs/tests/test_maintenance_foundation.py
git commit -m "feat(fusion_repairs): maintenance contract fee/source/serial/policy fields"
```
---
## Task 4: Spawn priced contracts on sale confirm (fix the dead trigger + wire it)
**Files:**
- Modify: `fusion_repairs/models/maintenance_contract.py` (rewrite `_spawn_maintenance_contracts`, lines 198-227; add `_fc_maintenance_anchor_date` helper)
- Modify: `fusion_repairs/models/repair_service_plan.py` (call it in `action_confirm`, before `return res` at line 250)
- Test: `fusion_repairs/tests/test_maintenance_foundation.py`
- [ ] **Step 1: Write the failing tests**
```python
def _make_product(self, **kw):
vals = {'name': 'Stairlift Unit', 'type': 'consu',
'x_fc_repair_category_id': self.category.id}
vals.update(kw)
return self.env['product.product'].create(vals)
def _confirm_so(self, product, commitment='2026-01-10'):
so = self.env['sale.order'].create({
'partner_id': self.partner.id,
'commitment_date': commitment,
'order_line': [(0, 0, {'product_id': product.id, 'product_uom_qty': 1})],
})
so.action_confirm()
return so
def _contracts_for(self, so):
return self.env['fusion.repair.maintenance.contract'].search(
[('original_sale_order_id', '=', so.id)])
def test_no_contract_when_category_not_maintainable(self):
cat = self.env['fusion.repair.product.category'].create(
{'name': 'Cane', 'code': 'cane', 'x_fc_maintenance_enabled': False})
so = self._confirm_so(self._make_product(x_fc_repair_category_id=cat.id))
self.assertFalse(self._contracts_for(so))
def test_contract_created_via_category_policy(self):
so = self._confirm_so(self._make_product())
contracts = self._contracts_for(so)
self.assertEqual(len(contracts), 1)
c = contracts
self.assertEqual(c.interval_months, 6)
self.assertEqual(c.x_fc_maintenance_fee, 149.0)
self.assertEqual(c.x_fc_source, 'sale')
self.assertEqual(c.x_fc_policy_category_id, self.category)
# anchor = commitment_date + 6 months
self.assertEqual(str(c.next_due_date), '2026-07-10')
def test_product_override_beats_category(self):
p = self._make_product()
p.product_tmpl_id.x_fc_maintenance_interval_months = 3
p.product_tmpl_id.x_fc_maintenance_fee = 199.0
so = self._confirm_so(p)
c = self._contracts_for(so)
self.assertEqual(c.interval_months, 3)
self.assertEqual(c.x_fc_maintenance_fee, 199.0)
def test_idempotent_on_reconfirm(self):
p = self._make_product()
so = self._confirm_so(p)
so._spawn_maintenance_contracts() # call again
self.assertEqual(len(self._contracts_for(so)), 1)
```
- [ ] **Step 2: Run to verify they fail** — contracts not created (trigger not wired) → assertions fail.
- [ ] **Step 3: Rewrite `_spawn_maintenance_contracts` + add the anchor helper**
Replace the body of `_spawn_maintenance_contracts` (lines 198-227) and add the helper, in the `SaleOrder` class of `maintenance_contract.py`:
```python
def _fc_maintenance_anchor_date(self, line):
"""Best-available delivery anchor: commitment_date -> date_order -> today.
(Non-ADP/lift units lack a delivery date; this fallback chain handles them.)"""
so = line.order_id
anchor = so.commitment_date or so.date_order
return fields.Date.to_date(anchor) if anchor else fields.Date.context_today(self)
def _spawn_maintenance_contracts(self):
"""Create a priced maintenance contract per maintainable unit on a confirmed SO.
Policy = product interval override, else the product's category policy.
Idempotent: by serial when captured, else by source sale line."""
Contract = self.env['fusion.repair.maintenance.contract'].sudo()
for so in self:
if so.state not in ('sale', 'done'):
continue
for line in so.order_line:
product = line.product_id
if not product:
continue
tmpl = product.product_tmpl_id
category = tmpl.x_fc_repair_category_id
product_interval = tmpl.x_fc_maintenance_interval_months or 0
cat_enabled = bool(category) and category.x_fc_maintenance_enabled
interval = product_interval or (
category.x_fc_maintenance_interval_months if cat_enabled else 0)
if interval <= 0 or not (product_interval > 0 or cat_enabled):
continue
fee = tmpl.x_fc_maintenance_fee or (
category.x_fc_maintenance_fee if category else 0.0)
# Capture serial only if fusion_claims' line field is present.
serial = ''
if 'x_fc_serial_number' in line._fields:
serial = (line.x_fc_serial_number or '').strip()
# Idempotency: serial regime vs source-line regime (spec §6.2).
if serial:
dedup = [('state', '=', 'active'), ('x_fc_device_serial', '=', serial)]
else:
dedup = [('state', '=', 'active'),
('x_fc_source_sale_line_id', '=', line.id)]
if Contract.search_count(dedup):
continue
anchor = so._fc_maintenance_anchor_date(line)
# One contract per serialized unit; without a serial, per quantity.
count = 1 if serial else max(int(line.product_uom_qty or 1), 1)
for _i in range(count):
Contract.create({
'partner_id': so.partner_id.id,
'product_id': product.id,
'original_sale_order_id': so.id,
'x_fc_source_sale_line_id': line.id,
'x_fc_source': 'sale',
'x_fc_device_serial': serial,
'x_fc_policy_category_id': category.id if category else False,
'interval_months': interval,
'x_fc_maintenance_fee': fee,
'next_due_date': anchor + relativedelta(months=interval),
'state': 'active',
})
```
- [ ] **Step 4: Wire it into the existing `action_confirm`**
In `repair_service_plan.py`, in `action_confirm`, change line 249-250 from:
```python
self._fc_spawn_labor_warranties()
return res
```
to:
```python
self._fc_spawn_labor_warranties()
self._spawn_maintenance_contracts()
return res
```
- [ ] **Step 5: Run to verify the Task-4 tests pass** — all four PASS.
- [ ] **Step 6: Commit**
```bash
git add fusion_repairs/models/maintenance_contract.py fusion_repairs/models/repair_service_plan.py fusion_repairs/tests/test_maintenance_foundation.py
git commit -m "feat(fusion_repairs): spawn priced maintenance contracts on sale confirm"
```
---
## Task 5: Show the fee in the reminder email
**Files:**
- Modify: `fusion_repairs/data/mail_template_data.xml` (the `email_template_maintenance_due_reminder` record)
- [ ] **Step 1: Read the current template**
Run:
```bash
docker exec odoo-modsdev-app sh -c "grep -n 'email_template_maintenance_due_reminder' /mnt/odoo-modules/fusion_repairs/data/mail_template_data.xml"
```
Then open that record's `<field name="body_html">` and find the equipment-name / due-date details table (the green-accent reminder).
- [ ] **Step 2: Add a fee row to the details table**
Inside the details table of the reminder body, after the "Next due" row, add (Canadian English, `$` + currency):
```xml
<tr t-if="object.x_fc_maintenance_fee">
<td style="opacity:0.6;width:35%;">Maintenance fee</td>
<td><span t-field="object.x_fc_maintenance_fee"
t-options='{"widget": "monetary", "display_currency": object.currency_id}'/>
<span style="opacity:0.6;"> + applicable tax</span></td>
</tr>
```
- [ ] **Step 3: Upgrade + manually verify the rendered email**
Run:
```bash
docker exec odoo-modsdev-app odoo -d fusion-dev -u fusion_repairs --stop-after-init
```
Then in odoo-shell render the template for a contract with a fee and confirm the fee line appears:
```bash
docker exec odoo-modsdev-app odoo shell -d fusion-dev --no-http <<'PY'
c = env['fusion.repair.maintenance.contract'].search([('x_fc_maintenance_fee','>',0)], limit=1)
tpl = env.ref('fusion_repairs.email_template_maintenance_due_reminder')
print('FEE' if 'applicable tax' in tpl._render_field('body_html', c.ids)[c.id] else 'MISSING')
PY
```
Expected: `FEE`.
- [ ] **Step 4: Commit**
```bash
git add fusion_repairs/data/mail_template_data.xml
git commit -m "feat(fusion_repairs): show maintenance fee in due-reminder email"
```
---
## Task 6: Expose policy fields in the category form + bump version
**Files:**
- Modify: `fusion_repairs/views/repair_product_category_views.xml`
- Modify: `fusion_repairs/__manifest__.py`
- [ ] **Step 1: Read the category form view**
Run:
```bash
docker exec odoo-modsdev-app sh -c "grep -n 'fusion.repair.product.category' /mnt/odoo-modules/fusion_repairs/views/repair_product_category_views.xml | head"
```
Locate the `<form>` for the category.
- [ ] **Step 2: Add a Maintenance group to the form**
Inside the category form sheet, add:
```xml
<group string="Maintenance Policy">
<field name="x_fc_maintenance_enabled"/>
<field name="x_fc_maintenance_interval_months"
invisible="not x_fc_maintenance_enabled"/>
<field name="x_fc_maintenance_fee"
invisible="not x_fc_maintenance_enabled"/>
<field name="x_fc_maintenance_service_product_id"
invisible="not x_fc_maintenance_enabled"/>
<field name="currency_id" invisible="1"/>
</group>
```
- [ ] **Step 3: Bump the version**
In `fusion_repairs/__manifest__.py`, change `'version': '19.0.2.2.6',` to `'version': '19.0.2.3.0',`.
- [ ] **Step 4: Upgrade + run the full test module green**
Run:
```bash
docker exec odoo-modsdev-app odoo -d fusion-dev --test-enable --test-tags /fusion_repairs -u fusion_repairs --stop-after-init --http-port=0 --gevent-port=0 2>&1 | tail -40
```
Expected: all `TestMaintenanceFoundation` tests PASS, 0 failures, module loads.
- [ ] **Step 5: Commit**
```bash
git add fusion_repairs/views/repair_product_category_views.xml fusion_repairs/__manifest__.py
git commit -m "feat(fusion_repairs): category maintenance-policy UI + version 19.0.2.3.0"
```
---
## Self-Review (against the spec)
- **Spec §2 D2 (flat fee per type):** Tasks 1-2 (policy on category + product override), Task 4 (fee snapshot on contract), Task 5 (fee in email). ✓
- **Spec §3.2 gap #1 (dead trigger):** Task 4 fixes + wires `_spawn_maintenance_contracts`. ✓
- **Spec §3.2 gap #3 (no cost shown):** Task 5. ✓
- **Spec §5.1 / §5.2 (policy + contract fields):** Tasks 1-3. ✓
- **Spec §6.1 (new-sale path, delivery anchor, idempotent, serial when present):** Task 4 (`_fc_maintenance_anchor_date`, two-regime dedup, guarded serial capture). ✓
- **Deferred to Plan 2:** `x_fc_maintenance_skill_id` (skills representation is §15 open item) — noted in File Structure.
- **No placeholders:** every code step shows complete code; the two "read first" steps (Tasks 5-6) target XML whose exact surrounding markup must be read live before editing, and give the exact snippet to insert.
- **Type consistency:** `x_fc_maintenance_fee` Monetary + `currency_id` used identically on category, product, contract; `_spawn_maintenance_contracts` / `_fc_maintenance_anchor_date` names consistent between maintenance_contract.py and the call site in repair_service_plan.py.
---
## Roadmap — Plans 25 (write each when reached; each needs its own live-source reads per spec §15)
- **Plan 2 — Technician-aware booking** (the largest build): read `fusion_tasks/models/technician_task.py` `_find_next_available_slot` (line 544) / `_get_available_gaps` (line 664) signatures + working-hours source; add `x_fc_maintenance_skill_id` to the category and confirm the `res.users.x_fc_repair_skills` representation; replace the `<input type="date">` booking page with a real slot-picker controller; on confirm create a `fusion.technician.task` (`task_type='maintenance'`) + the maintenance `repair.order`; double-book guard; office "Book maintenance" action; per-cycle `booking_token` regen in `roll_next_due_date`. Delivers: real self-serve booking.
- **Plan 3 — Maintenance visit log + checklist**: read the visit-report wizard + the inspection-certificate (M1) API; add `fusion.repair.maintenance.visit` + `fusion.repair.maintenance.checklist.line`; seed checklists per category; issue an inspection certificate for `safety_critical` categories. Delivers: queryable per-unit history + compliance proof.
- **Plan 4 — Backfill wizard** (two-regime, spec §6.2): `fusion.repair.maintenance.backfill.wizard`; serial dedup for ADP wheelchairs (guarded `fusion_claims` read), partner+base-product+sale-line dedup for lifts with accessory-line exclusion; stagger; dry-run report → execute. Delivers: the existing install base enrolled.
- **Plan 5 — Office follow-up crons**: `unbooked` + `overdue` crons gated on the existing `ir.config_parameter` toggles; per-row savepoint isolation. Delivers: staff nudges when clients don't self-serve.