folder rename
This commit is contained in:
67
fusion_plating/fusion_plating_cgp/README.md
Normal file
67
fusion_plating/fusion_plating_cgp/README.md
Normal 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)
|
||||
6
fusion_plating/fusion_plating_cgp/__init__.py
Normal file
6
fusion_plating/fusion_plating_cgp/__init__.py
Normal 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
|
||||
94
fusion_plating/fusion_plating_cgp/__manifest__.py
Normal file
94
fusion_plating/fusion_plating_cgp/__manifest__.py
Normal 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,
|
||||
}
|
||||
@@ -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>
|
||||
79
fusion_plating/fusion_plating_cgp/data/fp_demo_cgp_data.xml
Normal file
79
fusion_plating/fusion_plating_cgp/data/fp_demo_cgp_data.xml
Normal 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>
|
||||
34
fusion_plating/fusion_plating_cgp/data/fp_sequence_data.xml
Normal file
34
fusion_plating/fusion_plating_cgp/data/fp_sequence_data.xml
Normal 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>
|
||||
15
fusion_plating/fusion_plating_cgp/models/__init__.py
Normal file
15
fusion_plating/fusion_plating_cgp/models/__init__.py
Normal 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
|
||||
@@ -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')
|
||||
@@ -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'})
|
||||
@@ -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'})
|
||||
127
fusion_plating/fusion_plating_cgp/models/fp_cgp_psa.py
Normal file
127
fusion_plating/fusion_plating_cgp/models/fp_cgp_psa.py
Normal 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'})
|
||||
@@ -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'})
|
||||
116
fusion_plating/fusion_plating_cgp/models/fp_cgp_registration.py
Normal file
116
fusion_plating/fusion_plating_cgp/models/fp_cgp_registration.py
Normal 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'})
|
||||
@@ -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'})
|
||||
109
fusion_plating/fusion_plating_cgp/models/fp_cgp_visitor.py
Normal file
109
fusion_plating/fusion_plating_cgp/models/fp_cgp_visitor.py
Normal 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'})
|
||||
74
fusion_plating/fusion_plating_cgp/models/hr_employee.py
Normal file
74
fusion_plating/fusion_plating_cgp/models/hr_employee.py
Normal 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'
|
||||
35
fusion_plating/fusion_plating_cgp/models/res_company.py
Normal file
35
fusion_plating/fusion_plating_cgp/models/res_company.py
Normal 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'
|
||||
)
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
109
fusion_plating/fusion_plating_cgp/views/fp_cgp_ai_views.xml
Normal file
109
fusion_plating/fusion_plating_cgp/views/fp_cgp_ai_views.xml
Normal 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>
|
||||
@@ -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>
|
||||
120
fusion_plating/fusion_plating_cgp/views/fp_cgp_psa_views.xml
Normal file
120
fusion_plating/fusion_plating_cgp/views/fp_cgp_psa_views.xml
Normal 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>
|
||||
@@ -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 & 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 & 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 & 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>
|
||||
@@ -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 & 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>
|
||||
@@ -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>
|
||||
126
fusion_plating/fusion_plating_cgp/views/fp_cgp_visitor_views.xml
Normal file
126
fusion_plating/fusion_plating_cgp/views/fp_cgp_visitor_views.xml
Normal 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>
|
||||
64
fusion_plating/fusion_plating_cgp/views/fp_menu.xml
Normal file
64
fusion_plating/fusion_plating_cgp/views/fp_menu.xml
Normal 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 & 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>
|
||||
@@ -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 & Access">
|
||||
<field name="x_fc_cgp_ai"/>
|
||||
<field name="x_fc_cgp_access_level"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user