From 729743e2682f4ef311dd1523c0cbde854d10a36d Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Mon, 20 Apr 2026 20:31:02 -0400 Subject: [PATCH] =?UTF-8?q?Revert=20"chore(plating):=20retire=20fusion=5Fp?= =?UTF-8?q?lating=5Fculture=20=E2=80=94=20not=20a=20priority"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 95310c459d6c7c4d34dc7f0a2784e824b14007fe. --- .../fusion_plating_culture/README.md | 53 ++++++ .../fusion_plating_culture/__init__.py | 6 + .../fusion_plating_culture/__manifest__.py | 74 ++++++++ .../data/fp_demo_culture_data.xml | 72 ++++++++ .../data/fp_sequence_data.xml | 18 ++ .../fusion_plating_culture/models/__init__.py | 10 ++ .../fusion_plating_culture/models/fp_value.py | 100 +++++++++++ .../models/fp_value_recognition.py | 120 +++++++++++++ .../models/fp_value_rotation.py | 149 +++++++++++++++++ .../models/fp_value_set.py | 111 ++++++++++++ .../models/hr_employee.py | 47 ++++++ .../security/fp_culture_security.xml | 47 ++++++ .../security/ir.model.access.csv | 13 ++ .../src/scss/fusion_plating_culture.scss | 128 ++++++++++++++ .../fusion_plating_culture/views/fp_menu.xml | 43 +++++ .../views/fp_value_recognition_views.xml | 158 ++++++++++++++++++ .../views/fp_value_rotation_views.xml | 84 ++++++++++ .../views/fp_value_set_views.xml | 106 ++++++++++++ .../views/fp_value_views.xml | 134 +++++++++++++++ .../views/hr_employee_views.xml | 40 +++++ 20 files changed, 1513 insertions(+) create mode 100644 fusion_plating/fusion_plating_culture/README.md create mode 100644 fusion_plating/fusion_plating_culture/__init__.py create mode 100644 fusion_plating/fusion_plating_culture/__manifest__.py create mode 100644 fusion_plating/fusion_plating_culture/data/fp_demo_culture_data.xml create mode 100644 fusion_plating/fusion_plating_culture/data/fp_sequence_data.xml create mode 100644 fusion_plating/fusion_plating_culture/models/__init__.py create mode 100644 fusion_plating/fusion_plating_culture/models/fp_value.py create mode 100644 fusion_plating/fusion_plating_culture/models/fp_value_recognition.py create mode 100644 fusion_plating/fusion_plating_culture/models/fp_value_rotation.py create mode 100644 fusion_plating/fusion_plating_culture/models/fp_value_set.py create mode 100644 fusion_plating/fusion_plating_culture/models/hr_employee.py create mode 100644 fusion_plating/fusion_plating_culture/security/fp_culture_security.xml create mode 100644 fusion_plating/fusion_plating_culture/security/ir.model.access.csv create mode 100644 fusion_plating/fusion_plating_culture/static/src/scss/fusion_plating_culture.scss create mode 100644 fusion_plating/fusion_plating_culture/views/fp_menu.xml create mode 100644 fusion_plating/fusion_plating_culture/views/fp_value_recognition_views.xml create mode 100644 fusion_plating/fusion_plating_culture/views/fp_value_rotation_views.xml create mode 100644 fusion_plating/fusion_plating_culture/views/fp_value_set_views.xml create mode 100644 fusion_plating/fusion_plating_culture/views/fp_value_views.xml create mode 100644 fusion_plating/fusion_plating_culture/views/hr_employee_views.xml diff --git a/fusion_plating/fusion_plating_culture/README.md b/fusion_plating/fusion_plating_culture/README.md new file mode 100644 index 00000000..d2f44e80 --- /dev/null +++ b/fusion_plating/fusion_plating_culture/README.md @@ -0,0 +1,53 @@ +# Fusion Plating — Culture & Values + +Part of the Fusion Plating product family by Nexa Systems Inc. + +A configurable culture / values / fundamentals framework for plating and +metal finishing shops. Each shop loads its own values — this module ships +with no client-specific seed data. + +## What it gives you + +- **Value Sets** — a named collection of a shop's values (e.g. "Our Way", + "The 24 Fundamentals", "Core Behaviours"). A company can have several + sets, but usually one is marked as primary. +- **Values** — individual fundamentals, core behaviours, or beliefs, each + with a short title, long-form description, stories/examples, icon, and + display number. +- **Rotations** — a schedule that surfaces a "Fundamental of the + Day/Week/Month". The shop admin decides whether to enable an ir.cron + (none ships in this module) to advance rotations automatically. +- **Recognitions** — peer shout-outs tied to a specific value. "I saw + Sarah living Value #3 today when she stayed late to finish a customer + order." Recognitions flow into an employee's Culture tab on their HR + record. + +## Installation + +1. Install `fusion_plating` (core) and `hr` first. +2. Install `fusion_plating_culture`. +3. Go to **Plating → Culture → Configuration → Value Sets** and create + your shop's set. +4. Add values to that set. +5. (Optional) Create a rotation schedule if you want a + fundamental-of-the-week rotation. + +## Design notes + +- **Not client-specific.** Nothing ships with Entech, Westin, or any + particular shop's language baked in. Each shop loads its own. +- **Reuses core groups.** No new security groups — operator reads, + supervisor creates recognitions, manager configures value sets. +- **Theme-aware.** SCSS uses Bootstrap CSS variables so kanban tiles and + recognition cards render cleanly in both light and dark mode. +- **hr.employee extension uses `x_fc_` prefix** per the Fusion Central + field-naming convention for base Odoo model extensions. + +## Dependencies + +- `fusion_plating` — core +- `hr` — for employee extension and recognition targeting + +## License + +OPL-1 (Odoo Proprietary License v1.0) — Copyright 2026 Nexa Systems Inc. diff --git a/fusion_plating/fusion_plating_culture/__init__.py b/fusion_plating/fusion_plating_culture/__init__.py new file mode 100644 index 00000000..3c90fa80 --- /dev/null +++ b/fusion_plating/fusion_plating_culture/__init__.py @@ -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 diff --git a/fusion_plating/fusion_plating_culture/__manifest__.py b/fusion_plating/fusion_plating_culture/__manifest__.py new file mode 100644 index 00000000..d0469821 --- /dev/null +++ b/fusion_plating/fusion_plating_culture/__manifest__.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) +# Part of the Fusion Plating product family. + +{ + 'name': 'Fusion Plating — Culture & Values', + 'version': '19.0.1.0.0', + 'category': 'Manufacturing/Plating', + 'summary': 'Configurable culture framework for plating shops: values, fundamentals, peer recognitions, rotation schedules. Each shop loads its own values.', + 'description': """ +Fusion Plating — Culture & Values +================================= + +Part of the Fusion Plating product family by Nexa Systems Inc. + +A configurable culture / values / fundamentals framework for plating and +metal finishing shops. Every shop has a different way of talking about how +they expect their people to behave — this module gives each shop a place to +capture that language and put it to work in the daily flow of the plant. + +The module is intentionally generic. It ships with NO client-specific data. +Each shop loads its own value set: one shop may adopt the Entech "24 +Fundamentals", another may use a 10-value "Our Way" set, and another may +skip the module entirely. + +Concepts +-------- +* Value Set — a named collection of values owned by a company +* Value — an individual fundamental, behaviour, or belief +* Rotation — a schedule that surfaces a value-of-the-day/week/month +* Recognition — a peer shout-out tied to a specific value + +Extensions +---------- +* hr.employee gains a recognition count and a Culture tab showing the + employee's recognition history. + +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', + ], + 'data': [ + 'security/fp_culture_security.xml', + 'security/ir.model.access.csv', + 'data/fp_sequence_data.xml', + 'views/fp_value_set_views.xml', + 'views/fp_value_views.xml', + 'views/fp_value_rotation_views.xml', + 'views/fp_value_recognition_views.xml', + 'views/hr_employee_views.xml', + 'views/fp_menu.xml', + ], + 'demo': [ + 'data/fp_demo_culture_data.xml', + ], + 'assets': { + 'web.assets_backend': [ + 'fusion_plating_culture/static/src/scss/fusion_plating_culture.scss', + ], + }, + 'installable': True, + 'application': False, + 'auto_install': False, +} diff --git a/fusion_plating/fusion_plating_culture/data/fp_demo_culture_data.xml b/fusion_plating/fusion_plating_culture/data/fp_demo_culture_data.xml new file mode 100644 index 00000000..6060afd1 --- /dev/null +++ b/fusion_plating/fusion_plating_culture/data/fp_demo_culture_data.xml @@ -0,0 +1,72 @@ + + + + + + + Core Values + CORE + +

