folder rename

This commit is contained in:
gsinghpal
2026-04-16 20:53:53 -04:00
parent 3f3ddcbab4
commit 7c7ef06057
634 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
# Fusion Plating — Quality Bridge (EE)
Part of the Fusion Plating product family by Nexa Systems Inc.
One-way sync from Fusion Plating NCRs to Odoo Enterprise `quality.alert`
records. Lets plating shops running Odoo EE keep using the Fusion Plating
QMS (CE + EE compatible) while getting the benefit of EE's native quality
dashboards on the same data.
## Status
| Item | Value |
| --- | --- |
| Odoo version | 19.0 |
| License | OPL-1 |
| Auto-install | Yes — when `fusion_plating_quality` AND EE `quality` are both installed |
| Sync direction | One-way: NCR → quality.alert |
## How it works
1. Creating or updating a `fusion.plating.ncr` record triggers
`_sync_to_quality_alert()` in `models/fp_ncr.py`.
2. The bridge builds a vals dict from the NCR and creates (or updates) the
linked `quality.alert` record under a dedicated **Plating Quality** team.
3. The bridge is defensive: every field write is gated on the live
`quality.alert` schema so a minor EE release that renames or drops a
field does not break NCR saves.
4. If `quality.alert` is not available for any reason, the sync silently
no-ops — NCRs continue to work normally in the base QMS module.
## Field mapping
| Fusion Plating NCR | Odoo EE quality.alert |
| --- | --- |
| `description` (first line) | `name` / `title` |
| `description` (full html) | `description` (if html) or stripped text |
| `containment` | `action_corrective` (if present) |
| `root_cause` | `action_preventive` (if present) |
| `severity` | `priority` (0/1/2/3 star) |
| `state` | `stage_id` (best-effort name match) |
| `customer_partner_id` | `partner_id` |
| `company_id` | `company_id` |
| — | `team_id` (always "Plating Quality") |
## Per-record controls
Each NCR gains three `x_fc_*` fields (visible under the **Quality Alert
Sync** notebook page on the NCR form):
- `x_fc_auto_sync` — toggle per-record sync; set to False to freeze the
mirror on a specific NCR
- `x_fc_quality_alert_id` — read-only pointer to the mirrored alert
- `x_fc_quality_alert_synced` — read-only "has been synced at least once"
flag
## Manual sync
Use the **Sync to Quality** header button on the NCR form to push the
latest values on demand. The mirrored alert's **Quality Alert** smart
button jumps straight to the linked EE record.
## What the bridge does NOT do
- **No write-back.** Edits to the EE `quality.alert` record are not
propagated back into Fusion Plating. NCR is the source of truth.
- **No CAPA mirroring.** Fusion Plating CAPAs remain in
`fusion.plating.capa`; they are not copied into `quality.point` or
`quality.check`.
- **No historical backfill.** Existing NCRs created before this module was
installed are not synced automatically — run the **Sync to Quality**
button manually on the ones you want mirrored.
## Notes for deployment
- If your EE release uses `quality.team` instead of `quality.alert.team`,
edit `data/quality_alert_team_data.xml` to match and reinstall the
module. No Python changes required.
- The sync runs with `sudo()` so shop operators without direct write
access to the EE quality module still produce mirrored alerts cleanly.
Copyright (c) 2026 Nexa Systems Inc. All rights reserved.

View File

@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from . import models

