feat(tablet): mail template + notification + cleanup cron (Task 5)

Mail template renders the 4-digit code in both subject (mobile
notification glance) and body (big bold display). Per Rule 25 only
core res.users fields referenced; the code itself comes from ctx.

fp.notification.template wrapper enables admin UI customization of
the body without touching code. tablet_pin_reset_requested added to
TRIGGER_EVENTS selection.

Daily ir.cron purges used/expired rows > 7 days old (audit trail
lives in fp.tablet.session.event, not here, so aggressive cleanup
is safe).

Manifest bump 19.0.34.2.0 -> 19.0.35.0.0 (triggers asset cache
invalidation on -u so the new template + SCSS load cleanly).

Phase 1 backend complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-25 16:51:25 -04:00
parent 46c62ebefa
commit 2aa4bce089
3 changed files with 72 additions and 1 deletions

View File

@@ -28,6 +28,8 @@ TRIGGER_EVENTS = [
('cert_awaiting_issuance', 'Cert Awaiting Issuance'),
('cert_voided_re_notify', 'Cert Voided — Please Re-Issue'),
('job_shipped', 'Job Shipped (manual mark)'),
# Spec 2026-05-25 — tablet PIN self-service reset
('tablet_pin_reset_requested', 'Tablet PIN Reset Code Requested'),
]
# Sub 6 — map each trigger event to a communication stream. Contacts on

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Shop Floor',
'version': '19.0.34.2.0',
'version': '19.0.35.0.0',
'category': 'Manufacturing/Plating',
'summary': 'Shop-floor tablet stations, QR scanning, bake window enforcer.',
'description': """
@@ -59,6 +59,7 @@ Copyright (c) 2026 Nexa Systems Inc. All rights reserved.
],
'demo': [
'data/fp_demo_shopfloor_data.xml',
'data/fp_tablet_pin_reset_template.xml',
],
'assets': {
'web.assets_backend': [

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Spec: docs/superpowers/specs/2026-05-25-tablet-pin-self-service-design.md
Email template + notification-template wrapper + cleanup cron for
the tablet PIN self-service reset flow.
Per CLAUDE.md Rule 25 the mail.template references ONLY core
res.users fields (object.name, object.email, object.login,
object.company_id). The 4-digit code is passed in via context
(ctx.code) when /fp/tablet/request_reset_code calls send_mail.
-->
<odoo noupdate="1">
<!-- ===== Mail template ============================================ -->
<record id="fp_mail_template_tablet_pin_reset" model="mail.template">
<field name="name">FP: Tablet PIN Reset Code</field>
<field name="model_id" ref="base.model_res_users"/>
<field name="subject">🔒 Your ENTECH tablet temporary PIN: {{ ctx.get('code', '----') }}</field>
<field name="email_from">{{ (object.company_id.email or user.email) }}</field>
<field name="email_to">{{ object.email or object.login }}</field>
<field name="auto_delete" eval="True"/>
<field name="body_html" type="html">
<div style="font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif; max-width: 600px; margin: 0 auto; padding: 32px 24px;">
<div style="height: 4px; background-color: #1d4ed8; margin-bottom: 28px;"></div>
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #1d4ed8; font-weight: 600; margin-bottom: 8px;">
Electroless Nickel Technologies Inc. (ENTECH)
</div>
<h2 style="margin: 0 0 8px 0; font-size: 22px; font-weight: bold;">Your tablet temporary PIN</h2>
<p style="margin: 0 0 20px 0; font-size: 15px; opacity: 0.75;">
Hi <t t-out="object.name"/>, use this 4-digit PIN to unlock the
shop-floor tablet and set a new permanent PIN.
</p>
<div style="text-align: center; margin: 32px 0; padding: 24px; background: #f3f4f6; border-radius: 8px; font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 48px; font-weight: 700; letter-spacing: 0.3em; color: #1d4ed8;">
<t t-out="ctx.get('code', '----')"/>
</div>
<p style="margin: 16px 0; font-size: 13px; opacity: 0.65;">
This code expires in 72 hours. If you didn't request it, ignore
this email — no action needed. The previous PIN (if any) stays
valid until you successfully complete the reset on the tablet.
</p>
</div>
</field>
</record>
<!-- ===== fp.notification.template wrapper ========================= -->
<record id="fp_notif_tablet_pin_reset" model="fp.notification.template">
<field name="name">Tablet PIN Reset Code</field>
<field name="trigger_event">tablet_pin_reset_requested</field>
<field name="mail_template_id" ref="fp_mail_template_tablet_pin_reset"/>
<field name="active" eval="True"/>
</record>
<!-- ===== Cleanup cron ============================================ -->
<record id="cron_purge_expired_pin_resets" model="ir.cron">
<field name="name">Fusion Plating: Purge expired tablet PIN reset codes</field>
<field name="model_id" ref="model_fp_tablet_pin_reset"/>
<field name="state">code</field>
<field name="code">model._cron_purge_expired()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="active" eval="True"/>
</record>
</odoo>