The five core values that guide how we work, treat each other, and serve our customers every day on the shop floor.

+
+ + + + Safety First + 1 + 10 + + fa-shield + 1 +

Nothing we do is worth getting hurt over. Every person goes home safe, every day. We stop work when something is unsafe and speak up without hesitation.

+

A plater notices a leaking hose on the chrome line and immediately tags it out, even though it means pausing production for the shift.

+
+ + + Quality Excellence + 2 + 20 + + fa-star + 2 +

We take pride in doing it right the first time. Our coatings protect critical parts — there is no room for "good enough." We own the spec, own the process, and own the result.

+

An operator catches a thickness reading at the low end of spec and voluntarily re-plates the part rather than shipping borderline work.

+
+ + + Teamwork + 3 + 30 + + fa-users + 3 +

We win together or not at all. No one succeeds alone on a plating line — every station depends on the one before it. We help each other, share knowledge, and celebrate wins as a team.

+

The prep team stays late to help the EN line clear a rush order, even though it was not their line's work.

+
+ + + Continuous Improvement + 4 + 40 + + fa-line-chart + 4 +

Good enough today is not good enough tomorrow. We look for small, practical improvements every day — in our processes, our tools, and ourselves.

+

A shift lead redesigns the rack loading sequence and cuts cycle time by 12% without any capital spend.