View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
{
'name': 'Fusion Plating — Quality Bridge (EE)',
'version': '19.0.1.0.0',
'category': 'Manufacturing/Plating',
'summary': 'Enterprise bridge: mirrors Fusion Plating NCRs into Odoo EE '
'quality.alert for native dashboard integration. Auto-installs '
'when EE quality and fusion_plating_quality are both present.',
'description': """
Fusion Plating — Quality Bridge (Enterprise)
============================================
Part of the Fusion Plating product family by Nexa Systems Inc.
One-way sync from Fusion Plating NCRs to Odoo EE quality.alert records.
Shops on Odoo Enterprise running both modules continue to work primarily
in Fusion Plating's QMS (which is CE + EE compatible), while getting the
benefit of EE's native quality dashboards on the same data.
How it works
------------
* When a Fusion Plating NCR is created or updated, a mirrored
``quality.alert`` record is created (or updated) automatically.
* The sync is ONE-WAY — NCR is the source of truth. Changes made
directly to the EE quality.alert are NOT pushed back into the NCR.
* A dedicated "Plating Quality" team is created in the EE quality
module so mirrored alerts land in their own distinct team and do
not pollute the default quality dashboards.
* Per-NCR opt-out is available via the ``x_fc_auto_sync`` field.
Installation
------------
This module has ``auto_install = True``: it installs itself automatically
as soon as both ``fusion_plating_quality`` and ``quality`` (EE) are
present in the database. Community Edition users who do not have the EE
``quality`` module will never see this bridge — the Fusion Plating QMS
remains fully self-contained.
Copyright (c) 2026 Nexa Systems Inc. All rights reserved.
""",
'author': 'Nexa Systems Inc.',
'website': 'https://www.nexasystems.ca',
'maintainer': 'Nexa Systems Inc.',
'support': 'support@nexasystems.ca',
'license': 'OPL-1',
'price': 0.00,
'currency': 'CAD',
'depends': [
'fusion_plating_quality',
'quality',
],
'data': [
'security/ir.model.access.csv',
'data/quality_alert_team_data.xml',
'views/fp_ncr_views.xml',
],
'installable': True,
'application': False,
'auto_install': True,
}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
Creates a dedicated "Plating Quality" team in the Odoo EE quality module
so mirrored NCR alerts land in their own bucket and do not pollute the
shop's default quality dashboards.
NOTE ON MODEL NAME:
In recent Odoo 19 EE releases the team model on quality.alert is
``quality.alert.team``. Older EE releases used ``quality.team``. We
target the current name here; if a shop is on an older release and
gets a parsing error loading this file, swap the model to ``quality.team``
and reinstall. No Python changes are required because the bridge resolves
the team purely by XML ID.
-->
<odoo noupdate="1">
<record id="quality_team_plating" model="quality.alert.team">
<field name="name">Plating Quality</field>
</record>
</odoo>

View File

@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from . import fp_ncr

View File

