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,67 @@
# Fusion Plating — Controlled Goods Program (CGP)
Canadian Controlled Goods Program compliance pack for plating shops doing
defence or ITAR-adjacent work. Part of the Fusion Plating product family
by Nexa Systems Inc.
## What it covers
The Controlled Goods Program (CGP) is administered by Public Services and
Procurement Canada (PSPC) under the *Defence Production Act*. Every
Canadian entity that examines, possesses, or transfers controlled goods
must be registered, must appoint an Authorized Individual, must run
Personnel Security Assessments on anyone with access, and must maintain
visitor, movement, and incident logs. Non-compliance is a criminal
offence.
This module layers the minimum record set to stay audit-ready on top of
the Fusion Plating core.
## Records
| Model | Purpose |
|---|---|
| `fusion.plating.cgp.registration` | Company CGP registration with PSPC, 5-year renewal |
| `fusion.plating.cgp.authorized.individual` | AI appointment, training, state |
| `fusion.plating.cgp.psa` | Personnel Security Assessment (restricted) |
| `fusion.plating.cgp.visitor` | Visitor log with PSA / escort / approval |
| `fusion.plating.cgp.controlled.good` | Inventory of controlled goods handled |
| `fusion.plating.cgp.receipt.shipment` | Movement log with AI authorization |
| `fusion.plating.cgp.security.incident` | Security incidents, PSPC notification (restricted) |
| `fusion.plating.cgp.access.log` | Physical access log for controlled areas |
It also extends `hr.employee` with CGP fields (`x_fc_cgp_psa_id`,
`x_fc_cgp_ai`, etc.) and `res.company` with a link to the current CGP
registration.
## Security
CGP data is sensitive. This module introduces a **new restricted group**
on top of the core `fusion_plating.group_fusion_plating_manager`:
* **CGP Officer** — full access to every CGP record
* **CGP Designated Official** — implies CGP Officer; top-level accountable
person named in the PSPC registration
`ir.rule` records enforce that PSA and Security Incident records are
visible **only** to CGP Officer and above — a regular plating manager
cannot see them. No users are assigned by default; admin must grant
access explicitly.
## Security Plan template
On install the module seeds a `fusion.plating.doc.control` record titled
*"CGP Security Plan (Template)"*. Customise it to your facility and
submit to PSPC as part of your registration.
## Dependencies
* `fusion_plating_quality` — for `fusion.plating.doc.control`
* `hr` — for employee linkage on PSAs, AIs, and access logs
## Reference
<https://www.tpsgc-pwgsc.gc.ca/pmc-cgp/>
Copyright (c) 2026 Nexa Systems Inc. All rights reserved.
License: OPL-1 (Odoo Proprietary License v1.0)

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,94 @@
# -*- 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 — Controlled Goods Program',
'version': '19.0.1.0.0',
'category': 'Manufacturing/Plating',
'summary': 'Canadian Controlled Goods Program (CGP) compliance for plating '
'shops handling defence work: registration, authorized individuals, '
'personnel security assessments, visitor control, controlled goods '
'log, security incidents.',
'description': """
Fusion Plating — Controlled Goods Program (CGP)
===============================================
Part of the Fusion Plating product family by Nexa Systems Inc.
Canadian Controlled Goods Program compliance pack for plating / metal
finishing shops that handle defence or ITAR-adjacent work. The Controlled
Goods Program is administered by Public Services and Procurement Canada
(PSPC) under the Defence Production Act. Non-compliance is a criminal
offence, so this module defaults every record to restricted access and
keeps sensitive personnel security assessments out of the general
manager's view.
Records included
----------------
* CGP Registration — company registration with PSPC, 5-year renewal cycle
* Authorized Individuals — AI appointment, training, PSA linkage
* Personnel Security Assessments (PSA) — restricted to CGP Officer+
* Visitor Control — PSA-on-file check, escort, approval
* Controlled Goods Inventory — what the shop actually handles
* Receipts & Shipments — movement log with AI authorization
* Security Incidents — breach reporting, PSPC notification (restricted)
* Physical Access Log — entry / exit of controlled areas
* Security Plan — seeded as a doc.control template
Security model
--------------
A new restricted group ``CGP Officer`` is introduced on top of the core
Fusion Plating privilege. PSA and Security Incident records are visible
ONLY to the CGP Officer and the CGP Designated Official — not to the
generic manager role, because not every manager should see personnel
assessments. Admin must grant the new group manually; no user is
assigned by default.
Depends on ``fusion_plating_quality`` for the ``fusion.plating.doc.control``
model (the Security Plan lives there as a controlled document).
Reference: https://www.tpsgc-pwgsc.gc.ca/pmc-cgp/
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',
'hr',
],
'data': [
'security/fp_cgp_security.xml',
'security/ir.model.access.csv',
'data/fp_sequence_data.xml',
'data/fp_cgp_doc_template_data.xml',
'views/fp_cgp_registration_views.xml',
'views/fp_cgp_ai_views.xml',
'views/fp_cgp_psa_views.xml',
'views/fp_cgp_visitor_views.xml',
'views/fp_cgp_controlled_good_views.xml',
'views/fp_cgp_receipt_shipment_views.xml',
'views/fp_cgp_security_incident_views.xml',
'views/fp_cgp_access_log_views.xml',
'views/hr_employee_views.xml',
'views/fp_menu.xml',
],
'demo': [
'data/fp_demo_cgp_data.xml',
],
'assets': {
'web.assets_backend': [
'fusion_plating_cgp/static/src/scss/fusion_plating_cgp.scss',
],
},
'installable': True,
'application': False,
'auto_install': False,
}

View File

@@ -0,0 +1,23 @@
<?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 noupdate="1">
<!--
Seed a CGP Security Plan template as a controlled document.
This gives the shop a starting point; they customise it to
their facility and submit to PSPC as part of their CGP
registration package.
-->
<record id="doc_cgp_security_plan_template" model="fusion.plating.doc.control">
<field name="name">CGP Security Plan (Template)</field>
<field name="doc_type">manual</field>
<field name="revision">0.0</field>
<field name="state">draft</field>
</record>
</odoo>

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc. — DEMO DATA (temporary)
Remove this file and its manifest entry before production release.
-->
<odoo noupdate="1">
<!-- ========== CGP REGISTRATION ========== -->
<record id="demo_cgp_registration" model="fusion.plating.cgp.registration">
<field name="name">Fusion Plating Main — CGP Registration</field>
<field name="registration_number">CGP-2024-0012</field>
<field name="state">registered</field>
<field name="registration_date" eval="(DateTime.today() - timedelta(days=730)).strftime('%Y-%m-%d')"/>
<field name="expiry_date" eval="(DateTime.today() + timedelta(days=1096)).strftime('%Y-%m-%d')"/>
<field name="physical_address">45 Industrial Parkway, Mississauga, ON L5T 2R3</field>
<field name="last_compliance_review" eval="(DateTime.today() - timedelta(days=180)).strftime('%Y-%m-%d')"/>
<field name="next_compliance_review" eval="(DateTime.today() + timedelta(days=185)).strftime('%Y-%m-%d')"/>
<field name="notes" type="html"><p>Annual compliance review completed by PSPC inspector. No findings.</p></field>
</record>
<!-- ========== CONTROLLED GOODS ========== -->
<record id="demo_controlled_good_001" model="fusion.plating.cgp.controlled.good">
<field name="name">CF-188 Hornet — Main Landing Gear Strut (Group 2)</field>
<field name="schedule_category">Group 2: Munitions</field>
<field name="customer_id" ref="fusion_plating.demo_partner_aeroparts"/>
<field name="current_quantity">6</field>
<field name="location">Controlled Area A — Rack 3</field>
<field name="state">in_process</field>
<field name="description" type="html"><p>Hard chrome plating on main landing gear strut per DND spec. Parts received under controlled goods receipt CGR-2026-041.</p></field>
</record>
<record id="demo_controlled_good_002" model="fusion.plating.cgp.controlled.good">
<field name="name">LAV III Hull Component — Mounting Bracket</field>
<field name="schedule_category">Group 2: Munitions</field>
<field name="customer_id" ref="fusion_plating.demo_partner_precision"/>
<field name="current_quantity">24</field>
<field name="location">Controlled Area B — Shelf 7</field>
<field name="state">in_storage</field>
<field name="description" type="html"><p>EN plated mounting brackets awaiting final inspection and shipment. Stored under controlled conditions per Security Plan.</p></field>
</record>
<!-- ========== VISITORS ========== -->
<record id="demo_cgp_visitor_001" model="fusion.plating.cgp.visitor">
<field name="name">Col. James Whitfield</field>
<field name="company_represented">Department of National Defence</field>
<field name="visit_date" eval="(DateTime.now() - timedelta(hours=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="purpose">Annual DND quality surveillance audit of controlled goods handling procedures.</field>
<field name="is_canadian_citizen" eval="True"/>
<field name="visitor_psa_on_file" eval="True"/>
<field name="escort_required" eval="True"/>
<field name="accessed_controlled_area" eval="True"/>
<field name="state">checked_in</field>
</record>
<record id="demo_cgp_visitor_002" model="fusion.plating.cgp.visitor">
<field name="name">Patricia Nguyen</field>
<field name="company_represented">General Dynamics Land Systems — Canada</field>
<field name="visit_date" eval="(DateTime.now() - timedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="visit_end" eval="(DateTime.now() - timedelta(days=3) + timedelta(hours=4)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="purpose">Source inspection of LAV III mounting bracket lot prior to shipment.</field>
<field name="is_canadian_citizen" eval="True"/>
<field name="visitor_psa_on_file" eval="True"/>
<field name="escort_required" eval="True"/>
<field name="accessed_controlled_area" eval="True"/>
<field name="state">checked_out</field>
</record>
<!-- ========== SECURITY INCIDENT ========== -->
<record id="demo_cgp_incident_001" model="fusion.plating.cgp.security.incident">
<field name="name">CGP-INC-2026-001</field>
<field name="incident_date" eval="(DateTime.now() - timedelta(days=7)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="incident_type">unauthorized_access</field>
<field name="severity">minor</field>
<field name="state">investigating</field>
<field name="description" type="html"><p>Maintenance contractor entered Controlled Area B without signing the visitor log or obtaining AI authorization. Contractor was performing scheduled HVAC filter replacement and was unaware of the controlled-area boundary. No controlled goods were accessed or moved.</p></field>
<field name="containment" type="html"><p>Contractor escorted out of controlled area immediately. Badge access log reviewed to confirm no other unauthorized entries. Controlled goods inventory verified — no discrepancies.</p></field>
</record>
</odoo>

View File

@@ -0,0 +1,34 @@
<?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 noupdate="1">
<record id="seq_fp_cgp_psa" model="ir.sequence">
<field name="name">Fusion Plating: CGP PSA</field>
<field name="code">fusion.plating.cgp.psa</field>
<field name="prefix">PSA/%(year)s/</field>
<field name="padding">4</field>
<field name="company_id" eval="False"/>
</record>
<record id="seq_fp_cgp_movement" model="ir.sequence">
<field name="name">Fusion Plating: CGP Movement</field>
<field name="code">fusion.plating.cgp.movement</field>
<field name="prefix">CGP-MV/%(year)s/</field>
<field name="padding">4</field>
<field name="company_id" eval="False"/>
</record>
<record id="seq_fp_cgp_incident" model="ir.sequence">
<field name="name">Fusion Plating: CGP Incident</field>
<field name="code">fusion.plating.cgp.incident</field>
<field name="prefix">CGP-INC/%(year)s/</field>
<field name="padding">4</field>
<field name="company_id" eval="False"/>
</record>
</odoo>

View File

@@ -0,0 +1,15 @@
# -*- 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_cgp_registration
from . import fp_cgp_authorized_individual
from . import fp_cgp_psa
from . import fp_cgp_visitor
from . import fp_cgp_controlled_good
from . import fp_cgp_receipt_shipment
from . import fp_cgp_security_incident
from . import fp_cgp_access_log
from . import hr_employee
from . import res_company

View File

@@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from odoo import fields, models
class FpCgpAccessLog(models.Model):
"""Physical access log for CGP-controlled areas.
Every entry and exit from a controlled area must be logged so a
timeline can be reconstructed in the event of an incident. In a
typical shop the entries are created automatically by an access
control system; this model is the lightweight hand-entry fallback.
"""
_name = 'fusion.plating.cgp.access.log'
_description = 'Fusion Plating — CGP Access Log Entry'
_order = 'access_datetime desc, id desc'
employee_id = fields.Many2one(
'hr.employee',
string='Employee',
)
access_datetime = fields.Datetime(
string='Date / Time',
required=True,
default=lambda self: fields.Datetime.now(),
)
access_point = fields.Char(
string='Access Point',
required=True,
help='Door, gate, or zone name.',
)
entry_exit = fields.Selection(
[
('entry', 'Entry'),
('exit', 'Exit'),
],
string='Direction',
default='entry',
required=True,
)
access_type = fields.Selection(
[
('employee', 'Employee'),
('visitor_escorted', 'Visitor (Escorted)'),
('visitor_unescorted', 'Visitor (Unescorted)'),
],
string='Access Type',
default='employee',
required=True,
)
related_visitor_id = fields.Many2one(
'fusion.plating.cgp.visitor',
string='Related Visitor',
)
company_id = fields.Many2one(
'res.company',
string='Company',
default=lambda self: self.env.company,
)
notes = fields.Char(string='Notes')

View File

@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from odoo import api, fields, models
class FpCgpAuthorizedIndividual(models.Model):
"""Authorized Individual (AI) under the Controlled Goods Program.
Every registered entity must appoint at least one Authorized
Individual. The AI is PSPC-trained and is responsible for the
day-to-day application of the security plan: approving visitors,
authorizing movements of controlled goods, and vetting personnel
before granting access.
"""
_name = 'fusion.plating.cgp.authorized.individual'
_description = 'Fusion Plating — CGP Authorized Individual'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'appointment_date desc, id desc'
_rec_name = 'name'
name = fields.Char(
string='Name',
compute='_compute_name',
store=True,
)
employee_id = fields.Many2one(
'hr.employee',
string='Employee',
required=True,
tracking=True,
)
appointment_date = fields.Date(
string='Appointment Date',
tracking=True,
)
psa_level = fields.Selection(
[
('standard', 'Standard'),
('enhanced', 'Enhanced'),
('security_clearance', 'Security Clearance'),
],
string='PSA Level',
default='standard',
tracking=True,
help='Level of Personnel Security Assessment held by this AI.',
)
psa_expiry = fields.Date(
string='PSA Expiry',
tracking=True,
)
training_completed_date = fields.Date(
string='PSPC Training Completed',
tracking=True,
help='Date the AI completed the mandatory PSPC training.',
)
state = fields.Selection(
[
('active', 'Active'),
('inactive', 'Inactive'),
('suspended', 'Suspended'),
('revoked', 'Revoked'),
],
string='Status',
default='active',
required=True,
tracking=True,
)
company_id = fields.Many2one(
'res.company',
string='Company',
default=lambda self: self.env.company,
)
notes = fields.Html(string='Notes')
active = fields.Boolean(default=True)
_sql_constraints = [
(
'fp_cgp_ai_employee_uniq',
'unique(employee_id)',
'An employee can only be registered as an Authorized '
'Individual once.',
),
]
@api.depends('employee_id', 'employee_id.name')
def _compute_name(self):
for rec in self:
rec.name = rec.employee_id.name or 'New AI'
def action_activate(self):
self.write({'state': 'active'})
def action_suspend(self):
self.write({'state': 'suspended'})
def action_revoke(self):
self.write({'state': 'revoked'})
def action_deactivate(self):
self.write({'state': 'inactive'})

View File

@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from odoo import fields, models
class FpCgpControlledGood(models.Model):
"""Inventory of controlled goods currently handled by the shop.
This is intentionally a lightweight, hand-maintained register of what
the shop actually processes — parts, assemblies, or materials that
fall under the Schedule to the Defence Production Act. It is not a
replacement for the Odoo stock module; it is the CGP-specific audit
trail that an AI can show a PSPC inspector.
"""
_name = 'fusion.plating.cgp.controlled.good'
_description = 'Fusion Plating — Controlled Good'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'name'
name = fields.Char(
string='Name',
required=True,
tracking=True,
)
description = fields.Html(string='Description')
schedule_category = fields.Char(
string='Schedule Category',
tracking=True,
help='Category under the Schedule to the Defence Production Act, '
'e.g. "Group 2: Munitions" or a specific ECL entry.',
)
customer_id = fields.Many2one(
'res.partner',
string='Customer',
tracking=True,
)
current_quantity = fields.Float(
string='Current Quantity',
tracking=True,
)
location = fields.Char(
string='Storage Location',
tracking=True,
)
state = fields.Selection(
[
('received', 'Received'),
('in_process', 'In Process'),
('in_storage', 'In Storage'),
('shipped', 'Shipped'),
('destroyed', 'Destroyed'),
],
string='Status',
default='received',
required=True,
tracking=True,
)
company_id = fields.Many2one(
'res.company',
string='Company',
default=lambda self: self.env.company,
)
active = fields.Boolean(default=True)
def action_mark_in_process(self):
self.write({'state': 'in_process'})
def action_mark_in_storage(self):
self.write({'state': 'in_storage'})
def action_mark_shipped(self):
self.write({'state': 'shipped'})
def action_mark_destroyed(self):
self.write({'state': 'destroyed'})

View File

@@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
class FpCgpPsa(models.Model):
"""Personnel Security Assessment.
Every person — employee, contractor, or long-term visitor — who has
access to controlled goods must have a current Personnel Security
Assessment on file. PSAs are valid for up to five years and include
a review of citizenship, criminal record, and loyalty considerations.
PSA records are restricted via ``ir.rule`` so that only the CGP
Officer and Designated Official can see them. A regular plating
manager cannot see personnel assessments.
"""
_name = 'fusion.plating.cgp.psa'
_description = 'Fusion Plating — Personnel Security Assessment'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'assessment_date desc, id desc'
name = fields.Char(
string='Reference',
required=True,
copy=False,
readonly=True,
default=lambda self: self._default_name(),
tracking=True,
)
employee_id = fields.Many2one(
'hr.employee',
string='Employee',
required=True,
tracking=True,
)
assessment_date = fields.Date(
string='Assessment Date',
tracking=True,
)
assessed_by_id = fields.Many2one(
'res.users',
string='Assessed By',
default=lambda self: self.env.user,
tracking=True,
)
result = fields.Selection(
[
('pass', 'Pass'),
('conditional_pass', 'Conditional Pass'),
('fail', 'Fail'),
],
string='Result',
tracking=True,
)
expiry_date = fields.Date(
string='Expiry Date',
tracking=True,
help='PSAs are typically valid for five years.',
)
notes = fields.Html(
string='Internal Notes',
help='Internal notes — restricted to CGP Officer and above.',
)
document_ids = fields.Many2many(
'ir.attachment',
'fp_cgp_psa_attachment_rel',
'psa_id',
'attachment_id',
string='Supporting Documents',
help='Restricted-access supporting documents (criminal record '
'check, references, etc.).',
)
state = fields.Selection(
[
('draft', 'Draft'),
('in_progress', 'In Progress'),
('completed', 'Completed'),
('expired', 'Expired'),
],
string='Status',
default='draft',
required=True,
tracking=True,
)
company_id = fields.Many2one(
'res.company',
string='Company',
default=lambda self: self.env.company,
)
active = fields.Boolean(default=True)
@api.model
def _default_name(self):
seq = self.env['ir.sequence'].next_by_code('fusion.plating.cgp.psa')
return seq or '/'
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if not vals.get('name') or vals.get('name') == '/':
vals['name'] = self._default_name()
return super().create(vals_list)
@api.onchange('assessment_date')
def _onchange_assessment_date(self):
"""Default expiry to five years after assessment."""
for rec in self:
if rec.assessment_date and not rec.expiry_date:
rec.expiry_date = rec.assessment_date + relativedelta(years=5)
def action_start(self):
self.write({'state': 'in_progress'})
def action_complete(self):
self.write({'state': 'completed'})
def action_expire(self):
self.write({'state': 'expired'})
def action_reset_to_draft(self):
self.write({'state': 'draft'})

View File

@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from odoo import api, fields, models
class FpCgpReceiptShipment(models.Model):
"""Movement log for controlled goods.
Every receipt, shipment, or internal transfer of a controlled good
must be logged and authorized by an Authorized Individual. Cross-
border shipments additionally require an export permit reference.
"""
_name = 'fusion.plating.cgp.receipt.shipment'
_description = 'Fusion Plating — CGP Receipt / Shipment'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'date desc, id desc'
name = fields.Char(
string='Reference',
required=True,
copy=False,
readonly=True,
default=lambda self: self._default_name(),
tracking=True,
)
movement_type = fields.Selection(
[
('receipt', 'Receipt'),
('shipment', 'Shipment'),
('internal_transfer', 'Internal Transfer'),
],
string='Movement Type',
default='receipt',
required=True,
tracking=True,
)
controlled_good_id = fields.Many2one(
'fusion.plating.cgp.controlled.good',
string='Controlled Good',
required=True,
tracking=True,
)
date = fields.Datetime(
string='Date',
default=lambda self: fields.Datetime.now(),
tracking=True,
)
quantity = fields.Float(
string='Quantity',
tracking=True,
)
from_party = fields.Char(
string='From',
tracking=True,
)
to_party = fields.Char(
string='To',
tracking=True,
)
carrier = fields.Char(
string='Carrier',
tracking=True,
)
manifest_ref = fields.Char(
string='Manifest / Waybill',
tracking=True,
)
authorized_by_id = fields.Many2one(
'res.users',
string='Authorized By',
tracking=True,
help='Must be an Authorized Individual.',
)
export_permit_ref = fields.Char(
string='Export Permit Ref',
tracking=True,
help='Export permit reference when the movement crosses the '
'Canadian border.',
)
state = fields.Selection(
[
('draft', 'Draft'),
('authorized', 'Authorized'),
('executed', 'Executed'),
('closed', 'Closed'),
],
string='Status',
default='draft',
required=True,
tracking=True,
)
company_id = fields.Many2one(
'res.company',
string='Company',
default=lambda self: self.env.company,
)
notes = fields.Html(string='Notes')
active = fields.Boolean(default=True)
@api.model
def _default_name(self):
seq = self.env['ir.sequence'].next_by_code('fusion.plating.cgp.movement')
return seq or '/'
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if not vals.get('name') or vals.get('name') == '/':
vals['name'] = self._default_name()
return super().create(vals_list)
def action_authorize(self):
self.write({'state': 'authorized'})
def action_execute(self):
self.write({'state': 'executed'})
def action_close(self):
self.write({'state': 'closed'})
def action_reset_to_draft(self):
self.write({'state': 'draft'})

View File

@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
class FpCgpRegistration(models.Model):
"""Canadian Controlled Goods Program registration.
Every Canadian entity that examines, possesses, or transfers
controlled goods must be registered with Public Services and
Procurement Canada (PSPC) under the Defence Production Act.
Registration is valid for five years and must be renewed before
it lapses. A single registration is usually held per legal entity,
so this record lives on ``res.company``.
"""
_name = 'fusion.plating.cgp.registration'
_description = 'Fusion Plating — CGP Registration'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'registration_date desc, id desc'
name = fields.Char(
string='Reference',
required=True,
tracking=True,
help='Label for this registration, e.g. "Acme Plating — CGP Reg."',
)
company_id = fields.Many2one(
'res.company',
string='Company',
required=True,
default=lambda self: self.env.company,
tracking=True,
)
registration_number = fields.Char(
string='PSPC Registration Number',
tracking=True,
help='Registration number assigned by PSPC when the entity '
'is admitted to the Controlled Goods Program.',
)
registration_date = fields.Date(
string='Registration Date',
tracking=True,
)
expiry_date = fields.Date(
string='Expiry Date',
tracking=True,
help='CGP registrations are typically valid for five years.',
)
state = fields.Selection(
[
('pending', 'Pending'),
('registered', 'Registered'),
('suspended', 'Suspended'),
('expired', 'Expired'),
('revoked', 'Revoked'),
],
string='Status',
default='pending',
required=True,
tracking=True,
)
designated_official_id = fields.Many2one(
'hr.employee',
string='Designated Official',
tracking=True,
help='Top-level person legally accountable for CGP compliance '
'at this entity. Must have an active PSA on file.',
)
physical_address = fields.Char(
string='Registered Address',
tracking=True,
help='Physical address on record with PSPC.',
)
security_plan_doc_id = fields.Many2one(
'fusion.plating.doc.control',
string='Security Plan',
help='Link to the controlled document holding the current '
'CGP Security Plan for this registration.',
)
last_compliance_review = fields.Date(
string='Last Compliance Review',
tracking=True,
)
next_compliance_review = fields.Date(
string='Next Compliance Review',
tracking=True,
)
notes = fields.Html(string='Notes')
active = fields.Boolean(default=True)
@api.onchange('registration_date')
def _onchange_registration_date(self):
"""Default expiry to five years after registration."""
for rec in self:
if rec.registration_date and not rec.expiry_date:
rec.expiry_date = rec.registration_date + relativedelta(years=5)
def action_mark_registered(self):
self.write({'state': 'registered'})
def action_suspend(self):
self.write({'state': 'suspended'})
def action_expire(self):
self.write({'state': 'expired'})
def action_revoke(self):
self.write({'state': 'revoked'})
def action_reset_to_pending(self):
self.write({'state': 'pending'})

View File

@@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from odoo import api, fields, models
class FpCgpSecurityIncident(models.Model):
"""Security incident or breach under the Controlled Goods Program.
Any event that could have compromised the security of controlled
goods — unauthorized access, a missing item, a visitor violation,
a cyber intrusion — must be investigated and, depending on
severity, reported to PSPC. Incident records are restricted via
``ir.rule`` to the CGP Officer and above.
"""
_name = 'fusion.plating.cgp.security.incident'
_description = 'Fusion Plating — CGP Security Incident'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'incident_date desc, id desc'
name = fields.Char(
string='Reference',
required=True,
copy=False,
readonly=True,
default=lambda self: self._default_name(),
tracking=True,
)
incident_date = fields.Datetime(
string='Incident Date',
default=lambda self: fields.Datetime.now(),
required=True,
tracking=True,
)
discovered_by_id = fields.Many2one(
'res.users',
string='Discovered By',
default=lambda self: self.env.user,
tracking=True,
)
incident_type = fields.Selection(
[
('unauthorized_access', 'Unauthorized Access'),
('missing_item', 'Missing Item'),
('documentation_error', 'Documentation Error'),
('visitor_violation', 'Visitor Violation'),
('cyber', 'Cyber Incident'),
('other', 'Other'),
],
string='Incident Type',
default='other',
required=True,
tracking=True,
)
severity = fields.Selection(
[
('minor', 'Minor'),
('major', 'Major'),
('critical', 'Critical'),
],
string='Severity',
default='minor',
required=True,
tracking=True,
)
description = fields.Html(string='Description')
containment = fields.Html(string='Containment Actions')
reported_to_pspc = fields.Boolean(
string='Reported to PSPC',
tracking=True,
)
pspc_notification_date = fields.Date(
string='PSPC Notification Date',
tracking=True,
)
corrective_action = fields.Html(string='Corrective Action')
state = fields.Selection(
[
('discovered', 'Discovered'),
('investigating', 'Investigating'),
('reported', 'Reported'),
('closed', 'Closed'),
],
string='Status',
default='discovered',
required=True,
tracking=True,
)
company_id = fields.Many2one(
'res.company',
string='Company',
default=lambda self: self.env.company,
)
active = fields.Boolean(default=True)
@api.model
def _default_name(self):
seq = self.env['ir.sequence'].next_by_code('fusion.plating.cgp.incident')
return seq or '/'
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if not vals.get('name') or vals.get('name') == '/':
vals['name'] = self._default_name()
return super().create(vals_list)
def action_investigate(self):
self.write({'state': 'investigating'})
def action_report(self):
self.write({
'state': 'reported',
'reported_to_pspc': True,
'pspc_notification_date': fields.Date.context_today(self),
})
def action_close(self):
self.write({'state': 'closed'})
def action_reset(self):
self.write({'state': 'discovered'})

View File

@@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from odoo import fields, models
class FpCgpVisitor(models.Model):
"""Visitor log entry under the Controlled Goods Program.
Every visitor to a site that handles controlled goods must be
logged, approved by an Authorized Individual, and escorted if
they do not have a PSA on file. Foreign nationals can only access
controlled goods under very specific conditions.
"""
_name = 'fusion.plating.cgp.visitor'
_description = 'Fusion Plating — CGP Visitor'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'visit_date desc, id desc'
name = fields.Char(
string='Visitor Name',
required=True,
tracking=True,
)
company_represented = fields.Char(
string='Company Represented',
tracking=True,
)
visit_date = fields.Datetime(
string='Visit Start',
required=True,
default=lambda self: fields.Datetime.now(),
tracking=True,
)
visit_end = fields.Datetime(
string='Visit End',
tracking=True,
)
purpose = fields.Text(
string='Purpose',
tracking=True,
)
host_id = fields.Many2one(
'hr.employee',
string='Host',
tracking=True,
)
is_canadian_citizen = fields.Boolean(
string='Canadian Citizen',
tracking=True,
)
visitor_psa_on_file = fields.Boolean(
string='PSA On File',
tracking=True,
help='The visitor has a valid Personnel Security Assessment '
'on file with PSPC or an approved foreign equivalent.',
)
approved_by_id = fields.Many2one(
'res.users',
string='Approved By',
tracking=True,
help='Must be an Authorized Individual.',
)
escort_required = fields.Boolean(
string='Escort Required',
default=True,
tracking=True,
)
accessed_controlled_area = fields.Boolean(
string='Accessed Controlled Area',
tracking=True,
)
state = fields.Selection(
[
('scheduled', 'Scheduled'),
('checked_in', 'Checked In'),
('checked_out', 'Checked Out'),
('denied', 'Denied'),
('cancelled', 'Cancelled'),
],
string='Status',
default='scheduled',
required=True,
tracking=True,
)
company_id = fields.Many2one(
'res.company',
string='Company',
default=lambda self: self.env.company,
)
notes = fields.Html(string='Notes')
active = fields.Boolean(default=True)
def action_check_in(self):
self.write({'state': 'checked_in'})
def action_check_out(self):
self.write({
'state': 'checked_out',
'visit_end': fields.Datetime.now(),
})
def action_deny(self):
self.write({'state': 'denied'})
def action_cancel(self):
self.write({'state': 'cancelled'})

View File

@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
class HrEmployee(models.Model):
"""Extend hr.employee with CGP-specific fields.
Uses the ``x_fc_`` prefix on every new field, per Fusion Central
convention for extensions to base Odoo models.
"""
_inherit = 'hr.employee'
x_fc_cgp_psa_id = fields.Many2one(
'fusion.plating.cgp.psa',
string='Current PSA',
help='The current Personnel Security Assessment on file for '
'this employee.',
)
x_fc_cgp_psa_status = fields.Selection(
[
('not_assessed', 'Not Assessed'),
('current', 'Current'),
('expiring', 'Expiring'),
('expired', 'Expired'),
],
string='PSA Status',
compute='_compute_x_fc_cgp_psa_status',
store=False,
)
x_fc_cgp_ai = fields.Boolean(
string='Authorized Individual',
help='This employee is an Authorized Individual under the '
'Controlled Goods Program.',
)
x_fc_cgp_access_level = fields.Selection(
[
('none', 'No Access'),
('general', 'General Access'),
('controlled_area', 'Controlled Area'),
('designated_official', 'Designated Official'),
],
string='CGP Access Level',
default='none',
help='Level of physical access this employee has to CGP areas.',
)
@api.depends(
'x_fc_cgp_psa_id',
'x_fc_cgp_psa_id.state',
'x_fc_cgp_psa_id.expiry_date',
)
def _compute_x_fc_cgp_psa_status(self):
today = fields.Date.context_today(self)
warn_cutoff = today + relativedelta(months=3)
for rec in self:
psa = rec.x_fc_cgp_psa_id
if not psa or psa.state != 'completed':
rec.x_fc_cgp_psa_status = 'not_assessed'
continue
expiry = psa.expiry_date
if not expiry:
rec.x_fc_cgp_psa_status = 'current'
elif expiry < today:
rec.x_fc_cgp_psa_status = 'expired'
elif expiry <= warn_cutoff:
rec.x_fc_cgp_psa_status = 'expiring'
else:
rec.x_fc_cgp_psa_status = 'current'

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from odoo import api, fields, models
class ResCompany(models.Model):
"""Extend res.company with a link to the current CGP registration."""
_inherit = 'res.company'
x_fc_cgp_registration_id = fields.Many2one(
'fusion.plating.cgp.registration',
string='CGP Registration',
domain="[('company_id', '=', id)]",
help='Currently active Controlled Goods Program registration '
'for this company.',
)
x_fc_cgp_registered = fields.Boolean(
string='CGP Registered',
compute='_compute_x_fc_cgp_registered',
store=False,
)
@api.depends(
'x_fc_cgp_registration_id',
'x_fc_cgp_registration_id.state',
)
def _compute_x_fc_cgp_registered(self):
for rec in self:
reg = rec.x_fc_cgp_registration_id
rec.x_fc_cgp_registered = bool(
reg and reg.state == 'registered'
)

View File

@@ -0,0 +1,85 @@
<?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>
<!-- ================================================================== -->
<!-- NEW RESTRICTED GROUPS -->
<!-- -->
<!-- CGP data is sensitive. Not every plating manager should see -->
<!-- Personnel Security Assessments or security incidents, so two new -->
<!-- groups sit above the core Fusion Plating privilege. Admin must -->
<!-- grant them explicitly; no users are assigned by default. -->
<!-- ================================================================== -->
<!-- CGP OFFICER: day-to-day CGP compliance operator -->
<record id="group_fusion_plating_cgp_officer" model="res.groups">
<field name="name">CGP Officer</field>
<field name="sequence">50</field>
<field name="privilege_id"
ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids"
eval="[(4, ref('fusion_plating.group_fusion_plating_manager'))]"/>
</record>
<!-- CGP DESIGNATED OFFICIAL: legally accountable per PSPC registration -->
<record id="group_fusion_plating_cgp_designated_official" model="res.groups">
<field name="name">CGP Designated Official</field>
<field name="sequence">60</field>
<field name="privilege_id"
ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids"
eval="[(4, ref('group_fusion_plating_cgp_officer'))]"/>
</record>
<!-- ================================================================== -->
<!-- RECORD RULES -->
<!-- -->
<!-- Defense-in-depth on top of ir.model.access.csv. PSA and Security -->
<!-- Incident records should never be visible outside the CGP Officer -->
<!-- group even if someone accidentally widens ACL. We bind a -->
<!-- permissive-for-officer rule and rely on the default-deny that -->
<!-- Odoo applies to models that have no unrestricted global rule and -->
<!-- no access lines for other groups. -->
<!-- ================================================================== -->
<!-- PSA: only visible to CGP Officer (and implied groups above) -->
<record id="fp_cgp_psa_officer_rule" model="ir.rule">
<field name="name">Fusion Plating: CGP PSA — CGP Officer full access</field>
<field name="model_id" ref="model_fusion_plating_cgp_psa"/>
<field name="groups"
eval="[(4, ref('group_fusion_plating_cgp_officer'))]"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
<!-- Security Incident: only visible to CGP Officer -->
<record id="fp_cgp_incident_officer_rule" model="ir.rule">
<field name="name">Fusion Plating: CGP Security Incident — CGP Officer full access</field>
<field name="model_id" ref="model_fusion_plating_cgp_security_incident"/>
<field name="groups"
eval="[(4, ref('group_fusion_plating_cgp_officer'))]"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
<!-- ================================================================== -->
<!-- Multi-company isolation on CGP Registration -->
<!-- ================================================================== -->
<record id="fp_cgp_registration_company_rule" model="ir.rule">
<field name="name">Fusion Plating: CGP Registration — multi-company</field>
<field name="model_id" ref="model_fusion_plating_cgp_registration"/>
<field name="global" eval="True"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
</odoo>

View File

@@ -0,0 +1,19 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_cgp_registration_manager,fp.cgp.registration.manager,model_fusion_plating_cgp_registration,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_cgp_registration_officer,fp.cgp.registration.officer,model_fusion_plating_cgp_registration,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_ai_manager,fp.cgp.ai.manager,model_fusion_plating_cgp_authorized_individual,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_cgp_ai_officer,fp.cgp.ai.officer,model_fusion_plating_cgp_authorized_individual,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_psa_officer,fp.cgp.psa.officer,model_fusion_plating_cgp_psa,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_visitor_supervisor,fp.cgp.visitor.supervisor,model_fusion_plating_cgp_visitor,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_cgp_visitor_manager,fp.cgp.visitor.manager,model_fusion_plating_cgp_visitor,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_cgp_visitor_officer,fp.cgp.visitor.officer,model_fusion_plating_cgp_visitor,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_controlled_good_supervisor,fp.cgp.good.supervisor,model_fusion_plating_cgp_controlled_good,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_cgp_controlled_good_manager,fp.cgp.good.manager,model_fusion_plating_cgp_controlled_good,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_cgp_controlled_good_officer,fp.cgp.good.officer,model_fusion_plating_cgp_controlled_good,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_receipt_supervisor,fp.cgp.receipt.supervisor,model_fusion_plating_cgp_receipt_shipment,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_cgp_receipt_manager,fp.cgp.receipt.manager,model_fusion_plating_cgp_receipt_shipment,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_cgp_receipt_officer,fp.cgp.receipt.officer,model_fusion_plating_cgp_receipt_shipment,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_incident_officer,fp.cgp.incident.officer,model_fusion_plating_cgp_security_incident,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_access_log_supervisor,fp.cgp.access.log.supervisor,model_fusion_plating_cgp_access_log,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_cgp_access_log_manager,fp.cgp.access.log.manager,model_fusion_plating_cgp_access_log,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_cgp_access_log_officer,fp.cgp.access.log.officer,model_fusion_plating_cgp_access_log,group_fusion_plating_cgp_officer,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_cgp_registration_manager fp.cgp.registration.manager model_fusion_plating_cgp_registration fusion_plating.group_fusion_plating_manager 1 0 0 0
3 access_fp_cgp_registration_officer fp.cgp.registration.officer model_fusion_plating_cgp_registration group_fusion_plating_cgp_officer 1 1 1 1
4 access_fp_cgp_ai_manager fp.cgp.ai.manager model_fusion_plating_cgp_authorized_individual fusion_plating.group_fusion_plating_manager 1 0 0 0
5 access_fp_cgp_ai_officer fp.cgp.ai.officer model_fusion_plating_cgp_authorized_individual group_fusion_plating_cgp_officer 1 1 1 1
6 access_fp_cgp_psa_officer fp.cgp.psa.officer model_fusion_plating_cgp_psa group_fusion_plating_cgp_officer 1 1 1 1
7 access_fp_cgp_visitor_supervisor fp.cgp.visitor.supervisor model_fusion_plating_cgp_visitor fusion_plating.group_fusion_plating_supervisor 1 0 0 0
8 access_fp_cgp_visitor_manager fp.cgp.visitor.manager model_fusion_plating_cgp_visitor fusion_plating.group_fusion_plating_manager 1 0 0 0
9 access_fp_cgp_visitor_officer fp.cgp.visitor.officer model_fusion_plating_cgp_visitor group_fusion_plating_cgp_officer 1 1 1 1
10 access_fp_cgp_controlled_good_supervisor fp.cgp.good.supervisor model_fusion_plating_cgp_controlled_good fusion_plating.group_fusion_plating_supervisor 1 0 0 0
11 access_fp_cgp_controlled_good_manager fp.cgp.good.manager model_fusion_plating_cgp_controlled_good fusion_plating.group_fusion_plating_manager 1 0 0 0
12 access_fp_cgp_controlled_good_officer fp.cgp.good.officer model_fusion_plating_cgp_controlled_good group_fusion_plating_cgp_officer 1 1 1 1
13 access_fp_cgp_receipt_supervisor fp.cgp.receipt.supervisor model_fusion_plating_cgp_receipt_shipment fusion_plating.group_fusion_plating_supervisor 1 0 0 0
14 access_fp_cgp_receipt_manager fp.cgp.receipt.manager model_fusion_plating_cgp_receipt_shipment fusion_plating.group_fusion_plating_manager 1 0 0 0
15 access_fp_cgp_receipt_officer fp.cgp.receipt.officer model_fusion_plating_cgp_receipt_shipment group_fusion_plating_cgp_officer 1 1 1 1
16 access_fp_cgp_incident_officer fp.cgp.incident.officer model_fusion_plating_cgp_security_incident group_fusion_plating_cgp_officer 1 1 1 1
17 access_fp_cgp_access_log_supervisor fp.cgp.access.log.supervisor model_fusion_plating_cgp_access_log fusion_plating.group_fusion_plating_supervisor 1 0 0 0
18 access_fp_cgp_access_log_manager fp.cgp.access.log.manager model_fusion_plating_cgp_access_log fusion_plating.group_fusion_plating_manager 1 0 0 0
19 access_fp_cgp_access_log_officer fp.cgp.access.log.officer model_fusion_plating_cgp_access_log group_fusion_plating_cgp_officer 1 1 1 1

View File

@@ -0,0 +1,66 @@
// =============================================================================
// Fusion Plating — Controlled Goods Program
// Copyright 2026 Nexa Systems Inc.
// License OPL-1 (Odoo Proprietary License v1.0)
//
// THEME AWARENESS
// ---------------
// Same rule as the rest of the Fusion Plating product family: NO hardcoded
// backgrounds or text colours. Everything hangs off Bootstrap / Odoo CSS
// custom properties and `color-mix()` so light and dark modes share one
// set of rules.
//
// background: var(--bs-body-bg) // main surface
// surface: var(--o-view-background-color) // view canvas
// foreground: var(--bs-body-color) // main text
// muted text: var(--bs-secondary-color)
// border: var(--bs-border-color)
// warning: var(--bs-warning) // used for "classified" tint
// danger: var(--bs-danger) // used for restricted badge
//
// The CGP pack deals with sensitive records — we tint classified forms with
// a soft warning wash so they are visually distinct from routine records
// without being loud enough to interfere with reading the form.
// =============================================================================
// -----------------------------------------------------------------------------
// Classified form surface
// -----------------------------------------------------------------------------
// Applied to the <form> of PSA, Security Incident, Registration, Controlled
// Good, and Receipt/Shipment views. It puts a subtle amber left border and a
// light warning tint behind the sheet so the user knows this record is under
// CGP control without needing to read the title.
.o_fp_cgp_classified {
.o_form_sheet,
.o_form_sheet_bg {
border-left: 4px solid color-mix(in srgb, var(--bs-warning) 60%, transparent);
background-color: color-mix(in srgb, var(--bs-warning) 3%, var(--o-view-background-color, var(--bs-body-bg)));
}
// The restricted badge (see below) gets a little breathing room below
// the title when placed at the top of the sheet.
.o_fp_cgp_restricted_badge {
margin-bottom: 8px;
}
}
// -----------------------------------------------------------------------------
// Restricted badge — "CGP", "Restricted — PSA", etc.
// -----------------------------------------------------------------------------
// Uses the danger token tinted to the current surface so it reads the same on
// light and dark without any media queries.
.o_fp_cgp_restricted_badge {
display: inline-block;
padding: 3px 10px;
font-size: 0.72rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
border-radius: 4px;
background-color: color-mix(in srgb, var(--bs-danger) 15%, transparent);
color: var(--bs-danger);
border: 1px solid color-mix(in srgb, var(--bs-danger) 40%, transparent);
}

View File

@@ -0,0 +1,86 @@
<?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>
<record id="view_fp_cgp_access_log_list" model="ir.ui.view">
<field name="name">fp.cgp.access.log.list</field>
<field name="model">fusion.plating.cgp.access.log</field>
<field name="arch" type="xml">
<list string="CGP Access Log" editable="top">
<field name="access_datetime"/>
<field name="access_point"/>
<field name="entry_exit"/>
<field name="access_type"/>
<field name="employee_id"/>
<field name="related_visitor_id"/>
<field name="notes" optional="hide"/>
</list>
</field>
</record>
<record id="view_fp_cgp_access_log_form" model="ir.ui.view">
<field name="name">fp.cgp.access.log.form</field>
<field name="model">fusion.plating.cgp.access.log</field>
<field name="arch" type="xml">
<form string="CGP Access Log Entry">
<sheet>
<group>
<group>
<field name="access_datetime"/>
<field name="access_point"/>
<field name="entry_exit"/>
<field name="access_type"/>
</group>
<group>
<field name="employee_id"/>
<field name="related_visitor_id"/>
<field name="notes"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_fp_cgp_access_log_search" model="ir.ui.view">
<field name="name">fp.cgp.access.log.search</field>
<field name="model">fusion.plating.cgp.access.log</field>
<field name="arch" type="xml">
<search string="Access Log">
<field name="access_point"/>
<field name="employee_id"/>
<field name="related_visitor_id"/>
<separator/>
<filter string="Entries" name="entries"
domain="[('entry_exit','=','entry')]"/>
<filter string="Exits" name="exits"
domain="[('entry_exit','=','exit')]"/>
<separator/>
<filter string="Employees" name="emp_type"
domain="[('access_type','=','employee')]"/>
<filter string="Escorted Visitors" name="esc_vis"
domain="[('access_type','=','visitor_escorted')]"/>
<filter string="Unescorted Visitors" name="unesc_vis"
domain="[('access_type','=','visitor_unescorted')]"/>
<group>
<filter string="Access Point" name="group_point"
context="{'group_by':'access_point'}"/>
<filter string="Employee" name="group_employee"
context="{'group_by':'employee_id'}"/>
</group>
</search>
</field>
</record>
<record id="action_fp_cgp_access_log" model="ir.actions.act_window">
<field name="name">Access Log</field>
<field name="res_model">fusion.plating.cgp.access.log</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_cgp_access_log_search"/>
</record>
</odoo>

View File

@@ -0,0 +1,109 @@
<?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>
<record id="view_fp_cgp_ai_list" model="ir.ui.view">
<field name="name">fp.cgp.ai.list</field>
<field name="model">fusion.plating.cgp.authorized.individual</field>
<field name="arch" type="xml">
<list string="Authorized Individuals"
decoration-success="state == 'active'"
decoration-muted="state == 'inactive'"
decoration-warning="state == 'suspended'"
decoration-danger="state == 'revoked'">
<field name="name"/>
<field name="employee_id"/>
<field name="appointment_date"/>
<field name="psa_level"/>
<field name="psa_expiry"/>
<field name="training_completed_date"/>
<field name="state" widget="badge"
decoration-success="state == 'active'"
decoration-warning="state == 'suspended'"
decoration-danger="state == 'revoked'"
decoration-muted="state == 'inactive'"/>
</list>
</field>
</record>
<record id="view_fp_cgp_ai_form" model="ir.ui.view">
<field name="name">fp.cgp.ai.form</field>
<field name="model">fusion.plating.cgp.authorized.individual</field>
<field name="arch" type="xml">
<form string="Authorized Individual">
<header>
<button name="action_activate" string="Activate" type="object"
class="oe_highlight"
invisible="state == 'active'"/>
<button name="action_suspend" string="Suspend" type="object"
invisible="state not in ('active',)"/>
<button name="action_revoke" string="Revoke" type="object"
invisible="state == 'revoked'"/>
<button name="action_deactivate" string="Deactivate" type="object"
invisible="state != 'active'"/>
<field name="state" widget="statusbar"
statusbar_visible="active,inactive,suspended,revoked"/>
</header>
<sheet>
<div class="oe_title">
<label for="name"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Appointment">
<field name="employee_id"/>
<field name="appointment_date"/>
<field name="training_completed_date"/>
</group>
<group string="PSA">
<field name="psa_level"/>
<field name="psa_expiry"/>
</group>
</group>
<notebook>
<page string="Notes">
<field name="notes"/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="view_fp_cgp_ai_search" model="ir.ui.view">
<field name="name">fp.cgp.ai.search</field>
<field name="model">fusion.plating.cgp.authorized.individual</field>
<field name="arch" type="xml">
<search string="Authorized Individuals">
<field name="name"/>
<field name="employee_id"/>
<separator/>
<filter string="Active" name="active_ai"
domain="[('state','=','active')]"/>
<filter string="Suspended" name="suspended"
domain="[('state','=','suspended')]"/>
<filter string="Revoked" name="revoked"
domain="[('state','=','revoked')]"/>
<group>
<filter string="Status" name="group_state"
context="{'group_by':'state'}"/>
<filter string="PSA Level" name="group_level"
context="{'group_by':'psa_level'}"/>
</group>
</search>
</field>
</record>
<record id="action_fp_cgp_ai" model="ir.actions.act_window">
<field name="name">Authorized Individuals</field>
<field name="res_model">fusion.plating.cgp.authorized.individual</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_cgp_ai_search"/>
</record>
</odoo>

View File

@@ -0,0 +1,113 @@
<?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>
<record id="view_fp_cgp_controlled_good_list" model="ir.ui.view">
<field name="name">fp.cgp.controlled.good.list</field>
<field name="model">fusion.plating.cgp.controlled.good</field>
<field name="arch" type="xml">
<list string="Controlled Goods"
decoration-success="state == 'in_storage'"
decoration-info="state == 'received'"
decoration-warning="state == 'in_process'"
decoration-muted="state in ('shipped','destroyed')">
<field name="name"/>
<field name="schedule_category"/>
<field name="customer_id"/>
<field name="current_quantity"/>
<field name="location"/>
<field name="state" widget="badge"
decoration-info="state == 'received'"
decoration-warning="state == 'in_process'"
decoration-success="state == 'in_storage'"
decoration-muted="state in ('shipped','destroyed')"/>
</list>
</field>
</record>
<record id="view_fp_cgp_controlled_good_form" model="ir.ui.view">
<field name="name">fp.cgp.controlled.good.form</field>
<field name="model">fusion.plating.cgp.controlled.good</field>
<field name="arch" type="xml">
<form string="Controlled Good" class="o_fp_cgp_classified">
<header>
<button name="action_mark_in_process" string="In Process"
type="object"
invisible="state == 'in_process'"/>
<button name="action_mark_in_storage" string="In Storage"
type="object"
invisible="state == 'in_storage'"/>
<button name="action_mark_shipped" string="Shipped" type="object"
invisible="state == 'shipped'"/>
<button name="action_mark_destroyed" string="Destroyed"
type="object"
invisible="state == 'destroyed'"/>
<field name="state" widget="statusbar"
statusbar_visible="received,in_process,in_storage,shipped"/>
</header>
<sheet>
<div class="oe_title">
<label for="name"/>
<h1><field name="name"/></h1>
</div>
<span class="o_fp_cgp_restricted_badge">Controlled Good</span>
<group>
<group string="Classification">
<field name="schedule_category"/>
<field name="customer_id"/>
</group>
<group string="Inventory">
<field name="current_quantity"/>
<field name="location"/>
</group>
</group>
<notebook>
<page string="Description">
<field name="description"/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="view_fp_cgp_controlled_good_search" model="ir.ui.view">
<field name="name">fp.cgp.controlled.good.search</field>
<field name="model">fusion.plating.cgp.controlled.good</field>
<field name="arch" type="xml">
<search string="Controlled Goods">
<field name="name"/>
<field name="schedule_category"/>
<field name="customer_id"/>
<separator/>
<filter string="Received" name="received"
domain="[('state','=','received')]"/>
<filter string="In Process" name="in_process"
domain="[('state','=','in_process')]"/>
<filter string="In Storage" name="in_storage"
domain="[('state','=','in_storage')]"/>
<filter string="Shipped" name="shipped"
domain="[('state','=','shipped')]"/>
<group>
<filter string="Status" name="group_state"
context="{'group_by':'state'}"/>
<filter string="Customer" name="group_customer"
context="{'group_by':'customer_id'}"/>
</group>
</search>
</field>
</record>
<record id="action_fp_cgp_controlled_good" model="ir.actions.act_window">
<field name="name">Controlled Goods</field>
<field name="res_model">fusion.plating.cgp.controlled.good</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_cgp_controlled_good_search"/>
</record>
</odoo>

View File

@@ -0,0 +1,120 @@
<?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>
<record id="view_fp_cgp_psa_list" model="ir.ui.view">
<field name="name">fp.cgp.psa.list</field>
<field name="model">fusion.plating.cgp.psa</field>
<field name="arch" type="xml">
<list string="Personnel Security Assessments"
decoration-success="state == 'completed'"
decoration-danger="state == 'expired'"
decoration-info="state == 'in_progress'">
<field name="name"/>
<field name="employee_id"/>
<field name="assessment_date"/>
<field name="result"/>
<field name="expiry_date"/>
<field name="assessed_by_id"/>
<field name="state" widget="badge"
decoration-success="state == 'completed'"
decoration-info="state == 'in_progress'"
decoration-danger="state == 'expired'"
decoration-muted="state == 'draft'"/>
</list>
</field>
</record>
<record id="view_fp_cgp_psa_form" model="ir.ui.view">
<field name="name">fp.cgp.psa.form</field>
<field name="model">fusion.plating.cgp.psa</field>
<field name="arch" type="xml">
<form string="Personnel Security Assessment" class="o_fp_cgp_classified">
<header>
<button name="action_start" string="Start" type="object"
class="oe_highlight"
invisible="state != 'draft'"/>
<button name="action_complete" string="Complete" type="object"
class="oe_highlight"
invisible="state != 'in_progress'"/>
<button name="action_expire" string="Mark Expired" type="object"
invisible="state != 'completed'"/>
<button name="action_reset_to_draft" string="Reset" type="object"
invisible="state == 'draft'"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,in_progress,completed,expired"/>
</header>
<sheet>
<div class="oe_title">
<label for="name"/>
<h1><field name="name"/></h1>
</div>
<span class="o_fp_cgp_restricted_badge">Restricted — PSA</span>
<group>
<group string="Assessment">
<field name="employee_id"/>
<field name="assessed_by_id"/>
<field name="assessment_date"/>
<field name="expiry_date"/>
</group>
<group string="Outcome">
<field name="result"/>
</group>
</group>
<notebook>
<page string="Internal Notes">
<field name="notes"/>
</page>
<page string="Supporting Documents">
<field name="document_ids" widget="many2many_binary"/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="view_fp_cgp_psa_search" model="ir.ui.view">
<field name="name">fp.cgp.psa.search</field>
<field name="model">fusion.plating.cgp.psa</field>
<field name="arch" type="xml">
<search string="PSAs">
<field name="name"/>
<field name="employee_id"/>
<separator/>
<filter string="Completed" name="completed"
domain="[('state','=','completed')]"/>
<filter string="In Progress" name="in_progress"
domain="[('state','=','in_progress')]"/>
<filter string="Expired" name="expired"
domain="[('state','=','expired')]"/>
<separator/>
<filter string="Pass" name="pass_result"
domain="[('result','=','pass')]"/>
<filter string="Conditional" name="conditional"
domain="[('result','=','conditional_pass')]"/>
<filter string="Fail" name="fail_result"
domain="[('result','=','fail')]"/>
<group>
<filter string="Status" name="group_state"
context="{'group_by':'state'}"/>
<filter string="Employee" name="group_employee"
context="{'group_by':'employee_id'}"/>
</group>
</search>
</field>
</record>
<record id="action_fp_cgp_psa" model="ir.actions.act_window">
<field name="name">Personnel Security Assessments</field>
<field name="res_model">fusion.plating.cgp.psa</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_cgp_psa_search"/>
</record>
</odoo>

View File

@@ -0,0 +1,125 @@
<?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>
<record id="view_fp_cgp_receipt_shipment_list" model="ir.ui.view">
<field name="name">fp.cgp.receipt.shipment.list</field>
<field name="model">fusion.plating.cgp.receipt.shipment</field>
<field name="arch" type="xml">
<list string="CGP Receipts &amp; Shipments"
decoration-info="state == 'authorized'"
decoration-success="state == 'executed'"
decoration-muted="state == 'closed'">
<field name="name"/>
<field name="movement_type"/>
<field name="controlled_good_id"/>
<field name="date"/>
<field name="quantity"/>
<field name="from_party"/>
<field name="to_party"/>
<field name="authorized_by_id"/>
<field name="export_permit_ref" optional="hide"/>
<field name="state" widget="badge"
decoration-muted="state == 'draft'"
decoration-info="state == 'authorized'"
decoration-success="state == 'executed'"/>
</list>
</field>
</record>
<record id="view_fp_cgp_receipt_shipment_form" model="ir.ui.view">
<field name="name">fp.cgp.receipt.shipment.form</field>
<field name="model">fusion.plating.cgp.receipt.shipment</field>
<field name="arch" type="xml">
<form string="CGP Receipt / Shipment" class="o_fp_cgp_classified">
<header>
<button name="action_authorize" string="Authorize" type="object"
class="oe_highlight"
invisible="state != 'draft'"/>
<button name="action_execute" string="Execute" type="object"
class="oe_highlight"
invisible="state != 'authorized'"/>
<button name="action_close" string="Close" type="object"
invisible="state != 'executed'"/>
<button name="action_reset_to_draft" string="Reset" type="object"
invisible="state == 'draft'"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,authorized,executed,closed"/>
</header>
<sheet>
<div class="oe_title">
<label for="name"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Movement">
<field name="movement_type"/>
<field name="controlled_good_id"/>
<field name="date"/>
<field name="quantity"/>
</group>
<group string="Parties">
<field name="from_party"/>
<field name="to_party"/>
<field name="carrier"/>
<field name="manifest_ref"/>
<field name="authorized_by_id"/>
<field name="export_permit_ref"/>
</group>
</group>
<notebook>
<page string="Notes">
<field name="notes"/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="view_fp_cgp_receipt_shipment_search" model="ir.ui.view">
<field name="name">fp.cgp.receipt.shipment.search</field>
<field name="model">fusion.plating.cgp.receipt.shipment</field>
<field name="arch" type="xml">
<search string="Receipts &amp; Shipments">
<field name="name"/>
<field name="controlled_good_id"/>
<field name="manifest_ref"/>
<field name="export_permit_ref"/>
<separator/>
<filter string="Receipts" name="receipts"
domain="[('movement_type','=','receipt')]"/>
<filter string="Shipments" name="shipments"
domain="[('movement_type','=','shipment')]"/>
<filter string="Internal Transfers" name="transfers"
domain="[('movement_type','=','internal_transfer')]"/>
<separator/>
<filter string="Draft" name="draft"
domain="[('state','=','draft')]"/>
<filter string="Authorized" name="authorized"
domain="[('state','=','authorized')]"/>
<filter string="Executed" name="executed"
domain="[('state','=','executed')]"/>
<group>
<filter string="Movement Type" name="group_type"
context="{'group_by':'movement_type'}"/>
<filter string="Status" name="group_state"
context="{'group_by':'state'}"/>
</group>
</search>
</field>
</record>
<record id="action_fp_cgp_receipt_shipment" model="ir.actions.act_window">
<field name="name">Receipts &amp; Shipments</field>
<field name="res_model">fusion.plating.cgp.receipt.shipment</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_cgp_receipt_shipment_search"/>
</record>
</odoo>

View File

@@ -0,0 +1,122 @@
<?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>
<record id="view_fp_cgp_registration_list" model="ir.ui.view">
<field name="name">fp.cgp.registration.list</field>
<field name="model">fusion.plating.cgp.registration</field>
<field name="arch" type="xml">
<list string="CGP Registrations"
decoration-success="state == 'registered'"
decoration-warning="state == 'suspended'"
decoration-danger="state in ('expired','revoked')">
<field name="name"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="registration_number"/>
<field name="registration_date"/>
<field name="expiry_date"/>
<field name="designated_official_id"/>
<field name="next_compliance_review"/>
<field name="state" widget="badge"
decoration-success="state == 'registered'"
decoration-warning="state == 'suspended'"
decoration-danger="state in ('expired','revoked')"
decoration-info="state == 'pending'"/>
</list>
</field>
</record>
<record id="view_fp_cgp_registration_form" model="ir.ui.view">
<field name="name">fp.cgp.registration.form</field>
<field name="model">fusion.plating.cgp.registration</field>
<field name="arch" type="xml">
<form string="CGP Registration" class="o_fp_cgp_classified">
<header>
<button name="action_mark_registered" string="Mark Registered"
type="object" class="oe_highlight"
invisible="state != 'pending'"/>
<button name="action_suspend" string="Suspend" type="object"
invisible="state != 'registered'"/>
<button name="action_expire" string="Mark Expired" type="object"
invisible="state not in ('registered','suspended')"/>
<button name="action_revoke" string="Revoke" type="object"
invisible="state == 'revoked'"/>
<button name="action_reset_to_pending" string="Reset" type="object"
invisible="state == 'pending'"/>
<field name="state" widget="statusbar"
statusbar_visible="pending,registered,suspended,expired,revoked"/>
</header>
<sheet>
<div class="oe_title">
<label for="name"/>
<h1><field name="name"/></h1>
</div>
<span class="o_fp_cgp_restricted_badge">Restricted — CGP</span>
<group>
<group string="Registration">
<field name="company_id"/>
<field name="registration_number"/>
<field name="registration_date"/>
<field name="expiry_date"/>
<field name="physical_address"/>
</group>
<group string="People &amp; Plan">
<field name="designated_official_id"/>
<field name="security_plan_doc_id"/>
<field name="last_compliance_review"/>
<field name="next_compliance_review"/>
</group>
</group>
<notebook>
<page string="Notes">
<field name="notes"/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="view_fp_cgp_registration_search" model="ir.ui.view">
<field name="name">fp.cgp.registration.search</field>
<field name="model">fusion.plating.cgp.registration</field>
<field name="arch" type="xml">
<search string="CGP Registrations">
<field name="name"/>
<field name="registration_number"/>
<field name="designated_official_id"/>
<separator/>
<filter string="Registered" name="registered"
domain="[('state','=','registered')]"/>
<filter string="Pending" name="pending"
domain="[('state','=','pending')]"/>
<filter string="Suspended" name="suspended"
domain="[('state','=','suspended')]"/>
<filter string="Expired" name="expired"
domain="[('state','=','expired')]"/>
<separator/>
<filter string="Archived" name="inactive"
domain="[('active','=',False)]"/>
<group>
<filter string="Status" name="group_state"
context="{'group_by':'state'}"/>
<filter string="Company" name="group_company"
context="{'group_by':'company_id'}"/>
</group>
</search>
</field>
</record>
<record id="action_fp_cgp_registration" model="ir.actions.act_window">
<field name="name">CGP Registration</field>
<field name="res_model">fusion.plating.cgp.registration</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_cgp_registration_search"/>
</record>
</odoo>

View File

@@ -0,0 +1,132 @@
<?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>
<record id="view_fp_cgp_security_incident_list" model="ir.ui.view">
<field name="name">fp.cgp.security.incident.list</field>
<field name="model">fusion.plating.cgp.security.incident</field>
<field name="arch" type="xml">
<list string="CGP Security Incidents"
decoration-danger="severity == 'critical'"
decoration-warning="severity == 'major'"
decoration-muted="state == 'closed'">
<field name="name"/>
<field name="incident_date"/>
<field name="incident_type"/>
<field name="severity" widget="badge"
decoration-info="severity == 'minor'"
decoration-warning="severity == 'major'"
decoration-danger="severity == 'critical'"/>
<field name="discovered_by_id"/>
<field name="reported_to_pspc"/>
<field name="pspc_notification_date"/>
<field name="state" widget="badge"
decoration-info="state == 'discovered'"
decoration-warning="state == 'investigating'"
decoration-success="state == 'reported'"
decoration-muted="state == 'closed'"/>
</list>
</field>
</record>
<record id="view_fp_cgp_security_incident_form" model="ir.ui.view">
<field name="name">fp.cgp.security.incident.form</field>
<field name="model">fusion.plating.cgp.security.incident</field>
<field name="arch" type="xml">
<form string="Security Incident" class="o_fp_cgp_classified">
<header>
<button name="action_investigate" string="Investigate"
type="object" class="oe_highlight"
invisible="state != 'discovered'"/>
<button name="action_report" string="Report to PSPC"
type="object" class="oe_highlight"
invisible="state != 'investigating'"/>
<button name="action_close" string="Close" type="object"
invisible="state not in ('investigating','reported')"/>
<button name="action_reset" string="Reset" type="object"
invisible="state == 'discovered'"/>
<field name="state" widget="statusbar"
statusbar_visible="discovered,investigating,reported,closed"/>
</header>
<sheet>
<div class="oe_title">
<label for="name"/>
<h1><field name="name"/></h1>
</div>
<span class="o_fp_cgp_restricted_badge">Restricted — Security Incident</span>
<group>
<group string="Incident">
<field name="incident_date"/>
<field name="discovered_by_id"/>
<field name="incident_type"/>
<field name="severity"/>
</group>
<group string="PSPC Reporting">
<field name="reported_to_pspc"/>
<field name="pspc_notification_date"/>
</group>
</group>
<notebook>
<page string="Description">
<field name="description"/>
</page>
<page string="Containment">
<field name="containment"/>
</page>
<page string="Corrective Action">
<field name="corrective_action"/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="view_fp_cgp_security_incident_search" model="ir.ui.view">
<field name="name">fp.cgp.security.incident.search</field>
<field name="model">fusion.plating.cgp.security.incident</field>
<field name="arch" type="xml">
<search string="Security Incidents">
<field name="name"/>
<field name="discovered_by_id"/>
<separator/>
<filter string="Critical" name="critical"
domain="[('severity','=','critical')]"/>
<filter string="Major" name="major"
domain="[('severity','=','major')]"/>
<filter string="Minor" name="minor"
domain="[('severity','=','minor')]"/>
<separator/>
<filter string="Discovered" name="discovered"
domain="[('state','=','discovered')]"/>
<filter string="Investigating" name="investigating"
domain="[('state','=','investigating')]"/>
<filter string="Reported" name="reported"
domain="[('state','=','reported')]"/>
<filter string="Closed" name="closed"
domain="[('state','=','closed')]"/>
<group>
<filter string="Incident Type" name="group_type"
context="{'group_by':'incident_type'}"/>
<filter string="Severity" name="group_severity"
context="{'group_by':'severity'}"/>
<filter string="Status" name="group_state"
context="{'group_by':'state'}"/>
</group>
</search>
</field>
</record>
<record id="action_fp_cgp_security_incident" model="ir.actions.act_window">
<field name="name">Security Incidents</field>
<field name="res_model">fusion.plating.cgp.security.incident</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_cgp_security_incident_search"/>
</record>
</odoo>

View File

@@ -0,0 +1,126 @@
<?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>
<record id="view_fp_cgp_visitor_list" model="ir.ui.view">
<field name="name">fp.cgp.visitor.list</field>
<field name="model">fusion.plating.cgp.visitor</field>
<field name="arch" type="xml">
<list string="CGP Visitors"
decoration-success="state == 'checked_in'"
decoration-muted="state == 'checked_out'"
decoration-danger="state == 'denied'"
decoration-warning="state == 'cancelled'">
<field name="name"/>
<field name="company_represented"/>
<field name="visit_date"/>
<field name="visit_end"/>
<field name="host_id"/>
<field name="escort_required"/>
<field name="accessed_controlled_area"/>
<field name="approved_by_id"/>
<field name="state" widget="badge"
decoration-info="state == 'scheduled'"
decoration-success="state == 'checked_in'"
decoration-muted="state == 'checked_out'"
decoration-danger="state == 'denied'"
decoration-warning="state == 'cancelled'"/>
</list>
</field>
</record>
<record id="view_fp_cgp_visitor_form" model="ir.ui.view">
<field name="name">fp.cgp.visitor.form</field>
<field name="model">fusion.plating.cgp.visitor</field>
<field name="arch" type="xml">
<form string="CGP Visitor">
<header>
<button name="action_check_in" string="Check In" type="object"
class="oe_highlight"
invisible="state != 'scheduled'"/>
<button name="action_check_out" string="Check Out" type="object"
class="oe_highlight"
invisible="state != 'checked_in'"/>
<button name="action_deny" string="Deny" type="object"
invisible="state not in ('scheduled','checked_in')"/>
<button name="action_cancel" string="Cancel" type="object"
invisible="state in ('checked_out','cancelled','denied')"/>
<field name="state" widget="statusbar"
statusbar_visible="scheduled,checked_in,checked_out"/>
</header>
<sheet>
<div class="oe_title">
<label for="name"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Visit">
<field name="company_represented"/>
<field name="visit_date"/>
<field name="visit_end"/>
<field name="host_id"/>
<field name="purpose"/>
</group>
<group string="Security">
<field name="is_canadian_citizen"/>
<field name="visitor_psa_on_file"/>
<field name="escort_required"/>
<field name="accessed_controlled_area"/>
<field name="approved_by_id"/>
</group>
</group>
<notebook>
<page string="Notes">
<field name="notes"/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="view_fp_cgp_visitor_search" model="ir.ui.view">
<field name="name">fp.cgp.visitor.search</field>
<field name="model">fusion.plating.cgp.visitor</field>
<field name="arch" type="xml">
<search string="Visitors">
<field name="name"/>
<field name="company_represented"/>
<field name="host_id"/>
<separator/>
<filter string="Scheduled" name="scheduled"
domain="[('state','=','scheduled')]"/>
<filter string="Checked In" name="checked_in"
domain="[('state','=','checked_in')]"/>
<filter string="Checked Out" name="checked_out"
domain="[('state','=','checked_out')]"/>
<filter string="Denied" name="denied"
domain="[('state','=','denied')]"/>
<separator/>
<filter string="Accessed Controlled Area" name="accessed_ca"
domain="[('accessed_controlled_area','=',True)]"/>
<filter string="No PSA On File" name="no_psa"
domain="[('visitor_psa_on_file','=',False)]"/>
<group>
<filter string="Status" name="group_state"
context="{'group_by':'state'}"/>
<filter string="Host" name="group_host"
context="{'group_by':'host_id'}"/>
</group>
</search>
</field>
</record>
<record id="action_fp_cgp_visitor" model="ir.actions.act_window">
<field name="name">Visitors</field>
<field name="res_model">fusion.plating.cgp.visitor</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_cgp_visitor_search"/>
</record>
</odoo>

View File

@@ -0,0 +1,64 @@
<?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>
<!-- ===== CGP (parent submenu under the Plating app) ===== -->
<menuitem id="menu_fp_cgp"
name="CGP"
parent="fusion_plating.menu_fp_root"
sequence="70"
groups="group_fusion_plating_cgp_officer"/>
<menuitem id="menu_fp_cgp_registration"
name="Registration"
parent="menu_fp_cgp"
action="action_fp_cgp_registration"
sequence="10"/>
<menuitem id="menu_fp_cgp_ai"
name="Authorized Individuals"
parent="menu_fp_cgp"
action="action_fp_cgp_ai"
sequence="20"/>
<menuitem id="menu_fp_cgp_psa"
name="Personnel Security Assessments"
parent="menu_fp_cgp"
action="action_fp_cgp_psa"
sequence="30"/>
<menuitem id="menu_fp_cgp_visitor"
name="Visitors"
parent="menu_fp_cgp"
action="action_fp_cgp_visitor"
sequence="40"/>
<menuitem id="menu_fp_cgp_controlled_good"
name="Controlled Goods"
parent="menu_fp_cgp"
action="action_fp_cgp_controlled_good"
sequence="50"/>
<menuitem id="menu_fp_cgp_receipt_shipment"
name="Receipts &amp; Shipments"
parent="menu_fp_cgp"
action="action_fp_cgp_receipt_shipment"
sequence="60"/>
<menuitem id="menu_fp_cgp_security_incident"
name="Security Incidents"
parent="menu_fp_cgp"
action="action_fp_cgp_security_incident"
sequence="70"/>
<menuitem id="menu_fp_cgp_access_log"
name="Access Log"
parent="menu_fp_cgp"
action="action_fp_cgp_access_log"
sequence="80"/>
</odoo>

View File

@@ -0,0 +1,37 @@
<?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 HR employee form with a "Security (CGP)" notebook page.
Visible only to the CGP Officer group — a regular HR manager will
not see this tab or any of the PSA linkage.
-->
<record id="view_fp_cgp_hr_employee_form" model="ir.ui.view">
<field name="name">fp.cgp.hr.employee.form</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">
<xpath expr="//notebook" position="inside">
<page string="Security (CGP)"
groups="fusion_plating_cgp.group_fusion_plating_cgp_officer">
<group>
<group string="Personnel Security Assessment">
<field name="x_fc_cgp_psa_id"/>
<field name="x_fc_cgp_psa_status"/>
</group>
<group string="Role &amp; Access">
<field name="x_fc_cgp_ai"/>
<field name="x_fc_cgp_access_level"/>
</group>
</group>
</page>
</xpath>
</field>
</record>
</odoo>