folder rename
This commit is contained in:
39
fusion_plating/fusion_plating_safety/README.md
Normal file
39
fusion_plating/fusion_plating_safety/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Fusion Plating — Safety (EHS)
|
||||
|
||||
Part of the Fusion Plating product family by Nexa Systems Inc.
|
||||
|
||||
This add-on layers a process-agnostic Environmental, Health and Safety
|
||||
workspace on top of `fusion_plating`. It gives a plating shop a single
|
||||
place to manage day-to-day occupational health and safety obligations
|
||||
without depending on any jurisdiction-specific regulatory pack.
|
||||
|
||||
## Workspaces
|
||||
|
||||
| Workspace | Purpose |
|
||||
|-----------|---------|
|
||||
| SDS Library | Safety Data Sheet repository with version, hazard class, GHS pictograms, language, expiry tracking, and PDF attachment. |
|
||||
| Chemical Inventory | Physical chemical containers with storage location, on-hand quantity, reorder point, and incompatibility relations. |
|
||||
| Training Records | Per-employee training completions with auto-computed expiry and current/expiring/expired status. |
|
||||
| Training Types | Master catalogue of training courses (WHMIS, TDG, first-aid, LOTO, confined space, etc.) with validity windows. |
|
||||
| Exposure Monitoring | Air, biological, noise, and vibration sampling events with OEL reference and percent-of-limit. |
|
||||
| JHSC | Joint Health & Safety Committee with worker and management reps, plus a meeting register. |
|
||||
| Incident Register | Injury, near-miss, first-aid, lost-time, medical, property-damage, and environmental events with investigation, root cause, corrective action, and WSIB Form 7 flagging. |
|
||||
| PPE Issuance | Per-employee PPE issuance log with replacement scheduling. |
|
||||
|
||||
## Installation
|
||||
|
||||
This module depends on `fusion_plating`, `hr`, and `product`.
|
||||
|
||||
```bash
|
||||
docker exec odoo-dev-app odoo -d fusion-dev -u fusion_plating_safety --stop-after-init
|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
||||
- All field names on extended base Odoo models use the `x_fc_` prefix.
|
||||
- Security groups are reused from `fusion_plating` (Operator / Supervisor / Manager).
|
||||
- All copy is Canadian English.
|
||||
- Theme-aware SCSS uses CSS variables only — no hex colours.
|
||||
|
||||
Copyright (c) 2026 Nexa Systems Inc. All rights reserved.
|
||||
License: OPL-1 (Odoo Proprietary License v1.0)
|
||||
6
fusion_plating/fusion_plating_safety/__init__.py
Normal file
6
fusion_plating/fusion_plating_safety/__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
|
||||
91
fusion_plating/fusion_plating_safety/__manifest__.py
Normal file
91
fusion_plating/fusion_plating_safety/__manifest__.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# -*- 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 — Safety (EHS)',
|
||||
'version': '19.0.1.0.0',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'Occupational health and safety for plating shops: SDS library, '
|
||||
'WHMIS/TDG training, exposure monitoring, JHSC, incidents, PPE, '
|
||||
'chemical inventory.',
|
||||
'description': """
|
||||
Fusion Plating — Safety (EHS)
|
||||
=============================
|
||||
|
||||
Part of the Fusion Plating product family by Nexa Systems Inc.
|
||||
|
||||
This add-on layers a process-agnostic Environmental, Health and Safety
|
||||
workspace on top of the core Fusion Plating module. It gives a plating
|
||||
shop a single place to manage day-to-day safety obligations of running a
|
||||
metal-finishing facility without depending on any jurisdiction-specific
|
||||
regulatory pack.
|
||||
|
||||
Included workspaces
|
||||
-------------------
|
||||
* SDS Library — Safety Data Sheet repository with version, hazard class,
|
||||
GHS pictograms, language, expiry tracking and PDF attachment.
|
||||
* Chemical Inventory — physical chemical containers with storage location,
|
||||
on-hand quantity, reorder point and incompatibility relations.
|
||||
* Training Matrix — training type catalogue (WHMIS, TDG, first-aid, LOTO,
|
||||
confined space, etc.) plus per-employee records with auto-computed
|
||||
expiry and current/expiring/expired status.
|
||||
* Exposure Monitoring — air sampling, biological, noise and vibration
|
||||
monitoring events with OEL reference, percent-of-limit and result
|
||||
classification.
|
||||
* JHSC — Joint Health & Safety Committee with worker and management
|
||||
representatives, plus a meeting register with agenda, minutes and
|
||||
action items.
|
||||
* Incident Register — injury, near-miss, first-aid, lost-time, medical,
|
||||
property damage and environmental events with investigation, root
|
||||
cause, corrective action, WSIB Form 7 flagging and lost-time tracking.
|
||||
* PPE Issuance — per-employee personal protective equipment issuance log
|
||||
with replacement scheduling.
|
||||
|
||||
Designed to slot in alongside jurisdiction-specific compliance packs
|
||||
(fusion_plating_compliance_on, fusion_plating_compliance_tor) without
|
||||
duplicating their content.
|
||||
|
||||
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',
|
||||
'hr',
|
||||
'product',
|
||||
],
|
||||
'data': [
|
||||
'security/fp_safety_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'data/fp_sequence_data.xml',
|
||||
'data/fp_training_type_data.xml',
|
||||
'views/fp_sds_views.xml',
|
||||
'views/fp_chemical_views.xml',
|
||||
'views/fp_training_type_views.xml',
|
||||
'views/fp_training_record_views.xml',
|
||||
'views/fp_exposure_monitoring_views.xml',
|
||||
'views/fp_jhsc_views.xml',
|
||||
'views/fp_incident_views.xml',
|
||||
'views/fp_ppe_issuance_views.xml',
|
||||
'views/hr_employee_views.xml',
|
||||
'views/fp_menu.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/fp_demo_safety_data.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'fusion_plating_safety/static/src/scss/fusion_plating_safety.scss',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': False,
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2026 Nexa Systems Inc. — Demo safety data -->
|
||||
<odoo noupdate="1">
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- SDS — Safety Data Sheets -->
|
||||
<!-- ================================================================== -->
|
||||
<record id="demo_sds_chromic_acid" model="fusion.plating.sds">
|
||||
<field name="name">SDS-CrO3-2025</field>
|
||||
<field name="product_name">Chromic Acid</field>
|
||||
<field name="supplier_name">Allied Chemical Supply</field>
|
||||
<field name="cas_number">7738-94-5</field>
|
||||
<field name="version">4.1</field>
|
||||
<field name="issue_date" eval="DateTime.today() - timedelta(days=180)"/>
|
||||
<field name="hazard_class">corrosive</field>
|
||||
<field name="ghs_pictograms">GHS05,GHS06,GHS08</field>
|
||||
<field name="language">en</field>
|
||||
<field name="notes" type="html"><p>Corrosive and toxic. Known carcinogen (Cr VI). Handle with full PPE.</p></field>
|
||||
</record>
|
||||
|
||||
<record id="demo_sds_nickel_sulfate" model="fusion.plating.sds">
|
||||
<field name="name">SDS-NiSO4-2025</field>
|
||||
<field name="product_name">Nickel Sulfate</field>
|
||||
<field name="supplier_name">Great Lakes Chemicals</field>
|
||||
<field name="cas_number">7786-81-4</field>
|
||||
<field name="version">3.0</field>
|
||||
<field name="issue_date" eval="DateTime.today() - timedelta(days=120)"/>
|
||||
<field name="hazard_class">toxic</field>
|
||||
<field name="ghs_pictograms">GHS06,GHS08</field>
|
||||
<field name="language">en</field>
|
||||
<field name="notes" type="html"><p>Toxic and sensitizer. Potential carcinogen. Avoid skin contact and inhalation.</p></field>
|
||||
</record>
|
||||
|
||||
<record id="demo_sds_sulfuric_acid" model="fusion.plating.sds">
|
||||
<field name="name">SDS-H2SO4-2025</field>
|
||||
<field name="product_name">Sulfuric Acid</field>
|
||||
<field name="supplier_name">Allied Chemical Supply</field>
|
||||
<field name="cas_number">7664-93-9</field>
|
||||
<field name="version">5.2</field>
|
||||
<field name="issue_date" eval="DateTime.today() - timedelta(days=240)"/>
|
||||
<field name="hazard_class">corrosive</field>
|
||||
<field name="ghs_pictograms">GHS05</field>
|
||||
<field name="language">both</field>
|
||||
<field name="notes" type="html"><p>Highly corrosive. Causes severe burns. Use acid-resistant PPE.</p></field>
|
||||
</record>
|
||||
|
||||
<record id="demo_sds_sodium_hydroxide" model="fusion.plating.sds">
|
||||
<field name="name">SDS-NaOH-2025</field>
|
||||
<field name="product_name">Sodium Hydroxide</field>
|
||||
<field name="supplier_name">Great Lakes Chemicals</field>
|
||||
<field name="cas_number">1310-73-2</field>
|
||||
<field name="version">2.4</field>
|
||||
<field name="issue_date" eval="DateTime.today() - timedelta(days=90)"/>
|
||||
<field name="hazard_class">corrosive</field>
|
||||
<field name="ghs_pictograms">GHS05</field>
|
||||
<field name="language">en</field>
|
||||
<field name="notes" type="html"><p>Corrosive. Causes severe skin burns and eye damage.</p></field>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- Chemicals — linked to SDS and facility -->
|
||||
<!-- ================================================================== -->
|
||||
<record id="demo_chemical_chromic_acid" model="fusion.plating.chemical">
|
||||
<field name="name">Chromic Acid — Main Store</field>
|
||||
<field name="sds_id" ref="demo_sds_chromic_acid"/>
|
||||
<field name="facility_id" ref="fusion_plating.demo_facility_main"/>
|
||||
<field name="location">Acid Cabinet 1</field>
|
||||
<field name="container_size">25.0</field>
|
||||
<field name="container_uom">kg</field>
|
||||
<field name="quantity_on_hand">18.5</field>
|
||||
<field name="reorder_point">5.0</field>
|
||||
</record>
|
||||
|
||||
<record id="demo_chemical_nickel_sulfate" model="fusion.plating.chemical">
|
||||
<field name="name">Nickel Sulfate — Main Store</field>
|
||||
<field name="sds_id" ref="demo_sds_nickel_sulfate"/>
|
||||
<field name="facility_id" ref="fusion_plating.demo_facility_main"/>
|
||||
<field name="location">Chemical Storage Bay A</field>
|
||||
<field name="container_size">50.0</field>
|
||||
<field name="container_uom">kg</field>
|
||||
<field name="quantity_on_hand">32.0</field>
|
||||
<field name="reorder_point">10.0</field>
|
||||
</record>
|
||||
|
||||
<record id="demo_chemical_sulfuric_acid" model="fusion.plating.chemical">
|
||||
<field name="name">Sulfuric Acid — Main Store</field>
|
||||
<field name="sds_id" ref="demo_sds_sulfuric_acid"/>
|
||||
<field name="facility_id" ref="fusion_plating.demo_facility_main"/>
|
||||
<field name="location">Acid Cabinet 2</field>
|
||||
<field name="container_size">20.0</field>
|
||||
<field name="container_uom">L</field>
|
||||
<field name="quantity_on_hand">12.0</field>
|
||||
<field name="reorder_point">5.0</field>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- Incidents -->
|
||||
<!-- ================================================================== -->
|
||||
<record id="demo_incident_near_miss" model="fusion.plating.incident">
|
||||
<field name="name">INC-DEMO-001</field>
|
||||
<field name="incident_date" eval="DateTime.now() - timedelta(days=14)"/>
|
||||
<field name="facility_id" ref="fusion_plating.demo_facility_main"/>
|
||||
<field name="incident_type">near_miss</field>
|
||||
<field name="location">Chrome plating line 1</field>
|
||||
<field name="description" type="html"><p>Worker slipped on wet floor near rinse station. No injury.</p></field>
|
||||
<field name="immediate_action" type="html"><p>Area cleaned and non-slip mats placed around rinse station.</p></field>
|
||||
<field name="state">draft</field>
|
||||
</record>
|
||||
|
||||
<record id="demo_incident_first_aid" model="fusion.plating.incident">
|
||||
<field name="name">INC-DEMO-002</field>
|
||||
<field name="incident_date" eval="DateTime.now() - timedelta(days=7)"/>
|
||||
<field name="facility_id" ref="fusion_plating.demo_facility_main"/>
|
||||
<field name="incident_type">first_aid</field>
|
||||
<field name="location">Nickel plating area</field>
|
||||
<field name="description" type="html"><p>Minor chemical splash on forearm during tank top-up. First aid administered on-site.</p></field>
|
||||
<field name="immediate_action" type="html"><p>Affected area flushed with water for 15 minutes. First aid kit used.</p></field>
|
||||
<field name="investigation" type="html"><p>Worker was not wearing full-length gloves during transfer. PPE policy reminder issued.</p></field>
|
||||
<field name="state">investigation</field>
|
||||
</record>
|
||||
|
||||
<record id="demo_incident_property_damage" model="fusion.plating.incident">
|
||||
<field name="name">INC-DEMO-003</field>
|
||||
<field name="incident_date" eval="DateTime.now() - timedelta(days=30)"/>
|
||||
<field name="facility_id" ref="fusion_plating.demo_facility_east"/>
|
||||
<field name="incident_type">property_damage</field>
|
||||
<field name="location">Loading dock</field>
|
||||
<field name="description" type="html"><p>Forklift struck chemical storage rack causing minor structural damage. No spill.</p></field>
|
||||
<field name="immediate_action" type="html"><p>Area cordoned off. Structural assessment arranged.</p></field>
|
||||
<field name="investigation" type="html"><p>Tight turning radius at dock entrance identified as contributing factor.</p></field>
|
||||
<field name="root_cause" type="html"><p>Insufficient clearance between rack and dock pillar for forklift turning radius.</p></field>
|
||||
<field name="corrective_action" type="html"><p>Rack relocated 1.5m further from pillar. Floor markings updated.</p></field>
|
||||
<field name="state">closed</field>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- JHSC — Committee -->
|
||||
<!-- ================================================================== -->
|
||||
<record id="demo_jhsc_main" model="fusion.plating.jhsc">
|
||||
<field name="name">Main Plant JHSC</field>
|
||||
<field name="facility_id" ref="fusion_plating.demo_facility_main"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- JHSC Meetings -->
|
||||
<!-- ================================================================== -->
|
||||
<record id="demo_jhsc_meeting_held" model="fusion.plating.jhsc.meeting">
|
||||
<field name="name">Q1 2026 Safety Review</field>
|
||||
<field name="jhsc_id" ref="demo_jhsc_main"/>
|
||||
<field name="meeting_date" eval="DateTime.today() - timedelta(days=30)"/>
|
||||
<field name="agenda" type="html"><p>1. Review of Q1 incidents<br/>2. PPE compliance audit results<br/>3. Ventilation assessment update</p></field>
|
||||
<field name="minutes" type="html"><p>All agenda items reviewed. Two corrective actions assigned. Next meeting set for Q2.</p></field>
|
||||
<field name="state">held</field>
|
||||
</record>
|
||||
|
||||
<record id="demo_jhsc_meeting_planned" model="fusion.plating.jhsc.meeting">
|
||||
<field name="name">Q2 2026 Safety Review</field>
|
||||
<field name="jhsc_id" ref="demo_jhsc_main"/>
|
||||
<field name="meeting_date" eval="DateTime.today() + timedelta(days=60)"/>
|
||||
<field name="agenda" type="html"><p>1. Review of Q1 corrective actions<br/>2. Summer heat stress protocol<br/>3. Emergency drill scheduling</p></field>
|
||||
<field name="state">planned</field>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- PPE Issuance -->
|
||||
<!-- ================================================================== -->
|
||||
<record id="demo_ppe_respirator" model="fusion.plating.ppe.issuance">
|
||||
<field name="employee_id" ref="hr.employee_admin"/>
|
||||
<field name="issue_date" eval="DateTime.today() - timedelta(days=60)"/>
|
||||
<field name="ppe_type">respirator</field>
|
||||
<field name="size">M</field>
|
||||
<field name="quantity">1</field>
|
||||
<field name="next_replacement" eval="DateTime.today() + timedelta(days=120)"/>
|
||||
</record>
|
||||
|
||||
<record id="demo_ppe_gloves" model="fusion.plating.ppe.issuance">
|
||||
<field name="employee_id" ref="hr.employee_admin"/>
|
||||
<field name="issue_date" eval="DateTime.today() - timedelta(days=21)"/>
|
||||
<field name="ppe_type">gloves</field>
|
||||
<field name="size">L</field>
|
||||
<field name="quantity">2</field>
|
||||
<field name="next_replacement" eval="DateTime.today() + timedelta(days=30)"/>
|
||||
</record>
|
||||
|
||||
<record id="demo_ppe_face_shield" model="fusion.plating.ppe.issuance">
|
||||
<field name="employee_id" ref="hr.employee_admin"/>
|
||||
<field name="issue_date" eval="DateTime.today() - timedelta(days=30)"/>
|
||||
<field name="ppe_type">face_shield</field>
|
||||
<field name="size">Standard</field>
|
||||
<field name="quantity">1</field>
|
||||
<field name="next_replacement" eval="DateTime.today() + timedelta(days=150)"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- Exposure Monitoring -->
|
||||
<!-- ================================================================== -->
|
||||
<record id="demo_exposure_chromium" model="fusion.plating.exposure.monitoring">
|
||||
<field name="name">EXP-DEMO-001</field>
|
||||
<field name="facility_id" ref="fusion_plating.demo_facility_main"/>
|
||||
<field name="sample_date" eval="DateTime.today() - timedelta(days=14)"/>
|
||||
<field name="sample_type">personal_air</field>
|
||||
<field name="substance">Chromium (VI)</field>
|
||||
<field name="concentration">0.008</field>
|
||||
<field name="uom">mg/m3</field>
|
||||
<field name="oel_reference">Ontario Reg. 833 TWA</field>
|
||||
<field name="oel_limit">0.025</field>
|
||||
<field name="notes" type="html"><p>Personal air sample collected at chrome plating line 1 during normal operations.</p></field>
|
||||
</record>
|
||||
|
||||
<record id="demo_exposure_nickel" model="fusion.plating.exposure.monitoring">
|
||||
<field name="name">EXP-DEMO-002</field>
|
||||
<field name="facility_id" ref="fusion_plating.demo_facility_main"/>
|
||||
<field name="sample_date" eval="DateTime.today() - timedelta(days=7)"/>
|
||||
<field name="sample_type">personal_air</field>
|
||||
<field name="substance">Nickel (soluble compounds)</field>
|
||||
<field name="concentration">0.05</field>
|
||||
<field name="uom">mg/m3</field>
|
||||
<field name="oel_reference">Ontario Reg. 833 TWA</field>
|
||||
<field name="oel_limit">0.1</field>
|
||||
<field name="notes" type="html"><p>Personal air sample collected at nickel plating station during tank maintenance.</p></field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2026 Nexa Systems Inc.
|
||||
License OPL-1 (Odoo Proprietary License v1.0)
|
||||
Part of the Fusion Plating product family.
|
||||
-->
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record id="seq_fp_incident" model="ir.sequence">
|
||||
<field name="name">Fusion Plating: Incident</field>
|
||||
<field name="code">fusion.plating.incident</field>
|
||||
<field name="prefix">INC/%(year)s%(month)s/</field>
|
||||
<field name="padding">5</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="seq_fp_exposure_monitoring" model="ir.sequence">
|
||||
<field name="name">Fusion Plating: Exposure Monitoring</field>
|
||||
<field name="code">fusion.plating.exposure.monitoring</field>
|
||||
<field name="prefix">EXP/%(year)s%(month)s/</field>
|
||||
<field name="padding">5</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,103 @@
|
||||
<?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.
|
||||
|
||||
Generic Canadian training-type catalogue. Jurisdiction-specific
|
||||
compliance packs may add more.
|
||||
-->
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record id="training_type_whmis_2015" model="fusion.plating.training.type">
|
||||
<field name="name">WHMIS 2015</field>
|
||||
<field name="code">WHMIS</field>
|
||||
<field name="category">whmis</field>
|
||||
<field name="validity_months">12</field>
|
||||
<field name="required_for_roles">All workers exposed to hazardous products</field>
|
||||
<field name="description" type="html"><p>Workplace Hazardous Materials Information System (WHMIS 2015 / GHS aligned). Retraining required annually and whenever a new hazardous product is introduced.</p></field>
|
||||
</record>
|
||||
|
||||
<record id="training_type_tdg_road" model="fusion.plating.training.type">
|
||||
<field name="name">TDG Road</field>
|
||||
<field name="code">TDG</field>
|
||||
<field name="category">tdg</field>
|
||||
<field name="validity_months">36</field>
|
||||
<field name="required_for_roles">Shipping clerks, drivers handling dangerous goods</field>
|
||||
<field name="description" type="html"><p>Transportation of Dangerous Goods training for road transport. Certificate valid for 36 months.</p></field>
|
||||
</record>
|
||||
|
||||
<record id="training_type_first_aid_cpr" model="fusion.plating.training.type">
|
||||
<field name="name">Standard First Aid / CPR</field>
|
||||
<field name="code">FA-CPR</field>
|
||||
<field name="category">first_aid</field>
|
||||
<field name="validity_months">36</field>
|
||||
<field name="required_for_roles">Designated first-aid attendants</field>
|
||||
<field name="description" type="html"><p>Standard First Aid with CPR Level C. Required to maintain the workplace first-aid roster.</p></field>
|
||||
</record>
|
||||
|
||||
<record id="training_type_loto" model="fusion.plating.training.type">
|
||||
<field name="name">Lockout / Tagout</field>
|
||||
<field name="code">LOTO</field>
|
||||
<field name="category">loto</field>
|
||||
<field name="validity_months">24</field>
|
||||
<field name="required_for_roles">Maintenance, electricians, anyone servicing energised equipment</field>
|
||||
<field name="description" type="html"><p>Energy isolation procedures for equipment service and maintenance.</p></field>
|
||||
</record>
|
||||
|
||||
<record id="training_type_confined_space" model="fusion.plating.training.type">
|
||||
<field name="name">Confined Space Entry</field>
|
||||
<field name="code">CSE</field>
|
||||
<field name="category">confined_space</field>
|
||||
<field name="validity_months">12</field>
|
||||
<field name="required_for_roles">Tank cleaning crew, attendants, supervisors</field>
|
||||
<field name="description" type="html"><p>Confined space awareness, entry, attendant and rescue procedures. Tank entry training required for plating shops.</p></field>
|
||||
</record>
|
||||
|
||||
<record id="training_type_spill_response" model="fusion.plating.training.type">
|
||||
<field name="name">Chemical Spill Response</field>
|
||||
<field name="code">SPILL</field>
|
||||
<field name="category">process_specific</field>
|
||||
<field name="validity_months">12</field>
|
||||
<field name="required_for_roles">Operators, supervisors</field>
|
||||
<field name="description" type="html"><p>Identification, containment, neutralisation and reporting of plating chemistry spills.</p></field>
|
||||
</record>
|
||||
|
||||
<record id="training_type_respirator_fit" model="fusion.plating.training.type">
|
||||
<field name="name">Respirator Fit Test</field>
|
||||
<field name="code">FIT</field>
|
||||
<field name="category">ppe</field>
|
||||
<field name="validity_months">12</field>
|
||||
<field name="required_for_roles">Workers required to wear tight-fitting respirators</field>
|
||||
<field name="description" type="html"><p>Quantitative or qualitative respirator fit testing per CSA Z94.4.</p></field>
|
||||
</record>
|
||||
|
||||
<record id="training_type_forklift" model="fusion.plating.training.type">
|
||||
<field name="name">Forklift Operator</field>
|
||||
<field name="code">FL</field>
|
||||
<field name="category">other</field>
|
||||
<field name="validity_months">36</field>
|
||||
<field name="required_for_roles">Material handlers</field>
|
||||
<field name="description" type="html"><p>Powered industrial truck operator certification.</p></field>
|
||||
</record>
|
||||
|
||||
<record id="training_type_jhsc_member" model="fusion.plating.training.type">
|
||||
<field name="name">JHSC Member Training</field>
|
||||
<field name="code">JHSC</field>
|
||||
<field name="category">other</field>
|
||||
<field name="validity_months">12</field>
|
||||
<field name="required_for_roles">JHSC certified members</field>
|
||||
<field name="description" type="html"><p>Joint Health and Safety Committee certification training (Part 1 / Part 2 refresh).</p></field>
|
||||
</record>
|
||||
|
||||
<record id="training_type_supervisor_awareness" model="fusion.plating.training.type">
|
||||
<field name="name">Supervisor Awareness</field>
|
||||
<field name="code">SUP-AW</field>
|
||||
<field name="category">other</field>
|
||||
<field name="validity_months">0</field>
|
||||
<field name="required_for_roles">All supervisors</field>
|
||||
<field name="description" type="html"><p>One-time supervisor health and safety awareness training (e.g. Ontario "Supervisor Health and Safety Awareness in 5 Steps").</p></field>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
15
fusion_plating/fusion_plating_safety/models/__init__.py
Normal file
15
fusion_plating/fusion_plating_safety/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_sds
|
||||
from . import fp_chemical
|
||||
from . import fp_training_type
|
||||
from . import fp_training_record
|
||||
from . import fp_exposure_monitoring
|
||||
from . import fp_jhsc
|
||||
from . import fp_jhsc_meeting
|
||||
from . import fp_incident
|
||||
from . import fp_ppe_issuance
|
||||
from . import hr_employee
|
||||
96
fusion_plating/fusion_plating_safety/models/fp_chemical.py
Normal file
96
fusion_plating/fusion_plating_safety/models/fp_chemical.py
Normal file
@@ -0,0 +1,96 @@
|
||||
# -*- 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 FpChemical(models.Model):
|
||||
"""Physical chemical container in the shop's chemical inventory.
|
||||
|
||||
A chemical record represents a managed container — drum, tote, jug,
|
||||
cylinder — of a specific product, stored in a specific facility and
|
||||
location, with on-hand quantity and reorder thresholds. It links to the
|
||||
Safety Data Sheet that governs handling, and may optionally link to the
|
||||
Odoo product/stock record when the same chemistry is also tracked as
|
||||
inventory.
|
||||
|
||||
Storage compatibility (acid vs base, oxidizer vs flammable, etc.) is
|
||||
captured via a self-referential many2many of incompatible chemicals so
|
||||
a future workflow can warn on co-located storage.
|
||||
"""
|
||||
_name = 'fusion.plating.chemical'
|
||||
_description = 'Fusion Plating — Chemical'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'name'
|
||||
|
||||
name = fields.Char(
|
||||
string='Chemical',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
sds_id = fields.Many2one(
|
||||
'fusion.plating.sds',
|
||||
string='Safety Data Sheet',
|
||||
tracking=True,
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
'product.product',
|
||||
string='Stock Product',
|
||||
help='Optional link to the Odoo product when the chemistry is also '
|
||||
'tracked as inventory.',
|
||||
)
|
||||
facility_id = fields.Many2one(
|
||||
'fusion.plating.facility',
|
||||
string='Facility',
|
||||
tracking=True,
|
||||
)
|
||||
location = fields.Char(
|
||||
string='Storage Location',
|
||||
help='Free-text storage location, e.g. "Acid Cabinet 2" or "Drum bay B".',
|
||||
)
|
||||
container_size = fields.Float(
|
||||
string='Container Size',
|
||||
)
|
||||
container_uom = fields.Char(
|
||||
string='Container UoM',
|
||||
help='Free-text unit of measure for the container size, '
|
||||
'e.g. L, kg, lb, gal.',
|
||||
)
|
||||
quantity_on_hand = fields.Float(
|
||||
string='Quantity On Hand',
|
||||
tracking=True,
|
||||
)
|
||||
reorder_point = fields.Float(
|
||||
string='Reorder Point',
|
||||
help='When quantity on hand falls below this level, the chemical '
|
||||
'should be reordered.',
|
||||
)
|
||||
incompatible_with_ids = fields.Many2many(
|
||||
'fusion.plating.chemical',
|
||||
'fp_chemical_incompat_rel',
|
||||
'chemical_id',
|
||||
'incompatible_id',
|
||||
string='Incompatible With',
|
||||
help='Chemicals that must not be stored next to this one.',
|
||||
)
|
||||
needs_reorder = fields.Boolean(
|
||||
string='Needs Reorder',
|
||||
compute='_compute_needs_reorder',
|
||||
store=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
string='Company',
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
notes = fields.Html(string='Notes')
|
||||
|
||||
@api.depends('quantity_on_hand', 'reorder_point')
|
||||
def _compute_needs_reorder(self):
|
||||
for rec in self:
|
||||
rec.needs_reorder = bool(
|
||||
rec.reorder_point and rec.quantity_on_hand <= rec.reorder_point
|
||||
)
|
||||
@@ -0,0 +1,139 @@
|
||||
# -*- 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 FpExposureMonitoring(models.Model):
|
||||
"""An exposure monitoring sample.
|
||||
|
||||
A monitoring event captures one measurement of a worker's or area's
|
||||
exposure to a hazardous agent — air contaminant, biological marker,
|
||||
noise or vibration. The result is compared against an Occupational
|
||||
Exposure Limit (OEL), most commonly the time-weighted average (TWA)
|
||||
or short-term exposure limit (STEL) published by the relevant
|
||||
jurisdiction (e.g. Ontario Reg. 833 in Canada).
|
||||
|
||||
The percent-of-OEL is calculated automatically and the record is
|
||||
classified as below, approaching or exceeding the limit, providing
|
||||
an early warning when controls need to be tightened.
|
||||
"""
|
||||
_name = 'fusion.plating.exposure.monitoring'
|
||||
_description = 'Fusion Plating — Exposure Monitoring'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'sample_date desc, id desc'
|
||||
_rec_name = 'name'
|
||||
|
||||
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',
|
||||
ondelete='set null',
|
||||
tracking=True,
|
||||
)
|
||||
facility_id = fields.Many2one(
|
||||
'fusion.plating.facility',
|
||||
string='Facility',
|
||||
tracking=True,
|
||||
)
|
||||
sample_date = fields.Date(
|
||||
string='Sample Date',
|
||||
required=True,
|
||||
default=fields.Date.context_today,
|
||||
tracking=True,
|
||||
)
|
||||
sample_type = fields.Selection(
|
||||
[
|
||||
('personal_air', 'Personal Air'),
|
||||
('area_air', 'Area Air'),
|
||||
('biological', 'Biological'),
|
||||
('noise', 'Noise'),
|
||||
('vibration', 'Vibration'),
|
||||
],
|
||||
string='Sample Type',
|
||||
default='personal_air',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
substance = fields.Char(
|
||||
string='Substance / Agent',
|
||||
tracking=True,
|
||||
)
|
||||
concentration = fields.Float(
|
||||
string='Concentration',
|
||||
tracking=True,
|
||||
)
|
||||
uom = fields.Char(
|
||||
string='Unit of Measure',
|
||||
help='Free-text unit, e.g. mg/m3, ppm, dBA.',
|
||||
)
|
||||
oel_reference = fields.Char(
|
||||
string='OEL Reference',
|
||||
help='Citation for the exposure limit, e.g. "Ontario Reg. 833 TWA".',
|
||||
)
|
||||
oel_limit = fields.Float(
|
||||
string='OEL Limit',
|
||||
)
|
||||
percent_of_oel = fields.Float(
|
||||
string='% of OEL',
|
||||
compute='_compute_percent_of_oel',
|
||||
store=True,
|
||||
)
|
||||
result = fields.Selection(
|
||||
[
|
||||
('below', 'Below'),
|
||||
('approaching', 'Approaching'),
|
||||
('exceed', 'Exceeds'),
|
||||
],
|
||||
string='Result',
|
||||
compute='_compute_result',
|
||||
store=True,
|
||||
tracking=True,
|
||||
)
|
||||
notes = fields.Html(
|
||||
string='Notes',
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
string='Company',
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
# ==========================================================================
|
||||
# Defaults
|
||||
# ==========================================================================
|
||||
@api.model
|
||||
def _default_name(self):
|
||||
seq = self.env['ir.sequence'].next_by_code('fusion.plating.exposure.monitoring')
|
||||
return seq or '/'
|
||||
|
||||
# ==========================================================================
|
||||
# Computes
|
||||
# ==========================================================================
|
||||
@api.depends('concentration', 'oel_limit')
|
||||
def _compute_percent_of_oel(self):
|
||||
for rec in self:
|
||||
if rec.oel_limit:
|
||||
rec.percent_of_oel = (rec.concentration / rec.oel_limit) * 100.0
|
||||
else:
|
||||
rec.percent_of_oel = 0.0
|
||||
|
||||
@api.depends('percent_of_oel')
|
||||
def _compute_result(self):
|
||||
for rec in self:
|
||||
if rec.percent_of_oel >= 100.0:
|
||||
rec.result = 'exceed'
|
||||
elif rec.percent_of_oel >= 50.0:
|
||||
rec.result = 'approaching'
|
||||
else:
|
||||
rec.result = 'below'
|
||||
142
fusion_plating/fusion_plating_safety/models/fp_incident.py
Normal file
142
fusion_plating/fusion_plating_safety/models/fp_incident.py
Normal file
@@ -0,0 +1,142 @@
|
||||
# -*- 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 FpIncident(models.Model):
|
||||
"""A safety incident, near-miss, or environmental event.
|
||||
|
||||
The incident register is the central log of anything that did, or
|
||||
almost did, harm a worker or the environment. Each record walks
|
||||
through draft → investigation → closed and captures who was hurt,
|
||||
where, what happened, the immediate response, the investigation
|
||||
findings, the root cause and the corrective action.
|
||||
|
||||
For Ontario operations the WSIB Form 7 fields flag whether the
|
||||
incident is reportable to the Workplace Safety and Insurance Board
|
||||
and whether the form has actually been filed.
|
||||
"""
|
||||
_name = 'fusion.plating.incident'
|
||||
_description = 'Fusion Plating — Incident'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'incident_date desc, id desc'
|
||||
_rec_name = 'name'
|
||||
|
||||
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',
|
||||
required=True,
|
||||
default=fields.Datetime.now,
|
||||
tracking=True,
|
||||
)
|
||||
facility_id = fields.Many2one(
|
||||
'fusion.plating.facility',
|
||||
string='Facility',
|
||||
tracking=True,
|
||||
)
|
||||
reported_by_id = fields.Many2one(
|
||||
'res.users',
|
||||
string='Reported By',
|
||||
default=lambda self: self.env.user,
|
||||
tracking=True,
|
||||
)
|
||||
incident_type = fields.Selection(
|
||||
[
|
||||
('injury', 'Injury'),
|
||||
('near_miss', 'Near Miss'),
|
||||
('first_aid', 'First Aid'),
|
||||
('lost_time', 'Lost Time'),
|
||||
('medical', 'Medical'),
|
||||
('property_damage', 'Property Damage'),
|
||||
('environmental', 'Environmental'),
|
||||
('other', 'Other'),
|
||||
],
|
||||
string='Type',
|
||||
default='near_miss',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
employee_id = fields.Many2one(
|
||||
'hr.employee',
|
||||
string='Employee Involved',
|
||||
ondelete='set null',
|
||||
tracking=True,
|
||||
)
|
||||
location = fields.Char(
|
||||
string='Location',
|
||||
tracking=True,
|
||||
)
|
||||
description = fields.Html(
|
||||
string='Description',
|
||||
)
|
||||
immediate_action = fields.Html(
|
||||
string='Immediate Action',
|
||||
)
|
||||
investigation = fields.Html(
|
||||
string='Investigation',
|
||||
)
|
||||
root_cause = fields.Html(
|
||||
string='Root Cause',
|
||||
)
|
||||
corrective_action = fields.Html(
|
||||
string='Corrective Action',
|
||||
)
|
||||
wsib_reportable = fields.Boolean(
|
||||
string='WSIB Reportable',
|
||||
tracking=True,
|
||||
)
|
||||
wsib_form_7_submitted = fields.Boolean(
|
||||
string='WSIB Form 7 Submitted',
|
||||
tracking=True,
|
||||
)
|
||||
lost_time_days = fields.Integer(
|
||||
string='Lost-Time Days',
|
||||
tracking=True,
|
||||
)
|
||||
state = fields.Selection(
|
||||
[
|
||||
('draft', 'Draft'),
|
||||
('investigation', 'Investigation'),
|
||||
('closed', 'Closed'),
|
||||
],
|
||||
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)
|
||||
|
||||
# ==========================================================================
|
||||
# Defaults
|
||||
# ==========================================================================
|
||||
@api.model
|
||||
def _default_name(self):
|
||||
seq = self.env['ir.sequence'].next_by_code('fusion.plating.incident')
|
||||
return seq or '/'
|
||||
|
||||
# ==========================================================================
|
||||
# Actions
|
||||
# ==========================================================================
|
||||
def action_start_investigation(self):
|
||||
self.write({'state': 'investigation'})
|
||||
|
||||
def action_close(self):
|
||||
self.write({'state': 'closed'})
|
||||
|
||||
def action_reopen(self):
|
||||
self.write({'state': 'draft'})
|
||||
80
fusion_plating/fusion_plating_safety/models/fp_jhsc.py
Normal file
80
fusion_plating/fusion_plating_safety/models/fp_jhsc.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# -*- 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 FpJhsc(models.Model):
|
||||
"""A Joint Health and Safety Committee.
|
||||
|
||||
Most Canadian jurisdictions require workplaces above a certain employee
|
||||
count to maintain a Joint Health and Safety Committee (JHSC) with at
|
||||
least one worker representative and one management representative.
|
||||
The committee meets on a regular cadence to review hazards, incidents,
|
||||
and proposed improvements.
|
||||
|
||||
A site can have one or more committees (e.g. when multiple buildings
|
||||
or shifts each maintain their own). Membership is tracked as overall
|
||||
members plus the specific worker and management representative subsets.
|
||||
"""
|
||||
_name = 'fusion.plating.jhsc'
|
||||
_description = 'Fusion Plating — JHSC'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'name'
|
||||
|
||||
name = fields.Char(
|
||||
string='Committee',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
facility_id = fields.Many2one(
|
||||
'fusion.plating.facility',
|
||||
string='Facility',
|
||||
tracking=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
string='Company',
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
member_ids = fields.Many2many(
|
||||
'hr.employee',
|
||||
'fp_jhsc_member_rel',
|
||||
'jhsc_id',
|
||||
'employee_id',
|
||||
string='Members',
|
||||
)
|
||||
worker_rep_ids = fields.Many2many(
|
||||
'hr.employee',
|
||||
'fp_jhsc_worker_rep_rel',
|
||||
'jhsc_id',
|
||||
'employee_id',
|
||||
string='Worker Representatives',
|
||||
)
|
||||
mgmt_rep_ids = fields.Many2many(
|
||||
'hr.employee',
|
||||
'fp_jhsc_mgmt_rep_rel',
|
||||
'jhsc_id',
|
||||
'employee_id',
|
||||
string='Management Representatives',
|
||||
)
|
||||
meeting_ids = fields.One2many(
|
||||
'fusion.plating.jhsc.meeting',
|
||||
'jhsc_id',
|
||||
string='Meetings',
|
||||
)
|
||||
member_count = fields.Integer(
|
||||
compute='_compute_counts',
|
||||
)
|
||||
meeting_count = fields.Integer(
|
||||
compute='_compute_counts',
|
||||
)
|
||||
|
||||
def _compute_counts(self):
|
||||
for rec in self:
|
||||
rec.member_count = len(rec.member_ids)
|
||||
rec.meeting_count = len(rec.meeting_ids)
|
||||
@@ -0,0 +1,95 @@
|
||||
# -*- 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 FpJhscMeeting(models.Model):
|
||||
"""A scheduled or held JHSC meeting.
|
||||
|
||||
Captures the meeting's date, attendees, agenda, minutes, action items
|
||||
and supporting attachments. The state field walks the meeting through
|
||||
its lifecycle from planned through closed so the JHSC can audit which
|
||||
meetings still need minutes posted.
|
||||
"""
|
||||
_name = 'fusion.plating.jhsc.meeting'
|
||||
_description = 'Fusion Plating — JHSC Meeting'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'meeting_date desc, id desc'
|
||||
_rec_name = 'name'
|
||||
|
||||
name = fields.Char(
|
||||
string='Subject',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
jhsc_id = fields.Many2one(
|
||||
'fusion.plating.jhsc',
|
||||
string='Committee',
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
tracking=True,
|
||||
)
|
||||
facility_id = fields.Many2one(
|
||||
'fusion.plating.facility',
|
||||
related='jhsc_id.facility_id',
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
meeting_date = fields.Date(
|
||||
string='Meeting Date',
|
||||
required=True,
|
||||
default=fields.Date.context_today,
|
||||
tracking=True,
|
||||
)
|
||||
attendee_ids = fields.Many2many(
|
||||
'hr.employee',
|
||||
'fp_jhsc_meeting_attendee_rel',
|
||||
'meeting_id',
|
||||
'employee_id',
|
||||
string='Attendees',
|
||||
)
|
||||
agenda = fields.Html(
|
||||
string='Agenda',
|
||||
)
|
||||
minutes = fields.Html(
|
||||
string='Minutes',
|
||||
)
|
||||
action_items = fields.Html(
|
||||
string='Action Items',
|
||||
)
|
||||
state = fields.Selection(
|
||||
[
|
||||
('planned', 'Planned'),
|
||||
('held', 'Held'),
|
||||
('minutes_ready', 'Minutes Ready'),
|
||||
('closed', 'Closed'),
|
||||
],
|
||||
string='Status',
|
||||
default='planned',
|
||||
tracking=True,
|
||||
required=True,
|
||||
)
|
||||
attachment_ids = fields.Many2many(
|
||||
'ir.attachment',
|
||||
'fp_jhsc_meeting_attachment_rel',
|
||||
'meeting_id',
|
||||
'attachment_id',
|
||||
string='Attachments',
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
string='Company',
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
|
||||
def action_mark_held(self):
|
||||
self.write({'state': 'held'})
|
||||
|
||||
def action_post_minutes(self):
|
||||
self.write({'state': 'minutes_ready'})
|
||||
|
||||
def action_close(self):
|
||||
self.write({'state': 'closed'})
|
||||
@@ -0,0 +1,92 @@
|
||||
# -*- 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 FpPpeIssuance(models.Model):
|
||||
"""Per-employee Personal Protective Equipment issuance log.
|
||||
|
||||
Each record captures one issuance of a piece of PPE — respirator,
|
||||
gloves, apron, face shield — to an employee, with the size, quantity,
|
||||
issue date and the date the equipment is next due for replacement.
|
||||
|
||||
A historical PPE log demonstrates that the employer is providing
|
||||
appropriate protective equipment, which is itself an obligation under
|
||||
most occupational health and safety regulations.
|
||||
"""
|
||||
_name = 'fusion.plating.ppe.issuance'
|
||||
_description = 'Fusion Plating — PPE Issuance'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'issue_date desc, id desc'
|
||||
_rec_name = 'display_name'
|
||||
|
||||
employee_id = fields.Many2one(
|
||||
'hr.employee',
|
||||
string='Employee',
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
tracking=True,
|
||||
)
|
||||
issue_date = fields.Date(
|
||||
string='Issue Date',
|
||||
required=True,
|
||||
default=fields.Date.context_today,
|
||||
tracking=True,
|
||||
)
|
||||
ppe_type = fields.Selection(
|
||||
[
|
||||
('respirator', 'Respirator'),
|
||||
('gloves', 'Gloves'),
|
||||
('apron', 'Apron'),
|
||||
('face_shield', 'Face Shield'),
|
||||
('safety_glasses', 'Safety Glasses'),
|
||||
('boots', 'Boots'),
|
||||
('hearing', 'Hearing Protection'),
|
||||
('other', 'Other'),
|
||||
],
|
||||
string='PPE Type',
|
||||
required=True,
|
||||
default='gloves',
|
||||
tracking=True,
|
||||
)
|
||||
size = fields.Char(
|
||||
string='Size',
|
||||
)
|
||||
quantity = fields.Integer(
|
||||
string='Quantity',
|
||||
default=1,
|
||||
)
|
||||
next_replacement = fields.Date(
|
||||
string='Next Replacement',
|
||||
tracking=True,
|
||||
)
|
||||
notes = fields.Html(
|
||||
string='Notes',
|
||||
)
|
||||
display_name = fields.Char(
|
||||
compute='_compute_display_name',
|
||||
store=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
related='employee_id.company_id',
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
@api.depends('employee_id', 'ppe_type', 'issue_date')
|
||||
def _compute_display_name(self):
|
||||
ppe_label = dict(self._fields['ppe_type'].selection)
|
||||
for rec in self:
|
||||
parts = []
|
||||
if rec.employee_id:
|
||||
parts.append(rec.employee_id.name)
|
||||
if rec.ppe_type:
|
||||
parts.append(ppe_label.get(rec.ppe_type, rec.ppe_type))
|
||||
if rec.issue_date:
|
||||
parts.append(fields.Date.to_string(rec.issue_date))
|
||||
rec.display_name = ' — '.join(parts) if parts else 'PPE Issuance'
|
||||
180
fusion_plating/fusion_plating_safety/models/fp_sds.py
Normal file
180
fusion_plating/fusion_plating_safety/models/fp_sds.py
Normal file
@@ -0,0 +1,180 @@
|
||||
# -*- 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 FpSds(models.Model):
|
||||
"""Safety Data Sheet library entry.
|
||||
|
||||
A Safety Data Sheet (SDS) is a 16-section document supplied by a chemical
|
||||
manufacturer that describes the hazards, handling, storage, exposure
|
||||
controls and emergency information for a product. Under the WHMIS 2015 /
|
||||
GHS framework an SDS is considered current for three years from its
|
||||
issue date — after which a refresh from the supplier is required.
|
||||
|
||||
Each SDS in the library carries supplier metadata, hazard classification,
|
||||
GHS pictogram codes, language coverage and a link to the original PDF
|
||||
via ir.attachment.
|
||||
"""
|
||||
_name = 'fusion.plating.sds'
|
||||
_description = 'Fusion Plating — Safety Data Sheet'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'product_name, version desc, issue_date desc'
|
||||
_rec_name = 'name'
|
||||
|
||||
name = fields.Char(
|
||||
string='Reference',
|
||||
required=True,
|
||||
tracking=True,
|
||||
help='Internal reference for this SDS entry, often the product name.',
|
||||
)
|
||||
product_name = fields.Char(
|
||||
string='Product Name',
|
||||
tracking=True,
|
||||
)
|
||||
supplier_name = fields.Char(
|
||||
string='Supplier (Text)',
|
||||
tracking=True,
|
||||
help='Free-text supplier name as printed on the SDS, used when '
|
||||
'no res.partner record exists yet.',
|
||||
)
|
||||
supplier_id = fields.Many2one(
|
||||
'res.partner',
|
||||
string='Supplier',
|
||||
tracking=True,
|
||||
)
|
||||
cas_number = fields.Char(
|
||||
string='CAS Number',
|
||||
tracking=True,
|
||||
)
|
||||
version = fields.Char(
|
||||
string='Version',
|
||||
tracking=True,
|
||||
)
|
||||
issue_date = fields.Date(
|
||||
string='Issue Date',
|
||||
tracking=True,
|
||||
)
|
||||
expiry_date = fields.Date(
|
||||
string='Expiry Date',
|
||||
compute='_compute_expiry_date',
|
||||
store=True,
|
||||
tracking=True,
|
||||
help='Computed as issue date + 3 years per WHMIS / GHS rule. '
|
||||
'Refresh from the supplier is required before this date.',
|
||||
)
|
||||
state = fields.Selection(
|
||||
[
|
||||
('current', 'Current'),
|
||||
('expiring_soon', 'Expiring Soon'),
|
||||
('expired', 'Expired'),
|
||||
('withdrawn', 'Withdrawn'),
|
||||
],
|
||||
string='Status',
|
||||
compute='_compute_state',
|
||||
store=True,
|
||||
tracking=True,
|
||||
)
|
||||
hazard_class = fields.Selection(
|
||||
[
|
||||
('flammable', 'Flammable'),
|
||||
('oxidizer', 'Oxidizer'),
|
||||
('compressed_gas', 'Compressed Gas'),
|
||||
('corrosive', 'Corrosive'),
|
||||
('toxic', 'Toxic'),
|
||||
('carcinogen', 'Carcinogen'),
|
||||
('reproductive_toxin', 'Reproductive Toxin'),
|
||||
('sensitizer', 'Sensitizer'),
|
||||
('aquatic_toxin', 'Aquatic Toxin'),
|
||||
('other', 'Other'),
|
||||
],
|
||||
string='Primary Hazard Class',
|
||||
tracking=True,
|
||||
)
|
||||
ghs_pictograms = fields.Char(
|
||||
string='GHS Pictograms',
|
||||
help='Comma-separated GHS pictogram codes, e.g. GHS01,GHS02,GHS05.',
|
||||
)
|
||||
language = fields.Selection(
|
||||
[
|
||||
('en', 'English'),
|
||||
('fr', 'French'),
|
||||
('both', 'Bilingual (EN/FR)'),
|
||||
],
|
||||
string='Language',
|
||||
default='en',
|
||||
)
|
||||
attachment_id = fields.Many2one(
|
||||
'ir.attachment',
|
||||
string='SDS Document',
|
||||
help='The original SDS PDF supplied by the manufacturer.',
|
||||
)
|
||||
notes = fields.Html(
|
||||
string='Notes',
|
||||
)
|
||||
withdrawn = fields.Boolean(
|
||||
string='Withdrawn',
|
||||
tracking=True,
|
||||
help='Manually mark this SDS as withdrawn (e.g. product discontinued).',
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
string='Company',
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
|
||||
chemical_ids = fields.One2many(
|
||||
'fusion.plating.chemical',
|
||||
'sds_id',
|
||||
string='Chemicals',
|
||||
)
|
||||
chemical_count = fields.Integer(
|
||||
compute='_compute_chemical_count',
|
||||
)
|
||||
|
||||
# ==========================================================================
|
||||
# Computes
|
||||
# ==========================================================================
|
||||
@api.depends('issue_date')
|
||||
def _compute_expiry_date(self):
|
||||
for rec in self:
|
||||
if rec.issue_date:
|
||||
rec.expiry_date = rec.issue_date + relativedelta(years=3)
|
||||
else:
|
||||
rec.expiry_date = False
|
||||
|
||||
@api.depends('expiry_date', 'withdrawn')
|
||||
def _compute_state(self):
|
||||
today = fields.Date.context_today(self)
|
||||
warn_window = today + relativedelta(months=3)
|
||||
for rec in self:
|
||||
if rec.withdrawn:
|
||||
rec.state = 'withdrawn'
|
||||
elif not rec.expiry_date:
|
||||
rec.state = 'current'
|
||||
elif rec.expiry_date < today:
|
||||
rec.state = 'expired'
|
||||
elif rec.expiry_date <= warn_window:
|
||||
rec.state = 'expiring_soon'
|
||||
else:
|
||||
rec.state = 'current'
|
||||
|
||||
def _compute_chemical_count(self):
|
||||
for rec in self:
|
||||
rec.chemical_count = len(rec.chemical_ids)
|
||||
|
||||
# ==========================================================================
|
||||
# Actions
|
||||
# ==========================================================================
|
||||
def action_mark_withdrawn(self):
|
||||
self.write({'withdrawn': True})
|
||||
|
||||
def action_mark_active(self):
|
||||
self.write({'withdrawn': False})
|
||||
@@ -0,0 +1,126 @@
|
||||
# -*- 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 FpTrainingRecord(models.Model):
|
||||
"""Per-employee record of a completed training course.
|
||||
|
||||
Each record links an employee to a training type and captures when the
|
||||
course was completed, who delivered it, the certificate reference, and
|
||||
optionally a numeric score. The expiry date is computed automatically
|
||||
from the training type's validity window, and the state field rolls up
|
||||
to current / expiring soon / expired so the training matrix can be
|
||||
queried at a glance.
|
||||
"""
|
||||
_name = 'fusion.plating.training.record'
|
||||
_description = 'Fusion Plating — Training Record'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'completion_date desc, id desc'
|
||||
_rec_name = 'display_name'
|
||||
|
||||
employee_id = fields.Many2one(
|
||||
'hr.employee',
|
||||
string='Employee',
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
tracking=True,
|
||||
)
|
||||
training_type_id = fields.Many2one(
|
||||
'fusion.plating.training.type',
|
||||
string='Training Type',
|
||||
required=True,
|
||||
ondelete='restrict',
|
||||
tracking=True,
|
||||
)
|
||||
completion_date = fields.Date(
|
||||
string='Completion Date',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
expiry_date = fields.Date(
|
||||
string='Expiry Date',
|
||||
compute='_compute_expiry_date',
|
||||
store=True,
|
||||
)
|
||||
trainer = fields.Char(
|
||||
string='Trainer',
|
||||
tracking=True,
|
||||
)
|
||||
certificate_ref = fields.Char(
|
||||
string='Certificate Reference',
|
||||
tracking=True,
|
||||
)
|
||||
score = fields.Float(
|
||||
string='Score',
|
||||
)
|
||||
state = fields.Selection(
|
||||
[
|
||||
('current', 'Current'),
|
||||
('expiring_soon', 'Expiring Soon'),
|
||||
('expired', 'Expired'),
|
||||
],
|
||||
string='Status',
|
||||
compute='_compute_state',
|
||||
store=True,
|
||||
)
|
||||
attachment_id = fields.Many2one(
|
||||
'ir.attachment',
|
||||
string='Certificate',
|
||||
)
|
||||
notes = fields.Html(
|
||||
string='Notes',
|
||||
)
|
||||
display_name = fields.Char(
|
||||
compute='_compute_display_name',
|
||||
store=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
related='employee_id.company_id',
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
# ==========================================================================
|
||||
# Computes
|
||||
# ==========================================================================
|
||||
@api.depends('completion_date', 'training_type_id', 'training_type_id.validity_months')
|
||||
def _compute_expiry_date(self):
|
||||
for rec in self:
|
||||
if rec.completion_date and rec.training_type_id and rec.training_type_id.validity_months:
|
||||
rec.expiry_date = rec.completion_date + relativedelta(months=rec.training_type_id.validity_months)
|
||||
else:
|
||||
rec.expiry_date = False
|
||||
|
||||
@api.depends('expiry_date')
|
||||
def _compute_state(self):
|
||||
today = fields.Date.context_today(self)
|
||||
warn_window = today + relativedelta(months=2)
|
||||
for rec in self:
|
||||
if not rec.expiry_date:
|
||||
rec.state = 'current'
|
||||
elif rec.expiry_date < today:
|
||||
rec.state = 'expired'
|
||||
elif rec.expiry_date <= warn_window:
|
||||
rec.state = 'expiring_soon'
|
||||
else:
|
||||
rec.state = 'current'
|
||||
|
||||
@api.depends('employee_id', 'training_type_id', 'completion_date')
|
||||
def _compute_display_name(self):
|
||||
for rec in self:
|
||||
parts = []
|
||||
if rec.employee_id:
|
||||
parts.append(rec.employee_id.name)
|
||||
if rec.training_type_id:
|
||||
parts.append(rec.training_type_id.name)
|
||||
if rec.completion_date:
|
||||
parts.append(fields.Date.to_string(rec.completion_date))
|
||||
rec.display_name = ' — '.join(parts) if parts else 'Training Record'
|
||||
@@ -0,0 +1,61 @@
|
||||
# -*- 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 FpTrainingType(models.Model):
|
||||
"""Master catalogue of training courses required in the shop.
|
||||
|
||||
A training type defines a class of certification — WHMIS 2015, TDG Road,
|
||||
Standard First Aid / CPR, LOTO, Confined Space — together with how long
|
||||
a completion remains valid before retraining is required. The validity
|
||||
window drives the per-employee training record's automatic expiry.
|
||||
|
||||
Seed data ships a generic Canadian set; jurisdiction-specific compliance
|
||||
packs may add more.
|
||||
"""
|
||||
_name = 'fusion.plating.training.type'
|
||||
_description = 'Fusion Plating — Training Type'
|
||||
_order = 'category, name'
|
||||
|
||||
name = fields.Char(
|
||||
string='Training Type',
|
||||
required=True,
|
||||
)
|
||||
code = fields.Char(
|
||||
string='Code',
|
||||
)
|
||||
category = fields.Selection(
|
||||
[
|
||||
('whmis', 'WHMIS'),
|
||||
('tdg', 'TDG'),
|
||||
('first_aid', 'First Aid'),
|
||||
('loto', 'Lockout / Tagout'),
|
||||
('confined_space', 'Confined Space'),
|
||||
('process_specific', 'Process Specific'),
|
||||
('ppe', 'PPE'),
|
||||
('ergonomic', 'Ergonomic'),
|
||||
('other', 'Other'),
|
||||
],
|
||||
string='Category',
|
||||
default='other',
|
||||
required=True,
|
||||
)
|
||||
validity_months = fields.Integer(
|
||||
string='Validity (Months)',
|
||||
default=12,
|
||||
help='How long a completion remains valid before retraining is '
|
||||
'required. Set to 0 for one-time training that never expires.',
|
||||
)
|
||||
description = fields.Html(
|
||||
string='Description',
|
||||
)
|
||||
required_for_roles = fields.Char(
|
||||
string='Required For Roles',
|
||||
help='Free-text list of job titles or roles that require this '
|
||||
'training, e.g. "All operators, Supervisors".',
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
75
fusion_plating/fusion_plating_safety/models/hr_employee.py
Normal file
75
fusion_plating/fusion_plating_safety/models/hr_employee.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# -*- 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 HrEmployee(models.Model):
|
||||
"""Adds Fusion Plating safety roll-ups to hr.employee.
|
||||
|
||||
Each employee gets reverse links to their training records, exposure
|
||||
monitoring samples and incident records, plus a single boolean that
|
||||
rolls up to True only when every required training course is current.
|
||||
Field names use the x_fc_ prefix per the Fusion Central convention for
|
||||
extensions to base Odoo models.
|
||||
"""
|
||||
_inherit = 'hr.employee'
|
||||
|
||||
x_fc_training_ids = fields.One2many(
|
||||
'fusion.plating.training.record',
|
||||
'employee_id',
|
||||
string='Training Records',
|
||||
)
|
||||
x_fc_training_current = fields.Boolean(
|
||||
string='Training Current',
|
||||
compute='_compute_x_fc_training_current',
|
||||
store=True,
|
||||
help='True when every training record on this employee is currently '
|
||||
'in date (no expired records).',
|
||||
)
|
||||
x_fc_exposure_ids = fields.One2many(
|
||||
'fusion.plating.exposure.monitoring',
|
||||
'employee_id',
|
||||
string='Exposure Samples',
|
||||
)
|
||||
x_fc_incident_ids = fields.One2many(
|
||||
'fusion.plating.incident',
|
||||
'employee_id',
|
||||
string='Incidents',
|
||||
)
|
||||
x_fc_ppe_ids = fields.One2many(
|
||||
'fusion.plating.ppe.issuance',
|
||||
'employee_id',
|
||||
string='PPE Issued',
|
||||
)
|
||||
x_fc_training_count = fields.Integer(
|
||||
compute='_compute_x_fc_safety_counts',
|
||||
)
|
||||
x_fc_exposure_count = fields.Integer(
|
||||
compute='_compute_x_fc_safety_counts',
|
||||
)
|
||||
x_fc_incident_count = fields.Integer(
|
||||
compute='_compute_x_fc_safety_counts',
|
||||
)
|
||||
x_fc_ppe_count = fields.Integer(
|
||||
compute='_compute_x_fc_safety_counts',
|
||||
)
|
||||
|
||||
@api.depends('x_fc_training_ids', 'x_fc_training_ids.state')
|
||||
def _compute_x_fc_training_current(self):
|
||||
for rec in self:
|
||||
if not rec.x_fc_training_ids:
|
||||
rec.x_fc_training_current = True
|
||||
else:
|
||||
rec.x_fc_training_current = not any(
|
||||
t.state == 'expired' for t in rec.x_fc_training_ids
|
||||
)
|
||||
|
||||
def _compute_x_fc_safety_counts(self):
|
||||
for rec in self:
|
||||
rec.x_fc_training_count = len(rec.x_fc_training_ids)
|
||||
rec.x_fc_exposure_count = len(rec.x_fc_exposure_ids)
|
||||
rec.x_fc_incident_count = len(rec.x_fc_incident_ids)
|
||||
rec.x_fc_ppe_count = len(rec.x_fc_ppe_ids)
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- Multi-company isolation rules -->
|
||||
<!-- All safety records are scoped per-company so a multi-company -->
|
||||
<!-- shop sees only its own data. -->
|
||||
<!-- ================================================================== -->
|
||||
<record id="fp_safety_sds_company_rule" model="ir.rule">
|
||||
<field name="name">Fusion Plating Safety: SDS — multi-company</field>
|
||||
<field name="model_id" ref="model_fusion_plating_sds"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
|
||||
</record>
|
||||
|
||||
<record id="fp_safety_chemical_company_rule" model="ir.rule">
|
||||
<field name="name">Fusion Plating Safety: Chemical — multi-company</field>
|
||||
<field name="model_id" ref="model_fusion_plating_chemical"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
|
||||
</record>
|
||||
|
||||
<record id="fp_safety_training_record_company_rule" model="ir.rule">
|
||||
<field name="name">Fusion Plating Safety: Training Record — multi-company</field>
|
||||
<field name="model_id" ref="model_fusion_plating_training_record"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
|
||||
</record>
|
||||
|
||||
<record id="fp_safety_exposure_company_rule" model="ir.rule">
|
||||
<field name="name">Fusion Plating Safety: Exposure — multi-company</field>
|
||||
<field name="model_id" ref="model_fusion_plating_exposure_monitoring"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
|
||||
</record>
|
||||
|
||||
<record id="fp_safety_jhsc_company_rule" model="ir.rule">
|
||||
<field name="name">Fusion Plating Safety: JHSC — multi-company</field>
|
||||
<field name="model_id" ref="model_fusion_plating_jhsc"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
|
||||
</record>
|
||||
|
||||
<record id="fp_safety_jhsc_meeting_company_rule" model="ir.rule">
|
||||
<field name="name">Fusion Plating Safety: JHSC Meeting — multi-company</field>
|
||||
<field name="model_id" ref="model_fusion_plating_jhsc_meeting"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
|
||||
</record>
|
||||
|
||||
<record id="fp_safety_incident_company_rule" model="ir.rule">
|
||||
<field name="name">Fusion Plating Safety: Incident — multi-company</field>
|
||||
<field name="model_id" ref="model_fusion_plating_incident"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
|
||||
</record>
|
||||
|
||||
<record id="fp_safety_ppe_company_rule" model="ir.rule">
|
||||
<field name="name">Fusion Plating Safety: PPE — multi-company</field>
|
||||
<field name="model_id" ref="model_fusion_plating_ppe_issuance"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,28 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_fp_sds_operator,fp.sds.operator,model_fusion_plating_sds,fusion_plating.group_fusion_plating_operator,1,0,0,0
|
||||
access_fp_sds_supervisor,fp.sds.supervisor,model_fusion_plating_sds,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
|
||||
access_fp_sds_manager,fp.sds.manager,model_fusion_plating_sds,fusion_plating.group_fusion_plating_manager,1,1,1,1
|
||||
access_fp_chemical_operator,fp.chemical.operator,model_fusion_plating_chemical,fusion_plating.group_fusion_plating_operator,1,0,0,0
|
||||
access_fp_chemical_supervisor,fp.chemical.supervisor,model_fusion_plating_chemical,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
|
||||
access_fp_chemical_manager,fp.chemical.manager,model_fusion_plating_chemical,fusion_plating.group_fusion_plating_manager,1,1,1,1
|
||||
access_fp_training_type_operator,fp.training.type.operator,model_fusion_plating_training_type,fusion_plating.group_fusion_plating_operator,1,0,0,0
|
||||
access_fp_training_type_supervisor,fp.training.type.supervisor,model_fusion_plating_training_type,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
|
||||
access_fp_training_type_manager,fp.training.type.manager,model_fusion_plating_training_type,fusion_plating.group_fusion_plating_manager,1,1,1,1
|
||||
access_fp_training_record_operator,fp.training.record.operator,model_fusion_plating_training_record,fusion_plating.group_fusion_plating_operator,1,0,0,0
|
||||
access_fp_training_record_supervisor,fp.training.record.supervisor,model_fusion_plating_training_record,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
|
||||
access_fp_training_record_manager,fp.training.record.manager,model_fusion_plating_training_record,fusion_plating.group_fusion_plating_manager,1,1,1,1
|
||||
access_fp_exposure_operator,fp.exposure.monitoring.operator,model_fusion_plating_exposure_monitoring,fusion_plating.group_fusion_plating_operator,1,0,0,0
|
||||
access_fp_exposure_supervisor,fp.exposure.monitoring.supervisor,model_fusion_plating_exposure_monitoring,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
|
||||
access_fp_exposure_manager,fp.exposure.monitoring.manager,model_fusion_plating_exposure_monitoring,fusion_plating.group_fusion_plating_manager,1,1,1,1
|
||||
access_fp_jhsc_operator,fp.jhsc.operator,model_fusion_plating_jhsc,fusion_plating.group_fusion_plating_operator,1,0,0,0
|
||||
access_fp_jhsc_supervisor,fp.jhsc.supervisor,model_fusion_plating_jhsc,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
|
||||
access_fp_jhsc_manager,fp.jhsc.manager,model_fusion_plating_jhsc,fusion_plating.group_fusion_plating_manager,1,1,1,1
|
||||
access_fp_jhsc_meeting_operator,fp.jhsc.meeting.operator,model_fusion_plating_jhsc_meeting,fusion_plating.group_fusion_plating_operator,1,0,0,0
|
||||
access_fp_jhsc_meeting_supervisor,fp.jhsc.meeting.supervisor,model_fusion_plating_jhsc_meeting,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
|
||||
access_fp_jhsc_meeting_manager,fp.jhsc.meeting.manager,model_fusion_plating_jhsc_meeting,fusion_plating.group_fusion_plating_manager,1,1,1,1
|
||||
access_fp_incident_operator,fp.incident.operator,model_fusion_plating_incident,fusion_plating.group_fusion_plating_operator,1,1,1,0
|
||||
access_fp_incident_supervisor,fp.incident.supervisor,model_fusion_plating_incident,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
|
||||
access_fp_incident_manager,fp.incident.manager,model_fusion_plating_incident,fusion_plating.group_fusion_plating_manager,1,1,1,1
|
||||
access_fp_ppe_operator,fp.ppe.issuance.operator,model_fusion_plating_ppe_issuance,fusion_plating.group_fusion_plating_operator,1,0,0,0
|
||||
access_fp_ppe_supervisor,fp.ppe.issuance.supervisor,model_fusion_plating_ppe_issuance,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
|
||||
access_fp_ppe_manager,fp.ppe.issuance.manager,model_fusion_plating_ppe_issuance,fusion_plating.group_fusion_plating_manager,1,1,1,1
|
||||
|
@@ -0,0 +1,196 @@
|
||||
// =============================================================================
|
||||
// Fusion Plating — Safety (EHS) backend styles
|
||||
// Copyright 2026 Nexa Systems Inc.
|
||||
// License OPL-1 (Odoo Proprietary License v1.0)
|
||||
//
|
||||
// THEME AWARENESS
|
||||
// ---------------
|
||||
// This file NEVER hardcodes backgrounds or text colours. All surface colours
|
||||
// come from Odoo / Bootstrap CSS custom properties so the component renders
|
||||
// correctly in BOTH light and dark mode without any duplication:
|
||||
//
|
||||
// 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)
|
||||
// primary: var(--o-action) // Odoo action/brand
|
||||
//
|
||||
// Hazard / severity tints use color-mix() against the Bootstrap theme
|
||||
// tokens so a red badge is darker on light mode and brighter on dark mode
|
||||
// automatically — one rule, two looks.
|
||||
//
|
||||
// We never target `.o_dark`, `html.dark`, or `@media (prefers-color-scheme)`
|
||||
// to override colours. If you find yourself needing that, it's a smell —
|
||||
// use a variable instead.
|
||||
// =============================================================================
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Local helpers
|
||||
// -----------------------------------------------------------------------------
|
||||
@mixin fp-tint($color-var, $amount: 12%) {
|
||||
background-color: color-mix(in srgb, var(#{$color-var}) #{$amount}, transparent);
|
||||
color: var(#{$color-var});
|
||||
border: 1px solid color-mix(in srgb, var(#{$color-var}) 35%, transparent);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// SDS kanban — hazard-class tinted card
|
||||
// -----------------------------------------------------------------------------
|
||||
.o_fp_sds_kanban {
|
||||
|
||||
.o_fp_sds_card {
|
||||
// Use a left border tint by hazard class — subtle, theme-aware.
|
||||
border-left-width: 4px;
|
||||
border-left-color: var(--bs-border-color);
|
||||
|
||||
&[data-hazard="flammable"],
|
||||
&[data-hazard="oxidizer"] {
|
||||
border-left-color: var(--bs-danger);
|
||||
background-color: color-mix(in srgb, var(--bs-danger) 4%, var(--o-view-background-color, var(--bs-body-bg)));
|
||||
}
|
||||
&[data-hazard="corrosive"],
|
||||
&[data-hazard="toxic"],
|
||||
&[data-hazard="carcinogen"],
|
||||
&[data-hazard="reproductive_toxin"] {
|
||||
border-left-color: var(--bs-warning);
|
||||
background-color: color-mix(in srgb, var(--bs-warning) 4%, var(--o-view-background-color, var(--bs-body-bg)));
|
||||
}
|
||||
&[data-hazard="compressed_gas"],
|
||||
&[data-hazard="sensitizer"],
|
||||
&[data-hazard="aquatic_toxin"] {
|
||||
border-left-color: var(--bs-info, var(--o-action));
|
||||
}
|
||||
|
||||
// Status overlay — expired wins over hazard tint.
|
||||
&[data-state="expired"] {
|
||||
border-left-color: var(--bs-danger);
|
||||
opacity: 0.85;
|
||||
}
|
||||
&[data-state="expiring_soon"] {
|
||||
border-left-color: var(--bs-warning);
|
||||
}
|
||||
&[data-state="withdrawn"] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.o_fp_badge {
|
||||
padding: 2px 8px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
border-radius: 999px;
|
||||
|
||||
&[data-state="current"] {
|
||||
@include fp-tint(--bs-success);
|
||||
}
|
||||
&[data-state="expiring_soon"] {
|
||||
@include fp-tint(--bs-warning);
|
||||
}
|
||||
&[data-state="expired"] {
|
||||
@include fp-tint(--bs-danger);
|
||||
}
|
||||
&[data-state="withdrawn"] {
|
||||
@include fp-tint(--bs-secondary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Training kanban — expiry indicator on the card
|
||||
// -----------------------------------------------------------------------------
|
||||
.o_fp_training_kanban {
|
||||
|
||||
.o_fp_training_card {
|
||||
border-left-width: 4px;
|
||||
border-left-color: var(--bs-success);
|
||||
|
||||
&[data-state="expiring_soon"] {
|
||||
border-left-color: var(--bs-warning);
|
||||
}
|
||||
&[data-state="expired"] {
|
||||
border-left-color: var(--bs-danger);
|
||||
background-color: color-mix(in srgb, var(--bs-danger) 5%, var(--o-view-background-color, var(--bs-body-bg)));
|
||||
}
|
||||
}
|
||||
|
||||
.o_fp_badge {
|
||||
padding: 2px 8px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
border-radius: 999px;
|
||||
|
||||
&[data-state="current"] {
|
||||
@include fp-tint(--bs-success);
|
||||
}
|
||||
&[data-state="expiring_soon"] {
|
||||
@include fp-tint(--bs-warning);
|
||||
}
|
||||
&[data-state="expired"] {
|
||||
@include fp-tint(--bs-danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Incident kanban — severity tint by type
|
||||
// -----------------------------------------------------------------------------
|
||||
.o_fp_incident_kanban {
|
||||
|
||||
.o_fp_incident_card {
|
||||
border-left-width: 4px;
|
||||
border-left-color: var(--bs-secondary-color);
|
||||
|
||||
// High-severity
|
||||
&[data-type="injury"],
|
||||
&[data-type="lost_time"],
|
||||
&[data-type="medical"] {
|
||||
border-left-color: var(--bs-danger);
|
||||
background-color: color-mix(in srgb, var(--bs-danger) 5%, var(--o-view-background-color, var(--bs-body-bg)));
|
||||
}
|
||||
|
||||
// Mid-severity
|
||||
&[data-type="first_aid"],
|
||||
&[data-type="property_damage"],
|
||||
&[data-type="environmental"] {
|
||||
border-left-color: var(--bs-warning);
|
||||
}
|
||||
|
||||
// Low-severity
|
||||
&[data-type="near_miss"] {
|
||||
border-left-color: var(--bs-info, var(--o-action));
|
||||
}
|
||||
|
||||
// Closed records dim
|
||||
&[data-state="closed"] {
|
||||
opacity: 0.65;
|
||||
}
|
||||
}
|
||||
|
||||
.o_fp_badge {
|
||||
padding: 2px 8px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
border-radius: 999px;
|
||||
|
||||
&[data-state="draft"] {
|
||||
@include fp-tint(--bs-secondary-color);
|
||||
}
|
||||
&[data-state="investigation"] {
|
||||
@include fp-tint(--bs-warning);
|
||||
}
|
||||
&[data-state="closed"] {
|
||||
@include fp-tint(--bs-success);
|
||||
}
|
||||
}
|
||||
}
|
||||
109
fusion_plating/fusion_plating_safety/views/fp_chemical_views.xml
Normal file
109
fusion_plating/fusion_plating_safety/views/fp_chemical_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_chemical_list" model="ir.ui.view">
|
||||
<field name="name">fp.chemical.list</field>
|
||||
<field name="model">fusion.plating.chemical</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Chemicals" decoration-warning="needs_reorder">
|
||||
<field name="name"/>
|
||||
<field name="sds_id"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="location"/>
|
||||
<field name="container_size"/>
|
||||
<field name="container_uom"/>
|
||||
<field name="quantity_on_hand"/>
|
||||
<field name="reorder_point"/>
|
||||
<field name="needs_reorder"/>
|
||||
<field name="active" widget="boolean_toggle"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_chemical_form" model="ir.ui.view">
|
||||
<field name="name">fp.chemical.form</field>
|
||||
<field name="model">fusion.plating.chemical</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Chemical">
|
||||
<sheet>
|
||||
<widget name="web_ribbon" title="Reorder" bg_color="text-bg-warning" invisible="not needs_reorder"/>
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1><field name="name" placeholder="e.g. 50% Sodium Hydroxide"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="sds_id"/>
|
||||
<field name="product_id"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="location" placeholder="Acid Cabinet 2"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="active" widget="boolean_toggle"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="container_size"/>
|
||||
<field name="container_uom" placeholder="L"/>
|
||||
<field name="quantity_on_hand"/>
|
||||
<field name="reorder_point"/>
|
||||
<field name="needs_reorder"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Incompatibilities">
|
||||
<field name="incompatible_with_ids" widget="many2many_tags"/>
|
||||
<p class="text-muted mt-2">
|
||||
Chemicals that must not be stored next to this one
|
||||
(e.g. acids vs bases, oxidizers vs flammables).
|
||||
</p>
|
||||
</page>
|
||||
<page string="Notes">
|
||||
<field name="notes"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_chemical_search" model="ir.ui.view">
|
||||
<field name="name">fp.chemical.search</field>
|
||||
<field name="model">fusion.plating.chemical</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Chemicals">
|
||||
<field name="name"/>
|
||||
<field name="sds_id"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="location"/>
|
||||
<filter string="Needs Reorder" name="filter_reorder" domain="[('needs_reorder', '=', True)]"/>
|
||||
<filter string="Archived" name="filter_inactive" domain="[('active', '=', False)]"/>
|
||||
<group>
|
||||
<filter string="Facility" name="group_facility" context="{'group_by': 'facility_id'}"/>
|
||||
<filter string="Location" name="group_location" context="{'group_by': 'location'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_chemical" model="ir.actions.act_window">
|
||||
<field name="name">Chemicals</field>
|
||||
<field name="res_model">fusion.plating.chemical</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_fp_chemical_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Add a chemical
|
||||
</p>
|
||||
<p>
|
||||
Track the physical chemicals stored on site, who supplies them,
|
||||
where they live, and when they need to be reordered.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -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_exposure_monitoring_list" model="ir.ui.view">
|
||||
<field name="name">fp.exposure.monitoring.list</field>
|
||||
<field name="model">fusion.plating.exposure.monitoring</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Exposure Monitoring" decoration-danger="result == 'exceed'" decoration-warning="result == 'approaching'">
|
||||
<field name="name"/>
|
||||
<field name="sample_date"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="sample_type"/>
|
||||
<field name="substance"/>
|
||||
<field name="concentration"/>
|
||||
<field name="uom"/>
|
||||
<field name="oel_limit"/>
|
||||
<field name="percent_of_oel"/>
|
||||
<field name="result"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_exposure_monitoring_form" model="ir.ui.view">
|
||||
<field name="name">fp.exposure.monitoring.form</field>
|
||||
<field name="model">fusion.plating.exposure.monitoring</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Exposure Sample">
|
||||
<header>
|
||||
<field name="result" widget="statusbar" statusbar_visible="below,approaching,exceed"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1><field name="name"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group string="Sample">
|
||||
<field name="sample_date"/>
|
||||
<field name="sample_type"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<group string="Result">
|
||||
<field name="substance"/>
|
||||
<field name="concentration"/>
|
||||
<field name="uom" placeholder="mg/m3"/>
|
||||
<field name="oel_reference" placeholder="Ontario Reg. 833 TWA"/>
|
||||
<field name="oel_limit"/>
|
||||
<field name="percent_of_oel"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Notes">
|
||||
<field name="notes"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_exposure_monitoring_search" model="ir.ui.view">
|
||||
<field name="name">fp.exposure.monitoring.search</field>
|
||||
<field name="model">fusion.plating.exposure.monitoring</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Exposure Samples">
|
||||
<field name="name"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="substance"/>
|
||||
<filter string="Below" name="filter_below" domain="[('result', '=', 'below')]"/>
|
||||
<filter string="Approaching" name="filter_approaching" domain="[('result', '=', 'approaching')]"/>
|
||||
<filter string="Exceeds" name="filter_exceed" domain="[('result', '=', 'exceed')]"/>
|
||||
<group>
|
||||
<filter string="Sample Type" name="group_sample_type" context="{'group_by': 'sample_type'}"/>
|
||||
<filter string="Substance" name="group_substance" context="{'group_by': 'substance'}"/>
|
||||
<filter string="Employee" name="group_employee" context="{'group_by': 'employee_id'}"/>
|
||||
<filter string="Result" name="group_result" context="{'group_by': 'result'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_exposure_monitoring" model="ir.actions.act_window">
|
||||
<field name="name">Exposure Monitoring</field>
|
||||
<field name="res_model">fusion.plating.exposure.monitoring</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_fp_exposure_monitoring_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Log an exposure sample
|
||||
</p>
|
||||
<p>
|
||||
Capture air, biological, noise and vibration sampling results
|
||||
and compare them automatically against the applicable
|
||||
Occupational Exposure Limit.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
161
fusion_plating/fusion_plating_safety/views/fp_incident_views.xml
Normal file
161
fusion_plating/fusion_plating_safety/views/fp_incident_views.xml
Normal file
@@ -0,0 +1,161 @@
|
||||
<?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_incident_list" model="ir.ui.view">
|
||||
<field name="name">fp.incident.list</field>
|
||||
<field name="model">fusion.plating.incident</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Incident Register" decoration-danger="incident_type in ('injury', 'lost_time', 'medical')" decoration-warning="incident_type == 'first_aid'">
|
||||
<field name="name"/>
|
||||
<field name="incident_date"/>
|
||||
<field name="incident_type"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="location"/>
|
||||
<field name="lost_time_days"/>
|
||||
<field name="wsib_reportable"/>
|
||||
<field name="state"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_incident_form" model="ir.ui.view">
|
||||
<field name="name">fp.incident.form</field>
|
||||
<field name="model">fusion.plating.incident</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Incident">
|
||||
<header>
|
||||
<button name="action_start_investigation" type="object" string="Start Investigation" invisible="state != 'draft'"/>
|
||||
<button name="action_close" type="object" string="Close" invisible="state != 'investigation'"/>
|
||||
<button name="action_reopen" type="object" string="Reopen" invisible="state != 'closed'"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,investigation,closed"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1><field name="name"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group string="Incident">
|
||||
<field name="incident_date"/>
|
||||
<field name="incident_type"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="location"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="reported_by_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<group string="WSIB / Outcome">
|
||||
<field name="wsib_reportable"/>
|
||||
<field name="wsib_form_7_submitted" invisible="not wsib_reportable"/>
|
||||
<field name="lost_time_days"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Description">
|
||||
<field name="description"/>
|
||||
</page>
|
||||
<page string="Immediate Action">
|
||||
<field name="immediate_action"/>
|
||||
</page>
|
||||
<page string="Investigation">
|
||||
<field name="investigation"/>
|
||||
</page>
|
||||
<page string="Root Cause">
|
||||
<field name="root_cause"/>
|
||||
</page>
|
||||
<page string="Corrective Action">
|
||||
<field name="corrective_action"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_incident_kanban" model="ir.ui.view">
|
||||
<field name="name">fp.incident.kanban</field>
|
||||
<field name="model">fusion.plating.incident</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_fp_incident_kanban">
|
||||
<field name="id"/>
|
||||
<field name="name"/>
|
||||
<field name="incident_type"/>
|
||||
<field name="incident_date"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="state"/>
|
||||
<templates>
|
||||
<t t-name="card">
|
||||
<div class="o_fp_card o_fp_incident_card" t-att-data-type="record.incident_type.raw_value" t-att-data-state="record.state.raw_value">
|
||||
<div class="d-flex align-items-start justify-content-between">
|
||||
<div>
|
||||
<strong class="o_fp_card_title"><field name="name"/></strong>
|
||||
<div class="text-muted small"><field name="incident_type"/></div>
|
||||
</div>
|
||||
<i class="fa fa-exclamation-triangle text-muted" aria-hidden="true"/>
|
||||
</div>
|
||||
<div class="mt-2 small">
|
||||
<div><field name="incident_date"/></div>
|
||||
<div><field name="facility_id"/></div>
|
||||
<div><field name="employee_id"/></div>
|
||||
<span class="o_fp_badge mt-2 d-inline-block" t-att-data-state="record.state.raw_value">
|
||||
<field name="state"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_incident_search" model="ir.ui.view">
|
||||
<field name="name">fp.incident.search</field>
|
||||
<field name="model">fusion.plating.incident</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Incidents">
|
||||
<field name="name"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="location"/>
|
||||
<filter string="Draft" name="filter_draft" domain="[('state', '=', 'draft')]"/>
|
||||
<filter string="Investigation" name="filter_investigation" domain="[('state', '=', 'investigation')]"/>
|
||||
<filter string="Closed" name="filter_closed" domain="[('state', '=', 'closed')]"/>
|
||||
<separator/>
|
||||
<filter string="WSIB Reportable" name="filter_wsib" domain="[('wsib_reportable', '=', True)]"/>
|
||||
<filter string="Lost-Time" name="filter_lost_time" domain="[('incident_type', '=', 'lost_time')]"/>
|
||||
<filter string="Near Miss" name="filter_near_miss" domain="[('incident_type', '=', 'near_miss')]"/>
|
||||
<group>
|
||||
<filter string="Type" name="group_type" context="{'group_by': 'incident_type'}"/>
|
||||
<filter string="Facility" name="group_facility" context="{'group_by': 'facility_id'}"/>
|
||||
<filter string="Status" name="group_state" context="{'group_by': 'state'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_incident" model="ir.actions.act_window">
|
||||
<field name="name">Incident Register</field>
|
||||
<field name="res_model">fusion.plating.incident</field>
|
||||
<field name="view_mode">kanban,list,form</field>
|
||||
<field name="search_view_id" ref="view_fp_incident_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Log an incident, near-miss or first-aid event
|
||||
</p>
|
||||
<p>
|
||||
Capture every safety event — even close calls — and walk it
|
||||
through investigation to closure with a documented root
|
||||
cause and corrective action.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
186
fusion_plating/fusion_plating_safety/views/fp_jhsc_views.xml
Normal file
186
fusion_plating/fusion_plating_safety/views/fp_jhsc_views.xml
Normal file
@@ -0,0 +1,186 @@
|
||||
<?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>
|
||||
|
||||
<!-- ===================================================================== -->
|
||||
<!-- JHSC -->
|
||||
<!-- ===================================================================== -->
|
||||
<record id="view_fp_jhsc_list" model="ir.ui.view">
|
||||
<field name="name">fp.jhsc.list</field>
|
||||
<field name="model">fusion.plating.jhsc</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="JHSC">
|
||||
<field name="name"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="member_count"/>
|
||||
<field name="meeting_count"/>
|
||||
<field name="active" widget="boolean_toggle"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_jhsc_form" model="ir.ui.view">
|
||||
<field name="name">fp.jhsc.form</field>
|
||||
<field name="model">fusion.plating.jhsc</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="JHSC">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1><field name="name" placeholder="e.g. Site A JHSC"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="facility_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="active" widget="boolean_toggle"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="member_count"/>
|
||||
<field name="meeting_count"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Members">
|
||||
<field name="member_ids" widget="many2many_tags"/>
|
||||
</page>
|
||||
<page string="Worker Representatives">
|
||||
<field name="worker_rep_ids" widget="many2many_tags"/>
|
||||
</page>
|
||||
<page string="Management Representatives">
|
||||
<field name="mgmt_rep_ids" widget="many2many_tags"/>
|
||||
</page>
|
||||
<page string="Meetings">
|
||||
<field name="meeting_ids">
|
||||
<list>
|
||||
<field name="meeting_date"/>
|
||||
<field name="name"/>
|
||||
<field name="state"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_jhsc_search" model="ir.ui.view">
|
||||
<field name="name">fp.jhsc.search</field>
|
||||
<field name="model">fusion.plating.jhsc</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="JHSC">
|
||||
<field name="name"/>
|
||||
<field name="facility_id"/>
|
||||
<filter string="Archived" name="filter_inactive" domain="[('active', '=', False)]"/>
|
||||
<group>
|
||||
<filter string="Facility" name="group_facility" context="{'group_by': 'facility_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_jhsc" model="ir.actions.act_window">
|
||||
<field name="name">JHSC</field>
|
||||
<field name="res_model">fusion.plating.jhsc</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_fp_jhsc_search"/>
|
||||
</record>
|
||||
|
||||
<!-- ===================================================================== -->
|
||||
<!-- JHSC Meetings -->
|
||||
<!-- ===================================================================== -->
|
||||
<record id="view_fp_jhsc_meeting_list" model="ir.ui.view">
|
||||
<field name="name">fp.jhsc.meeting.list</field>
|
||||
<field name="model">fusion.plating.jhsc.meeting</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="JHSC Meetings">
|
||||
<field name="meeting_date"/>
|
||||
<field name="name"/>
|
||||
<field name="jhsc_id"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="state"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_jhsc_meeting_form" model="ir.ui.view">
|
||||
<field name="name">fp.jhsc.meeting.form</field>
|
||||
<field name="model">fusion.plating.jhsc.meeting</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="JHSC Meeting">
|
||||
<header>
|
||||
<button name="action_mark_held" type="object" string="Mark Held" invisible="state != 'planned'"/>
|
||||
<button name="action_post_minutes" type="object" string="Post Minutes" invisible="state not in ('held',)"/>
|
||||
<button name="action_close" type="object" string="Close" invisible="state not in ('minutes_ready',)"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="planned,held,minutes_ready,closed"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1><field name="name" placeholder="e.g. Q1 2026 JHSC Meeting"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="jhsc_id"/>
|
||||
<field name="meeting_date"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Attendees">
|
||||
<field name="attendee_ids" widget="many2many_tags"/>
|
||||
</page>
|
||||
<page string="Agenda">
|
||||
<field name="agenda"/>
|
||||
</page>
|
||||
<page string="Minutes">
|
||||
<field name="minutes"/>
|
||||
</page>
|
||||
<page string="Action Items">
|
||||
<field name="action_items"/>
|
||||
</page>
|
||||
<page string="Attachments">
|
||||
<field name="attachment_ids" widget="many2many_binary"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_jhsc_meeting_search" model="ir.ui.view">
|
||||
<field name="name">fp.jhsc.meeting.search</field>
|
||||
<field name="model">fusion.plating.jhsc.meeting</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="JHSC Meetings">
|
||||
<field name="name"/>
|
||||
<field name="jhsc_id"/>
|
||||
<field name="facility_id"/>
|
||||
<filter string="Planned" name="filter_planned" domain="[('state', '=', 'planned')]"/>
|
||||
<filter string="Held" name="filter_held" domain="[('state', '=', 'held')]"/>
|
||||
<filter string="Minutes Ready" name="filter_minutes" domain="[('state', '=', 'minutes_ready')]"/>
|
||||
<filter string="Closed" name="filter_closed" domain="[('state', '=', 'closed')]"/>
|
||||
<group>
|
||||
<filter string="Committee" name="group_jhsc" context="{'group_by': 'jhsc_id'}"/>
|
||||
<filter string="Status" name="group_state" context="{'group_by': 'state'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_jhsc_meeting" model="ir.actions.act_window">
|
||||
<field name="name">JHSC Meetings</field>
|
||||
<field name="res_model">fusion.plating.jhsc.meeting</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_fp_jhsc_meeting_search"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
71
fusion_plating/fusion_plating_safety/views/fp_menu.xml
Normal file
71
fusion_plating/fusion_plating_safety/views/fp_menu.xml
Normal file
@@ -0,0 +1,71 @@
|
||||
<?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>
|
||||
|
||||
<!-- ===== SAFETY (top-level under Plating) ===== -->
|
||||
<menuitem id="menu_fp_safety_root"
|
||||
name="Safety"
|
||||
parent="fusion_plating.menu_fp_root"
|
||||
sequence="45"
|
||||
groups="fusion_plating.group_fusion_plating_operator"/>
|
||||
|
||||
<menuitem id="menu_fp_safety_sds"
|
||||
name="SDS Library"
|
||||
parent="menu_fp_safety_root"
|
||||
action="action_fp_sds"
|
||||
sequence="10"/>
|
||||
|
||||
<menuitem id="menu_fp_safety_training"
|
||||
name="Training Records"
|
||||
parent="menu_fp_safety_root"
|
||||
action="action_fp_training_record"
|
||||
sequence="20"/>
|
||||
|
||||
<menuitem id="menu_fp_safety_exposure"
|
||||
name="Exposure Monitoring"
|
||||
parent="menu_fp_safety_root"
|
||||
action="action_fp_exposure_monitoring"
|
||||
sequence="30"/>
|
||||
|
||||
<menuitem id="menu_fp_safety_jhsc"
|
||||
name="JHSC"
|
||||
parent="menu_fp_safety_root"
|
||||
action="action_fp_jhsc"
|
||||
sequence="40"/>
|
||||
|
||||
<menuitem id="menu_fp_safety_jhsc_meetings"
|
||||
name="JHSC Meetings"
|
||||
parent="menu_fp_safety_root"
|
||||
action="action_fp_jhsc_meeting"
|
||||
sequence="45"/>
|
||||
|
||||
<menuitem id="menu_fp_safety_incidents"
|
||||
name="Incident Register"
|
||||
parent="menu_fp_safety_root"
|
||||
action="action_fp_incident"
|
||||
sequence="50"/>
|
||||
|
||||
<menuitem id="menu_fp_safety_ppe"
|
||||
name="PPE Issuance"
|
||||
parent="menu_fp_safety_root"
|
||||
action="action_fp_ppe_issuance"
|
||||
sequence="60"/>
|
||||
|
||||
<!-- ===== Configuration (under existing Plating > Configuration) ===== -->
|
||||
<menuitem id="menu_fp_safety_training_types"
|
||||
name="Training Types"
|
||||
parent="fusion_plating.menu_fp_config"
|
||||
action="action_fp_training_type"
|
||||
sequence="60"/>
|
||||
|
||||
<menuitem id="menu_fp_safety_chemicals"
|
||||
name="Chemicals"
|
||||
parent="fusion_plating.menu_fp_config"
|
||||
action="action_fp_chemical"
|
||||
sequence="70"/>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,75 @@
|
||||
<?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_ppe_issuance_list" model="ir.ui.view">
|
||||
<field name="name">fp.ppe.issuance.list</field>
|
||||
<field name="model">fusion.plating.ppe.issuance</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="PPE Issuance">
|
||||
<field name="employee_id"/>
|
||||
<field name="ppe_type"/>
|
||||
<field name="size"/>
|
||||
<field name="quantity"/>
|
||||
<field name="issue_date"/>
|
||||
<field name="next_replacement"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_ppe_issuance_form" model="ir.ui.view">
|
||||
<field name="name">fp.ppe.issuance.form</field>
|
||||
<field name="model">fusion.plating.ppe.issuance</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="PPE Issuance">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="employee_id"/>
|
||||
<field name="ppe_type"/>
|
||||
<field name="size"/>
|
||||
<field name="quantity"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="issue_date"/>
|
||||
<field name="next_replacement"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Notes">
|
||||
<field name="notes"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_ppe_issuance_search" model="ir.ui.view">
|
||||
<field name="name">fp.ppe.issuance.search</field>
|
||||
<field name="model">fusion.plating.ppe.issuance</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="PPE Issuance">
|
||||
<field name="employee_id"/>
|
||||
<field name="ppe_type"/>
|
||||
<group>
|
||||
<filter string="Employee" name="group_employee" context="{'group_by': 'employee_id'}"/>
|
||||
<filter string="PPE Type" name="group_ppe_type" context="{'group_by': 'ppe_type'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_ppe_issuance" model="ir.actions.act_window">
|
||||
<field name="name">PPE Issuance</field>
|
||||
<field name="res_model">fusion.plating.ppe.issuance</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_fp_ppe_issuance_search"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
165
fusion_plating/fusion_plating_safety/views/fp_sds_views.xml
Normal file
165
fusion_plating/fusion_plating_safety/views/fp_sds_views.xml
Normal file
@@ -0,0 +1,165 @@
|
||||
<?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_sds_list" model="ir.ui.view">
|
||||
<field name="name">fp.sds.list</field>
|
||||
<field name="model">fusion.plating.sds</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Safety Data Sheets" decoration-danger="state == 'expired'" decoration-warning="state == 'expiring_soon'" decoration-muted="state == 'withdrawn'">
|
||||
<field name="name"/>
|
||||
<field name="product_name"/>
|
||||
<field name="supplier_name"/>
|
||||
<field name="cas_number"/>
|
||||
<field name="version"/>
|
||||
<field name="issue_date"/>
|
||||
<field name="expiry_date"/>
|
||||
<field name="hazard_class"/>
|
||||
<field name="language"/>
|
||||
<field name="state"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_sds_form" model="ir.ui.view">
|
||||
<field name="name">fp.sds.form</field>
|
||||
<field name="model">fusion.plating.sds</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Safety Data Sheet">
|
||||
<header>
|
||||
<button name="action_mark_withdrawn" type="object" string="Mark Withdrawn" invisible="withdrawn"/>
|
||||
<button name="action_mark_active" type="object" string="Mark Active" invisible="not withdrawn"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="current,expiring_soon,expired,withdrawn"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<widget name="web_ribbon" title="Withdrawn" bg_color="text-bg-secondary" invisible="not withdrawn"/>
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1><field name="name" placeholder="e.g. Acme Bright Acid Copper"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group string="Product">
|
||||
<field name="product_name"/>
|
||||
<field name="cas_number"/>
|
||||
<field name="hazard_class"/>
|
||||
<field name="ghs_pictograms" placeholder="GHS01,GHS05,GHS07"/>
|
||||
<field name="language"/>
|
||||
</group>
|
||||
<group string="Supplier & Revision">
|
||||
<field name="supplier_id"/>
|
||||
<field name="supplier_name"/>
|
||||
<field name="version"/>
|
||||
<field name="issue_date"/>
|
||||
<field name="expiry_date"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="active" widget="boolean_toggle"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Document">
|
||||
<group>
|
||||
<field name="attachment_id"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Notes">
|
||||
<field name="notes"/>
|
||||
</page>
|
||||
<page string="Linked Chemicals">
|
||||
<field name="chemical_ids">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="location"/>
|
||||
<field name="quantity_on_hand"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_sds_kanban" model="ir.ui.view">
|
||||
<field name="name">fp.sds.kanban</field>
|
||||
<field name="model">fusion.plating.sds</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_fp_sds_kanban">
|
||||
<field name="id"/>
|
||||
<field name="name"/>
|
||||
<field name="product_name"/>
|
||||
<field name="supplier_name"/>
|
||||
<field name="hazard_class"/>
|
||||
<field name="state"/>
|
||||
<field name="expiry_date"/>
|
||||
<templates>
|
||||
<t t-name="card">
|
||||
<div class="o_fp_card o_fp_sds_card" t-att-data-hazard="record.hazard_class.raw_value" t-att-data-state="record.state.raw_value">
|
||||
<div class="d-flex align-items-start justify-content-between">
|
||||
<div>
|
||||
<strong class="o_fp_card_title"><field name="name"/></strong>
|
||||
<div class="text-muted small"><field name="product_name"/></div>
|
||||
</div>
|
||||
<i class="fa fa-flask text-muted" aria-hidden="true"/>
|
||||
</div>
|
||||
<div class="mt-2 small">
|
||||
<div><field name="supplier_name"/></div>
|
||||
<div class="text-muted">Expires: <field name="expiry_date"/></div>
|
||||
<span class="o_fp_badge mt-2 d-inline-block" t-att-data-state="record.state.raw_value">
|
||||
<field name="state"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_sds_search" model="ir.ui.view">
|
||||
<field name="name">fp.sds.search</field>
|
||||
<field name="model">fusion.plating.sds</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Safety Data Sheets">
|
||||
<field name="name"/>
|
||||
<field name="product_name"/>
|
||||
<field name="supplier_name"/>
|
||||
<field name="cas_number"/>
|
||||
<filter string="Current" name="filter_current" domain="[('state', '=', 'current')]"/>
|
||||
<filter string="Expiring Soon" name="filter_expiring" domain="[('state', '=', 'expiring_soon')]"/>
|
||||
<filter string="Expired" name="filter_expired" domain="[('state', '=', 'expired')]"/>
|
||||
<filter string="Withdrawn" name="filter_withdrawn" domain="[('state', '=', 'withdrawn')]"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="filter_inactive" domain="[('active', '=', False)]"/>
|
||||
<group>
|
||||
<filter string="Hazard Class" name="group_hazard" context="{'group_by': 'hazard_class'}"/>
|
||||
<filter string="Status" name="group_state" context="{'group_by': 'state'}"/>
|
||||
<filter string="Supplier" name="group_supplier" context="{'group_by': 'supplier_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_sds" model="ir.actions.act_window">
|
||||
<field name="name">SDS Library</field>
|
||||
<field name="res_model">fusion.plating.sds</field>
|
||||
<field name="view_mode">kanban,list,form</field>
|
||||
<field name="search_view_id" ref="view_fp_sds_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Add a Safety Data Sheet
|
||||
</p>
|
||||
<p>
|
||||
Maintain a current SDS for every hazardous product on site.
|
||||
Under WHMIS / GHS each SDS is valid for three years from
|
||||
its issue date — refresh from the supplier before expiry.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,131 @@
|
||||
<?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_training_record_list" model="ir.ui.view">
|
||||
<field name="name">fp.training.record.list</field>
|
||||
<field name="model">fusion.plating.training.record</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Training Records" decoration-danger="state == 'expired'" decoration-warning="state == 'expiring_soon'">
|
||||
<field name="employee_id"/>
|
||||
<field name="training_type_id"/>
|
||||
<field name="completion_date"/>
|
||||
<field name="expiry_date"/>
|
||||
<field name="trainer"/>
|
||||
<field name="certificate_ref"/>
|
||||
<field name="state"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_training_record_form" model="ir.ui.view">
|
||||
<field name="name">fp.training.record.form</field>
|
||||
<field name="model">fusion.plating.training.record</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Training Record">
|
||||
<header>
|
||||
<field name="state" widget="statusbar" statusbar_visible="current,expiring_soon,expired"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="employee_id"/>
|
||||
<field name="training_type_id"/>
|
||||
<field name="completion_date"/>
|
||||
<field name="expiry_date"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="trainer"/>
|
||||
<field name="certificate_ref"/>
|
||||
<field name="score"/>
|
||||
<field name="attachment_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Notes">
|
||||
<field name="notes"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_training_record_kanban" model="ir.ui.view">
|
||||
<field name="name">fp.training.record.kanban</field>
|
||||
<field name="model">fusion.plating.training.record</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_fp_training_kanban">
|
||||
<field name="id"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="training_type_id"/>
|
||||
<field name="completion_date"/>
|
||||
<field name="expiry_date"/>
|
||||
<field name="state"/>
|
||||
<templates>
|
||||
<t t-name="card">
|
||||
<div class="o_fp_card o_fp_training_card" t-att-data-state="record.state.raw_value">
|
||||
<div class="d-flex align-items-start justify-content-between">
|
||||
<div>
|
||||
<strong class="o_fp_card_title"><field name="employee_id"/></strong>
|
||||
<div class="text-muted small"><field name="training_type_id"/></div>
|
||||
</div>
|
||||
<i class="fa fa-graduation-cap text-muted" aria-hidden="true"/>
|
||||
</div>
|
||||
<div class="mt-2 small">
|
||||
<div>Completed: <field name="completion_date"/></div>
|
||||
<div>Expires: <field name="expiry_date"/></div>
|
||||
<span class="o_fp_badge mt-2 d-inline-block" t-att-data-state="record.state.raw_value">
|
||||
<field name="state"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_training_record_search" model="ir.ui.view">
|
||||
<field name="name">fp.training.record.search</field>
|
||||
<field name="model">fusion.plating.training.record</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Training Records">
|
||||
<field name="employee_id"/>
|
||||
<field name="training_type_id"/>
|
||||
<field name="certificate_ref"/>
|
||||
<filter string="Current" name="filter_current" domain="[('state', '=', 'current')]"/>
|
||||
<filter string="Expiring Soon" name="filter_expiring" domain="[('state', '=', 'expiring_soon')]"/>
|
||||
<filter string="Expired" name="filter_expired" domain="[('state', '=', 'expired')]"/>
|
||||
<group>
|
||||
<filter string="Employee" name="group_employee" context="{'group_by': 'employee_id'}"/>
|
||||
<filter string="Training Type" name="group_type" context="{'group_by': 'training_type_id'}"/>
|
||||
<filter string="Status" name="group_state" context="{'group_by': 'state'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_training_record" model="ir.actions.act_window">
|
||||
<field name="name">Training Records</field>
|
||||
<field name="res_model">fusion.plating.training.record</field>
|
||||
<field name="view_mode">kanban,list,form</field>
|
||||
<field name="search_view_id" ref="view_fp_training_record_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Log a training completion
|
||||
</p>
|
||||
<p>
|
||||
Build the shop training matrix one completion at a time.
|
||||
Records expire automatically based on the training type's
|
||||
validity window.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,77 @@
|
||||
<?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_training_type_list" model="ir.ui.view">
|
||||
<field name="name">fp.training.type.list</field>
|
||||
<field name="model">fusion.plating.training.type</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Training Types">
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="category"/>
|
||||
<field name="validity_months"/>
|
||||
<field name="required_for_roles"/>
|
||||
<field name="active" widget="boolean_toggle"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_training_type_form" model="ir.ui.view">
|
||||
<field name="name">fp.training.type.form</field>
|
||||
<field name="model">fusion.plating.training.type</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Training Type">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1><field name="name" placeholder="e.g. WHMIS 2015"/></h1>
|
||||
<div class="text-muted"><field name="code" placeholder="WHMIS"/></div>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="category"/>
|
||||
<field name="validity_months"/>
|
||||
<field name="active" widget="boolean_toggle"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="required_for_roles"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Description">
|
||||
<field name="description"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_training_type_search" model="ir.ui.view">
|
||||
<field name="name">fp.training.type.search</field>
|
||||
<field name="model">fusion.plating.training.type</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Training Types">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<filter string="Archived" name="filter_inactive" domain="[('active', '=', False)]"/>
|
||||
<group>
|
||||
<filter string="Category" name="group_category" context="{'group_by': 'category'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_training_type" model="ir.actions.act_window">
|
||||
<field name="name">Training Types</field>
|
||||
<field name="res_model">fusion.plating.training.type</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_fp_training_type_search"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,82 @@
|
||||
<?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_hr_employee_form_inherit_fp_safety" model="ir.ui.view">
|
||||
<field name="name">hr.employee.form.inherit.fp.safety</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="Safety" name="fp_safety">
|
||||
<group>
|
||||
<group>
|
||||
<field name="x_fc_training_current"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="x_fc_training_count"/>
|
||||
<field name="x_fc_exposure_count"/>
|
||||
<field name="x_fc_incident_count"/>
|
||||
<field name="x_fc_ppe_count"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Training">
|
||||
<field name="x_fc_training_ids">
|
||||
<list>
|
||||
<field name="training_type_id"/>
|
||||
<field name="completion_date"/>
|
||||
<field name="expiry_date"/>
|
||||
<field name="trainer"/>
|
||||
<field name="certificate_ref"/>
|
||||
<field name="state"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Exposure">
|
||||
<field name="x_fc_exposure_ids">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="sample_date"/>
|
||||
<field name="sample_type"/>
|
||||
<field name="substance"/>
|
||||
<field name="concentration"/>
|
||||
<field name="oel_limit"/>
|
||||
<field name="percent_of_oel"/>
|
||||
<field name="result"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Incidents">
|
||||
<field name="x_fc_incident_ids">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="incident_date"/>
|
||||
<field name="incident_type"/>
|
||||
<field name="facility_id"/>
|
||||
<field name="state"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="PPE Issued">
|
||||
<field name="x_fc_ppe_ids">
|
||||
<list>
|
||||
<field name="issue_date"/>
|
||||
<field name="ppe_type"/>
|
||||
<field name="size"/>
|
||||
<field name="quantity"/>
|
||||
<field name="next_replacement"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user