@@ -0,0 +1,289 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
import logging
from odoo import _, api, fields, models
_logger = logging.getLogger(__name__)
# Map Fusion Plating NCR states to a human readable stage name we will
# try to match (case-insensitive, partial match) against any available
# quality.alert.stage records. If no match is found, the alert is left
# with its default stage and Odoo's own onchange will pick one.
_STATE_TO_STAGE_HINT = {
'draft': 'new',
'open': 'in progress',
'containment': 'in progress',
'disposition': 'in progress',
'closed': 'done',
}
# Fusion Plating severity -> quality.alert priority (0..3 star scale
# used across mrp/quality). Kept defensive: priority is written via
# setdefault, so if a shop has customised the field we don't clobber it.
_SEVERITY_TO_PRIORITY = {
'low': '0',
'medium': '1',
'high': '2',
'critical': '3',
}
class FpNcr(models.Model):
"""Extend Fusion Plating NCR so each record is mirrored into the
Odoo EE ``quality.alert`` model. One-way sync, NCR is the source
of truth.
"""
_inherit = 'fusion.plating.ncr'
x_fc_quality_alert_id = fields.Many2one(
'quality.alert',
string='Quality Alert',
copy=False,
readonly=True,
help='Mirrored Odoo EE quality.alert record created from this NCR.',
)
x_fc_quality_alert_synced = fields.Boolean(
string='Synced to Quality',
copy=False,
readonly=True,
help='True once this NCR has been mirrored to quality.alert at '
'least once.',
)
x_fc_auto_sync = fields.Boolean(
string='Auto-sync to Quality',
default=True,
copy=False,
help='When enabled, creating or updating this NCR automatically '
'pushes changes to the mirrored Odoo EE quality.alert record.',
)
# ------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------
def _fp_bridge_strip_html(self, html_value):
"""Convert an HTML field value to plain text for quality.alert
fields that are plain Text. Kept tiny on purpose so it has no
dependencies beyond what's already in core Odoo."""
if not html_value:
return ''
try:
from odoo.tools import html2plaintext
return html2plaintext(html_value) or ''
except Exception: # pragma: no cover - extremely defensive
return str(html_value)
def _fp_bridge_team(self):
"""Return the dedicated Plating team record, if it exists."""
return self.env.ref(
'fusion_plating_bridge_quality.quality_team_plating',
raise_if_not_found=False,
)
def _fp_bridge_stage_for_state(self, state_value):
"""Best-effort mapping of our NCR state to a quality.alert.stage.
The EE quality module ships with a handful of default stages
(``New``, ``In Progress``, ``Confirmed``, ``Solved``, ``Cancelled``)
but shops often rename these. We do a case-insensitive partial
match against the hint and fall back to False so Odoo picks the
default stage itself.
"""
Stage = self.env.get('quality.alert.stage')
if Stage is None:
return False
hint = _STATE_TO_STAGE_HINT.get(state_value or '', '')
if not hint:
return False
stage = Stage.sudo().search(
[('name', 'ilike', hint)],
limit=1,
)
return stage.id if stage else False
def _prepare_quality_alert_vals(self):
"""Build the vals dict used to create/update a quality.alert from
this NCR. Written defensively — every field is checked against
the live ``quality.alert`` model before being added, so the bridge
keeps working even if a minor EE release renames or removes a
field.
"""
self.ensure_one()
Alert = self.env.get('quality.alert')
if Alert is None:
return {}
alert_fields = Alert._fields
# Build a short, dashboard-friendly title from the NCR description
# (falling back to the reference).
desc_plain = self._fp_bridge_strip_html(self.description)
title_source = desc_plain or (self.name or '')
title = (title_source or '').strip().splitlines()[0] if title_source else self.name
if title and len(title) > 200:
title = title[:197] + '...'
if not title:
title = self.name
vals = {}
# --- Identity / title ------------------------------------------------
if 'name' in alert_fields:
# quality.alert ``name`` is usually the short title in EE.
vals['name'] = title or self.name
if 'title' in alert_fields:
vals['title'] = title or self.name
# --- Team ------------------------------------------------------------
team = self._fp_bridge_team()
if team and 'team_id' in alert_fields:
vals['team_id'] = team.id
# --- Company ---------------------------------------------------------
if 'company_id' in alert_fields and self.company_id:
vals['company_id'] = self.company_id.id
# --- Description -----------------------------------------------------
# quality.alert.description has historically been Html in EE; write
# the raw NCR html straight through when the target is Html, and
# strip to plaintext otherwise.
if 'description' in alert_fields:
desc_field = alert_fields['description']
if getattr(desc_field, 'type', None) == 'html':
vals['description'] = self.description or False
else:
vals['description'] = desc_plain or False
# --- Partner (customer) ---------------------------------------------
if 'partner_id' in alert_fields and self.customer_partner_id:
vals['partner_id'] = self.customer_partner_id.id
# --- Stage / state mapping ------------------------------------------
if 'stage_id' in alert_fields:
stage_id = self._fp_bridge_stage_for_state(self.state)
if stage_id:
vals['stage_id'] = stage_id
# Some EE versions also expose a simple state selection on
# quality.alert. Only touch it if it exists.
if 'action_corrective' in alert_fields:
# Mirror our containment narrative into corrective actions
# when that html field exists on EE quality.alert.
if alert_fields['action_corrective'].type == 'html':
vals['action_corrective'] = self.containment or False
else:
vals['action_corrective'] = self._fp_bridge_strip_html(
self.containment
) or False
if 'action_preventive' in alert_fields:
if alert_fields['action_preventive'].type == 'html':
vals['action_preventive'] = self.root_cause or False
else:
vals['action_preventive'] = self._fp_bridge_strip_html(
self.root_cause
) or False
# --- Priority --------------------------------------------------------
if 'priority' in alert_fields:
prio = _SEVERITY_TO_PRIORITY.get(self.severity)
if prio is not None:
vals['priority'] = prio
# --- Reason (root cause dropdown) -----------------------------------
# quality.alert may expose ``reason_id`` pointing at quality.reason.
# We do not create reason records — shops curate those themselves —
# but we leave the mapping point here for future use.
return vals
# ------------------------------------------------------------------
# Sync engine
# ------------------------------------------------------------------
def _fp_bridge_quality_available(self):
"""Cheap capability check: is the EE quality.alert model loaded
in this database?"""
return self.env.get('quality.alert') is not None
def _sync_to_quality_alert(self):
"""Create or update the mirrored quality.alert for every NCR in
``self`` that has ``x_fc_auto_sync`` enabled. Silent no-op when
the EE quality module isn't available."""
if not self._fp_bridge_quality_available():
return
Alert = self.env['quality.alert'].sudo()
for ncr in self:
if not ncr.x_fc_auto_sync:
continue
try:
vals = ncr._prepare_quality_alert_vals()
if not vals:
continue
if ncr.x_fc_quality_alert_id:
ncr.x_fc_quality_alert_id.sudo().write(vals)
else:
alert = Alert.create(vals)
# Bypass normal write to avoid re-triggering sync.
ncr.with_context(
fp_bridge_skip_sync=True,
).write({
'x_fc_quality_alert_id': alert.id,
'x_fc_quality_alert_synced': True,
})
except Exception as exc:
_logger.warning(
'Fusion Plating bridge: failed to sync NCR %s to '
'quality.alert: %s',
ncr.name, exc,
)
# Non-fatal — never break the NCR save just because the
# mirror failed.
def action_sync_to_quality(self):
"""Manual "Sync to Quality" header button."""
self._sync_to_quality_alert()
return {'type': 'ir.actions.client', 'tag': 'reload'}
def action_view_quality_alert(self):
self.ensure_one()
if not self.x_fc_quality_alert_id:
return False
return {
'name': _('Quality Alert'),
'type': 'ir.actions.act_window',
'res_model': 'quality.alert',
'res_id': self.x_fc_quality_alert_id.id,
'view_mode': 'form',
'target': 'current',
}
# ------------------------------------------------------------------
# CRUD overrides
# ------------------------------------------------------------------
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
if not self.env.context.get('fp_bridge_skip_sync'):
records._sync_to_quality_alert()
return records
# Fields whose changes should trigger a resync.
_FP_BRIDGE_SYNC_FIELDS = {
'description',
'root_cause',
'containment',
'disposition',
'state',
'severity',
'customer_partner_id',
'x_fc_auto_sync',
}
def write(self, vals):
result = super().write(vals)
if self.env.context.get('fp_bridge_skip_sync'):
return result
if self._FP_BRIDGE_SYNC_FIELDS & set(vals.keys()):
self._sync_to_quality_alert()
return result