+
+ + + Customer Focus + 5 + 50 + + fa-handshake-o + 5 +

Our customers trust us with their most critical parts. We honour that trust by delivering on time, communicating proactively, and treating every job like it matters — because it does.

+

The shipping coordinator calls a customer two days before the due date to confirm the delivery window, preventing a costly production delay on their end.

+
+ +
diff --git a/fusion_plating/fusion_plating_culture/data/fp_sequence_data.xml b/fusion_plating/fusion_plating_culture/data/fp_sequence_data.xml new file mode 100644 index 00000000..00b95d9f --- /dev/null +++ b/fusion_plating/fusion_plating_culture/data/fp_sequence_data.xml @@ -0,0 +1,18 @@ + + + + + + Fusion Plating: Recognition + fusion.plating.value.recognition + REC/%(year)s/ + 5 + + + + + diff --git a/fusion_plating/fusion_plating_culture/models/__init__.py b/fusion_plating/fusion_plating_culture/models/__init__.py new file mode 100644 index 00000000..a6d8b143 --- /dev/null +++ b/fusion_plating/fusion_plating_culture/models/__init__.py @@ -0,0 +1,10 @@ +# -*- 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_value_set +from . import fp_value +from . import fp_value_rotation +from . import fp_value_recognition +from . import hr_employee diff --git a/fusion_plating/fusion_plating_culture/models/fp_value.py b/fusion_plating/fusion_plating_culture/models/fp_value.py new file mode 100644 index 00000000..eba93840 --- /dev/null +++ b/fusion_plating/fusion_plating_culture/models/fp_value.py @@ -0,0 +1,100 @@ +# -*- 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 FpValue(models.Model): + """A single value, fundamental, core behaviour, or belief. + + Values are grouped under a fusion.plating.value.set. Each value has a + short title, an optional display number (e.g. "Fundamental #3"), a + long-form description, stories / examples of living the value, and an + icon for display in kanban tiles. + """ + _name = 'fusion.plating.value' + _description = 'Fusion Plating — Value' + _order = 'set_id, sequence, number, name' + + name = fields.Char( + string='Title', + required=True, + help='Short, memorable title for the value, e.g. ' + '"Do What\'s Best for the Customer".', + ) + sequence = fields.Integer( + string='Sequence', + default=10, + help='Display order within the set.', + ) + number = fields.Integer( + string='Number', + help='Optional display number (e.g. 1, 2, 3). Shows on kanban ' + 'tiles and in rotation schedules.', + ) + set_id = fields.Many2one( + 'fusion.plating.value.set', + string='Value Set', + required=True, + ondelete='cascade', + index=True, + ) + company_id = fields.Many2one( + 'res.company', + string='Company', + related='set_id.company_id', + store=True, + readonly=True, + ) + description = fields.Html( + string='Description', + help='Long-form explanation of what this value means and how it ' + 'shows up in daily work.', + ) + examples = fields.Html( + string='Stories & Examples', + help='Stories, anecdotes, and concrete examples of people living ' + 'this value.', + ) + icon = fields.Char( + string='Icon', + default='fa-star', + help='Font Awesome icon class (without the "fa " prefix), ' + 'e.g. "fa-heart", "fa-bolt".', + ) + color = fields.Integer( + string='Color Index', + default=0, + help='Kanban colour index (0-11).', + ) + active = fields.Boolean( + string='Active', + default=True, + ) + recognition_ids = fields.One2many( + 'fusion.plating.value.recognition', + 'value_id', + string='Recognitions', + ) + recognition_count = fields.Integer( + string='Recognition Count', + compute='_compute_recognition_count', + ) + + @api.depends('recognition_ids', 'recognition_ids.state') + def _compute_recognition_count(self): + for rec in self: + rec.recognition_count = len(rec.recognition_ids.filtered( + lambda r: r.state == 'published' + )) + + def name_get(self): + result = [] + for rec in self: + if rec.number: + result.append((rec.id, f'#{rec.number} — {rec.name}')) + else: + result.append((rec.id, rec.name)) + return result diff --git a/fusion_plating/fusion_plating_culture/models/fp_value_recognition.py b/fusion_plating/fusion_plating_culture/models/fp_value_recognition.py new file mode 100644 index 00000000..65d2a194 --- /dev/null +++ b/fusion_plating/fusion_plating_culture/models/fp_value_recognition.py @@ -0,0 +1,120 @@ +# -*- 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 FpValueRecognition(models.Model): + """A peer recognition tied to a specific value. + + Example: "I saw Sarah living Value #3 yesterday when she stayed late + to re-mask a customer part after first-article inspection flagged a + nick." + + Recognitions start in draft, move to published, and can be archived + when they roll off a stand-up wall or a newsletter. + """ + _name = 'fusion.plating.value.recognition' + _description = 'Fusion Plating — Value Recognition' + _inherit = ['mail.thread', 'mail.activity.mixin'] + _order = 'nomination_date desc, id desc' + + name = fields.Char( + string='Title', + required=True, + tracking=True, + help='Short headline for the recognition, e.g. ' + '"Sarah stayed late for the customer".', + default=lambda self: _('New Recognition'), + ) + reference = fields.Char( + string='Reference', + readonly=True, + copy=False, + default=lambda self: _('New'), + ) + value_id = fields.Many2one( + 'fusion.plating.value', + string='Value', + required=True, + tracking=True, + ondelete='restrict', + ) + set_id = fields.Many2one( + 'fusion.plating.value.set', + string='Value Set', + related='value_id.set_id', + store=True, + readonly=True, + ) + recognized_employee_id = fields.Many2one( + 'hr.employee', + string='Recognized', + required=True, + tracking=True, + help='The person being recognized.', + ) + nominated_by_id = fields.Many2one( + 'res.users', + string='Nominated By', + default=lambda self: self.env.user, + tracking=True, + ) + nomination_date = fields.Datetime( + string='Nominated On', + default=fields.Datetime.now, + tracking=True, + ) + story = fields.Html( + string='Story', + help='What happened? Describe the moment you saw the value being ' + 'lived.', + ) + state = fields.Selection( + [ + ('draft', 'Draft'), + ('published', 'Published'), + ('archived', 'Archived'), + ], + string='Status', + default='draft', + required=True, + tracking=True, + ) + company_id = fields.Many2one( + 'res.company', + string='Company', + default=lambda self: self.env.company, + ) + color = fields.Integer( + string='Color Index', + default=0, + ) + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if not vals.get('reference') or vals['reference'] == _('New'): + vals['reference'] = self.env['ir.sequence'].next_by_code( + 'fusion.plating.value.recognition' + ) or _('New') + return super().create(vals_list) + + def action_publish(self): + for rec in self: + rec.state = 'published' + rec.message_post(body=_('Recognition published.')) + return True + + def action_archive_recognition(self): + for rec in self: + rec.state = 'archived' + rec.message_post(body=_('Recognition archived.')) + return True + + def action_reset_to_draft(self): + for rec in self: + rec.state = 'draft' + return True diff --git a/fusion_plating/fusion_plating_culture/models/fp_value_rotation.py b/fusion_plating/fusion_plating_culture/models/fp_value_rotation.py new file mode 100644 index 00000000..785b89d8 --- /dev/null +++ b/fusion_plating/fusion_plating_culture/models/fp_value_rotation.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) +# Part of the Fusion Plating product family. + +from datetime import timedelta + +from odoo import _, api, fields, models + + +class FpValueRotation(models.Model): + """A schedule that rotates a value to the front of the shop's attention. + + Used to drive "Fundamental of the Day/Week/Month" programs. The admin + creates a rotation, picks a set and a period, and enables it. This + module does NOT ship an ir.cron — the shop admin wires a cron to call + `_cron_advance_rotation` if they want automatic advancement. Many + shops prefer to advance the rotation manually at a weekly stand-up. + """ + _name = 'fusion.plating.value.rotation' + _description = 'Fusion Plating — Value Rotation Schedule' + _inherit = ['mail.thread'] + _order = 'name' + + name = fields.Char( + string='Name', + required=True, + tracking=True, + help='Display name of the rotation, e.g. "Weekly Fundamental".', + ) + set_id = fields.Many2one( + 'fusion.plating.value.set', + string='Value Set', + required=True, + ondelete='cascade', + tracking=True, + ) + rotation_period = fields.Selection( + [ + ('daily', 'Daily'), + ('weekly', 'Weekly'), + ('monthly', 'Monthly'), + ], + string='Rotation Period', + required=True, + default='weekly', + tracking=True, + ) + current_value_id = fields.Many2one( + 'fusion.plating.value', + string='Current Value', + readonly=True, + tracking=True, + help='The value currently surfaced by this rotation. Updated by ' + '_cron_advance_rotation.', + domain="[('set_id', '=', set_id)]", + ) + last_rotation_date = fields.Date( + string='Last Rotated On', + readonly=True, + ) + next_rotation_date = fields.Date( + string='Next Rotation', + compute='_compute_next_rotation_date', + ) + company_id = fields.Many2one( + 'res.company', + string='Company', + related='set_id.company_id', + store=True, + readonly=True, + ) + active = fields.Boolean( + string='Active', + default=True, + ) + + @api.depends('last_rotation_date', 'rotation_period') + def _compute_next_rotation_date(self): + for rec in self: + if not rec.last_rotation_date: + rec.next_rotation_date = fields.Date.context_today(rec) + continue + delta_days = { + 'daily': 1, + 'weekly': 7, + 'monthly': 30, + }.get(rec.rotation_period, 7) + rec.next_rotation_date = rec.last_rotation_date + timedelta( + days=delta_days + ) + + def action_advance(self): + """Manually advance the rotation to the next value. + + Wired to a form button so a shop that doesn't want cron-driven + rotation can move the fundamental forward at a stand-up meeting. + """ + for rec in self: + rec._advance_one() + return True + + def _advance_one(self): + """Move this rotation to the next value in the set, wrapping.""" + self.ensure_one() + values = self.set_id.value_ids.filtered('active').sorted( + key=lambda v: (v.sequence, v.number, v.id) + ) + if not values: + return False + if not self.current_value_id: + next_value = values[0] + else: + try: + idx = list(values).index(self.current_value_id) + next_idx = (idx + 1) % len(values) + next_value = values[next_idx] + except ValueError: + next_value = values[0] + self.write({ + 'current_value_id': next_value.id, + 'last_rotation_date': fields.Date.context_today(self), + }) + self.message_post( + body=_('Rotation advanced to %(value)s.') % { + 'value': next_value.display_name, + } + ) + return True + + @api.model + def _cron_advance_rotation(self): + """Cron entry point. + + A shop admin can wire an ir.cron to this method. The module + intentionally does not ship one — different shops want different + cadences. Only advances rotations whose next_rotation_date is + today or earlier. + """ + today = fields.Date.context_today(self) + rotations = self.search([('active', '=', True)]) + for rotation in rotations: + if ( + not rotation.last_rotation_date + or rotation.next_rotation_date + and rotation.next_rotation_date <= today + ): + rotation._advance_one() + return True diff --git a/fusion_plating/fusion_plating_culture/models/fp_value_set.py b/fusion_plating/fusion_plating_culture/models/fp_value_set.py new file mode 100644 index 00000000..96d729f9 --- /dev/null +++ b/fusion_plating/fusion_plating_culture/models/fp_value_set.py @@ -0,0 +1,111 @@ +# -*- 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 FpValueSet(models.Model): + """A named collection of values owned by a company. + + Each shop loads its own set. One shop might call theirs + "Entech Fundamentals" with 24 items, another "Our Way" with 10. A shop + may have several sets (historical, draft, archived) but usually only + one is marked primary. + + The module ships NO seed data — this is a generic framework. + """ + _name = 'fusion.plating.value.set' + _description = 'Fusion Plating — Value Set' + _inherit = ['mail.thread', 'mail.activity.mixin'] + _order = 'is_primary desc, name' + + name = fields.Char( + string='Name', + required=True, + tracking=True, + help='Display name of the set, e.g. "Entech Fundamentals", "Our Way".', + ) + code = fields.Char( + string='Code', + required=True, + tracking=True, + help='Unique slug identifier, e.g. "entech_fundamentals".', + ) + description = fields.Html( + string='Description', + help='Long-form description of what this set represents and how ' + 'the shop uses it.', + ) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + tracking=True, + ) + value_ids = fields.One2many( + 'fusion.plating.value', + 'set_id', + string='Values', + ) + value_count = fields.Integer( + string='Value Count', + compute='_compute_value_count', + ) + is_primary = fields.Boolean( + string='Primary Set', + default=False, + tracking=True, + help='Marks this as the shop\'s primary culture set. Only one set ' + 'per company should be primary at a time.', + ) + active = fields.Boolean( + string='Active', + default=True, + ) + + _sql_constraints = [ + ( + 'fp_value_set_code_company_uniq', + 'unique(code, company_id)', + 'Value set code must be unique within a company.', + ), + ] + + @api.depends('value_ids') + def _compute_value_count(self): + for rec in self: + rec.value_count = len(rec.value_ids) + + def write(self, vals): + """When flipping a set to primary, demote any other primary set in + the same company so there is only one primary per company. + """ + res = super().write(vals) + if vals.get('is_primary'): + for rec in self: + if rec.is_primary: + others = self.search([ + ('id', '!=', rec.id), + ('company_id', '=', rec.company_id.id), + ('is_primary', '=', True), + ]) + if others: + others.write({'is_primary': False}) + return res + + @api.model_create_multi + def create(self, vals_list): + recs = super().create(vals_list) + for rec in recs: + if rec.is_primary: + others = self.search([ + ('id', '!=', rec.id), + ('company_id', '=', rec.company_id.id), + ('is_primary', '=', True), + ]) + if others: + others.write({'is_primary': False}) + return recs diff --git a/fusion_plating/fusion_plating_culture/models/hr_employee.py b/fusion_plating/fusion_plating_culture/models/hr_employee.py new file mode 100644 index 00000000..49cb07f0 --- /dev/null +++ b/fusion_plating/fusion_plating_culture/models/hr_employee.py @@ -0,0 +1,47 @@ +# -*- 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): + """Extend hr.employee with culture recognition totals. + + Uses the x_fc_ prefix per the Fusion Central field-naming convention + for base Odoo model extensions. + """ + _inherit = 'hr.employee' + + x_fc_recognition_ids = fields.One2many( + 'fusion.plating.value.recognition', + 'recognized_employee_id', + string='Recognitions', + ) + x_fc_recognition_count = fields.Integer( + string='Recognition Count', + compute='_compute_x_fc_recognition_stats', + ) + x_fc_last_recognized_date = fields.Datetime( + string='Last Recognized', + compute='_compute_x_fc_recognition_stats', + ) + + @api.depends( + 'x_fc_recognition_ids', + 'x_fc_recognition_ids.state', + 'x_fc_recognition_ids.nomination_date', + ) + def _compute_x_fc_recognition_stats(self): + for rec in self: + published = rec.x_fc_recognition_ids.filtered( + lambda r: r.state == 'published' + ) + rec.x_fc_recognition_count = len(published) + if published: + rec.x_fc_last_recognized_date = max( + published.mapped('nomination_date') + ) + else: + rec.x_fc_last_recognized_date = False diff --git a/fusion_plating/fusion_plating_culture/security/fp_culture_security.xml b/fusion_plating/fusion_plating_culture/security/fp_culture_security.xml new file mode 100644 index 00000000..c8500a27 --- /dev/null +++ b/fusion_plating/fusion_plating_culture/security/fp_culture_security.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + Fusion Plating: Value Set — multi-company + + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + + + Fusion Plating: Value — multi-company + + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + + + Fusion Plating: Value Rotation — multi-company + + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + + + Fusion Plating: Recognition — multi-company + + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + + diff --git a/fusion_plating/fusion_plating_culture/security/ir.model.access.csv b/fusion_plating/fusion_plating_culture/security/ir.model.access.csv new file mode 100644 index 00000000..df4b01b8 --- /dev/null +++ b/fusion_plating/fusion_plating_culture/security/ir.model.access.csv @@ -0,0 +1,13 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_fp_value_set_operator,fp.value.set.operator,model_fusion_plating_value_set,fusion_plating.group_fusion_plating_operator,1,0,0,0 +access_fp_value_set_supervisor,fp.value.set.supervisor,model_fusion_plating_value_set,fusion_plating.group_fusion_plating_supervisor,1,0,0,0 +access_fp_value_set_manager,fp.value.set.manager,model_fusion_plating_value_set,fusion_plating.group_fusion_plating_manager,1,1,1,1 +access_fp_value_operator,fp.value.operator,model_fusion_plating_value,fusion_plating.group_fusion_plating_operator,1,0,0,0 +access_fp_value_supervisor,fp.value.supervisor,model_fusion_plating_value,fusion_plating.group_fusion_plating_supervisor,1,0,0,0 +access_fp_value_manager,fp.value.manager,model_fusion_plating_value,fusion_plating.group_fusion_plating_manager,1,1,1,1 +access_fp_value_rotation_operator,fp.value.rotation.operator,model_fusion_plating_value_rotation,fusion_plating.group_fusion_plating_operator,1,0,0,0 +access_fp_value_rotation_supervisor,fp.value.rotation.supervisor,model_fusion_plating_value_rotation,fusion_plating.group_fusion_plating_supervisor,1,1,0,0 +access_fp_value_rotation_manager,fp.value.rotation.manager,model_fusion_plating_value_rotation,fusion_plating.group_fusion_plating_manager,1,1,1,1 +access_fp_value_recognition_operator,fp.value.recognition.operator,model_fusion_plating_value_recognition,fusion_plating.group_fusion_plating_operator,1,0,0,0 +access_fp_value_recognition_supervisor,fp.value.recognition.supervisor,model_fusion_plating_value_recognition,fusion_plating.group_fusion_plating_supervisor,1,1,1,0 +access_fp_value_recognition_manager,fp.value.recognition.manager,model_fusion_plating_value_recognition,fusion_plating.group_fusion_plating_manager,1,1,1,1 diff --git a/fusion_plating/fusion_plating_culture/static/src/scss/fusion_plating_culture.scss b/fusion_plating/fusion_plating_culture/static/src/scss/fusion_plating_culture.scss new file mode 100644 index 00000000..6a18e45d --- /dev/null +++ b/fusion_plating/fusion_plating_culture/static/src/scss/fusion_plating_culture.scss @@ -0,0 +1,128 @@ +// ============================================================================= +// Fusion Plating — Culture & Values — backend styles +// Copyright 2026 Nexa Systems Inc. +// License OPL-1 (Odoo Proprietary License v1.0) +// +// THEME AWARENESS +// --------------- +// Like the core fusion_plating SCSS, this file uses ONLY Bootstrap / +// Odoo CSS custom properties so every surface adapts to light and dark +// mode automatically. No hardcoded hex colors anywhere. +// +// background: var(--bs-body-bg) +// surface: var(--o-view-background-color) +// foreground: var(--bs-body-color) +// muted text: var(--bs-secondary-color) +// border: var(--bs-border-color) +// primary: var(--o-action) +// +// Semantic tints use color-mix() so a green badge darkens on light mode +// and brightens on dark mode automatically. +// ============================================================================= + + +// ----------------------------------------------------------------------------- +// Value kanban tile — a colourful card showing number + name + icon +// ----------------------------------------------------------------------------- +.o_fp_value_card { + background-color: var(--o-view-background-color, var(--bs-body-bg)); + color: var(--bs-body-color); + border: 1px solid var(--bs-border-color); + border-radius: 12px; + padding: 16px 18px; + min-height: 140px; + transition: border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease; + + &:hover { + border-color: color-mix(in srgb, var(--o-action) 50%, var(--bs-border-color)); + box-shadow: 0 4px 14px color-mix(in srgb, var(--bs-body-color) 10%, transparent); + transform: translateY(-1px); + } + + .o_fp_value_number { + font-size: 2.4rem; + font-weight: 700; + line-height: 1; + color: var(--o-action); + letter-spacing: -0.02em; + } + + .o_fp_value_icon { + font-size: 1.4rem; + opacity: 0.6; + } + + .o_fp_value_name { + font-size: 1rem; + line-height: 1.25; + color: var(--bs-body-color); + } + + .o_fp_value_meta { + color: var(--bs-secondary-color); + } +} + + +// ----------------------------------------------------------------------------- +// Recognition kanban card — badge-style card grouped by state +// ----------------------------------------------------------------------------- +.o_fp_recognition_card { + background-color: var(--o-view-background-color, var(--bs-body-bg)); + color: var(--bs-body-color); + border: 1px solid var(--bs-border-color); + border-left-width: 4px; + border-left-color: color-mix(in srgb, var(--o-action) 70%, var(--bs-border-color)); + border-radius: 10px; + padding: 12px 14px; + transition: border-color 120ms ease, box-shadow 120ms ease; + + &:hover { + border-color: color-mix(in srgb, var(--o-action) 50%, var(--bs-border-color)); + box-shadow: 0 2px 8px color-mix(in srgb, var(--bs-body-color) 8%, transparent); + } + + strong { + color: var(--bs-body-color); + font-size: 0.95rem; + line-height: 1.2; + } + + .text-muted { + color: var(--bs-secondary-color) !important; + } +} + + +// ----------------------------------------------------------------------------- +// Recognition kanban — state-based accent on the left border +// ----------------------------------------------------------------------------- +.o_fp_recognition_kanban { + + // Draft — subtle muted accent + .o_kanban_group[data-id$="draft"] .o_fp_recognition_card { + border-left-color: var(--bs-secondary-color); + } + + // Published — success accent + .o_kanban_group[data-id$="published"] .o_fp_recognition_card { + border-left-color: var(--bs-success); + } + + // Archived — dimmed + .o_kanban_group[data-id$="archived"] .o_fp_recognition_card { + border-left-color: color-mix(in srgb, var(--bs-secondary-color) 60%, transparent); + opacity: 0.75; + } +} + + +// ----------------------------------------------------------------------------- +// Value kanban — spacing for the grouped kanban columns +// ----------------------------------------------------------------------------- +.o_fp_value_kanban { + + .o_kanban_record { + margin-bottom: 8px; + } +} diff --git a/fusion_plating/fusion_plating_culture/views/fp_menu.xml b/fusion_plating/fusion_plating_culture/views/fp_menu.xml new file mode 100644 index 00000000..4dba06c7 --- /dev/null +++ b/fusion_plating/fusion_plating_culture/views/fp_menu.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + diff --git a/fusion_plating/fusion_plating_culture/views/fp_value_recognition_views.xml b/fusion_plating/fusion_plating_culture/views/fp_value_recognition_views.xml new file mode 100644 index 00000000..cc17d6bd --- /dev/null +++ b/fusion_plating/fusion_plating_culture/views/fp_value_recognition_views.xml @@ -0,0 +1,158 @@ + + + + + + fp.value.recognition.list + fusion.plating.value.recognition + + + + + + + + + + + + + + + fp.value.recognition.form + fusion.plating.value.recognition + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + +
+ + +
+
+ + + fp.value.recognition.kanban + fusion.plating.value.recognition + + + + + + + + + + + + +
+
+ +