View File

@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_bridge_quality_alert_manager,fp.bridge.quality.alert.manager,quality.model_quality_alert,fusion_plating.group_fusion_plating_manager,1,1,1,0
access_fp_bridge_quality_alert_supervisor,fp.bridge.quality.alert.supervisor,quality.model_quality_alert,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_bridge_quality_alert_stage_manager,fp.bridge.quality.alert.stage.manager,quality.model_quality_alert_stage,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_bridge_quality_alert_team_manager,fp.bridge.quality.alert.team.manager,quality.model_quality_alert_team,fusion_plating.group_fusion_plating_manager,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_bridge_quality_alert_manager fp.bridge.quality.alert.manager quality.model_quality_alert fusion_plating.group_fusion_plating_manager 1 1 1 0
3 access_fp_bridge_quality_alert_supervisor fp.bridge.quality.alert.supervisor quality.model_quality_alert fusion_plating.group_fusion_plating_supervisor 1 0 0 0
4 access_fp_bridge_quality_alert_stage_manager fp.bridge.quality.alert.stage.manager quality.model_quality_alert_stage fusion_plating.group_fusion_plating_manager 1 0 0 0
5 access_fp_bridge_quality_alert_team_manager fp.bridge.quality.alert.team.manager quality.model_quality_alert_team fusion_plating.group_fusion_plating_manager 1 0 0 0

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
-->
<odoo>
<!--
Extend the Fusion Plating NCR form to surface the mirrored EE
quality.alert record. Adds:
* A header "Sync to Quality" button
* A smart button jumping to the mirrored alert
* A "Quality Alert Sync" notebook page with the raw sync fields
-->
<record id="view_fp_ncr_form_bridge_quality" model="ir.ui.view">
<field name="name">fp.ncr.form.bridge.quality</field>
<field name="model">fusion.plating.ncr</field>
<field name="inherit_id" ref="fusion_plating_quality.view_fp_ncr_form"/>
<field name="arch" type="xml">
<!-- Header: manual sync button -->
<xpath expr="//header" position="inside">
<button name="action_sync_to_quality"
string="Sync to Quality"
type="object"
class="btn-secondary"
invisible="not x_fc_auto_sync"
help="Push this NCR's latest values into the mirrored Odoo EE quality.alert record."/>
</xpath>
<!-- Smart button: jump to mirrored alert -->
<xpath expr="//div[@name='button_box']" position="inside">
<button name="action_view_quality_alert"
type="object"
class="oe_stat_button"
icon="fa-bell"
invisible="not x_fc_quality_alert_id">
<div class="o_stat_info">
<span class="o_stat_text">Quality</span>
<span class="o_stat_text">Alert</span>
</div>
</button>
</xpath>
<!-- Notebook page: raw sync fields -->
<xpath expr="//notebook" position="inside">
<page string="Quality Alert Sync" name="quality_alert_sync">
<group>
<group string="Sync Status">
<field name="x_fc_auto_sync"/>
<field name="x_fc_quality_alert_synced" readonly="1"/>
<field name="x_fc_quality_alert_id" readonly="1"/>
</group>
<group string="About">
<div colspan="2" class="text-muted">
<p>
This NCR is mirrored into the Odoo Enterprise
<strong>Quality</strong> module so EE users can monitor it
from the native quality dashboards.
</p>
<p>
The sync is <strong>one-way</strong>: Fusion Plating is the
source of truth. Edits made directly to the mirrored
<em>quality.alert</em> record will be overwritten the next
time this NCR changes.
</p>
<p>
Turn off <strong>Auto-sync to Quality</strong> on an individual
NCR to freeze its mirror, or use the
<strong>Sync to Quality</strong> header button to push changes
on demand.
</p>
</div>
</group>
</group>
</page>
</xpath>
</field>
</record>
</odoo>