# -*- 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 from ._fp_uom_selection import FP_UOM_SELECTION class FpStepKind(models.Model): """User-extensible Step Kind catalog. Replaces the hardcoded `default_kind` Selection on fp.step.template and fusion.plating.process.node. Each kind carries a list of default inputs that get seeded onto a step template when the kind is picked. """ _name = 'fp.step.kind' _description = 'Fusion Plating — Step Kind' _order = 'sequence, name' code = fields.Char( string='Code', required=True, index=True, help='Stable lowercase technical key. Used by automation rules and ' 'workflow-state triggers (e.g. "cleaning", "plate"). Lower-case' ' enforced; underscores allowed.', ) name = fields.Char(string='Name', required=True, translate=True) sequence = fields.Integer(string='Sequence', default=10) active = fields.Boolean(string='Active', default=True) description = fields.Html(string='Description') icon = fields.Selection( selection='_get_icon_selection', string='Icon', default='fa-cog', ) # 2026-05-24 — Shop Floor live-step fix. # Each kind self-declares which plant-view column its steps land in. # Replaces the hardcoded _STEP_KIND_TO_AREA dict (removed from # fusion_plating_jobs/models/fp_job_step.py). Pre-migrate # 19.0.21.2.0 seeds existing rows before NOT NULL hits the schema. area_kind = fields.Selection( [ ('receiving', 'Receiving'), ('masking', 'Masking'), ('blasting', 'Blasting'), ('racking', 'Racking'), ('plating', 'Plating'), ('baking', 'Baking'), ('de_racking', 'De-Racking'), ('inspection', 'Final Inspection'), ('shipping', 'Shipping'), ], string='Shop Floor Column', required=True, index=True, help='Determines which column on the Shop Floor plant kanban shows ' 'cards whose active step uses this kind. Step kinds drive ' 'routing automatically — picking a kind tells the system both ' 'what gates fire AND where the card lives.', ) company_id = fields.Many2one( 'res.company', string='Company', default=lambda self: self.env.company, ) default_input_ids = fields.One2many( 'fp.step.kind.default.input', 'kind_id', string='Default Inputs', copy=True, help='Auto-seeded onto a step template when this kind is picked ' '(via the "Seed Defaults" action).', ) template_count = fields.Integer( string='Templates', compute='_compute_template_count') _sql_constraints = [ ('fp_step_kind_code_company_uniq', 'unique(code, company_id)', 'Step kind code must be unique within a company.'), ] # Curated FontAwesome 4 icon catalog for the visual icon picker. # Bigger than the 24 historical fp.process.node list — covers # manufacturing, lab, quality, shipping, safety, time, status etc. # FA4 ships with Odoo (no extra deps). Key = CSS class, Value = label. _ICON_SELECTION = [ # Process / chemistry ('fa-flask', 'Flask / Chemistry'), ('fa-tint', 'Drop / Liquid'), ('fa-tachometer', 'Gauge / Measure'), ('fa-thermometer-half', 'Temp / Heat'), ('fa-fire', 'Fire / Bake'), ('fa-snowflake-o', 'Cold / Freeze'), ('fa-bolt', 'Bolt / Electric'), ('fa-plug', 'Plug / Power'), ('fa-magnet', 'Magnet'), ('fa-bullseye', 'Target / Blast'), ('fa-shower', 'Shower / Clean'), ('fa-bathtub', 'Bathtub / Soak'), ('fa-tachometer', 'Tachometer'), # Equipment / shop ('fa-industry', 'Industry / Line'), ('fa-wrench', 'Wrench / Operation'), ('fa-cog', 'Gear / General'), ('fa-cogs', 'Gears / System'), ('fa-sitemap', 'Sitemap / Process'), ('fa-cubes', 'Cubes / Batch'), ('fa-cube', 'Cube / Part'), ('fa-th', 'Grid / Racking'), ('fa-th-large', 'Large Grid'), ('fa-server', 'Server / Rack'), ('fa-database', 'Database / Tank'), ('fa-archive', 'Archive / Box'), ('fa-recycle', 'Recycle / Reuse'), ('fa-balance-scale', 'Scale / Balance'), # Masking / surface ('fa-paint-brush', 'Paint / Masking'), ('fa-eraser', 'Eraser / De-Masking'), ('fa-shield', 'Shield / Protect'), ('fa-diamond', 'Diamond / Plating'), ('fa-circle-o-notch', 'Coating Layer'), # Inspection / quality ('fa-search', 'Search / Inspect'), ('fa-search-plus', 'Search Plus'), ('fa-eye', 'Eye / Visual'), ('fa-eye-slash', 'Eye Slash / Hidden'), ('fa-camera', 'Camera / Photo'), ('fa-check', 'Check'), ('fa-check-circle', 'Check / Approve'), ('fa-check-square-o', 'Checkbox'), ('fa-times', 'Reject / Cancel'), ('fa-times-circle', 'Reject Circle'), ('fa-exclamation-triangle', 'Warning'), ('fa-exclamation-circle', 'Alert'), ('fa-info-circle', 'Info'), ('fa-question-circle', 'Question'), ('fa-bug', 'Bug / Defect'), ('fa-flag', 'Flag'), ('fa-flag-checkered', 'Finish / Done'), ('fa-trophy', 'Trophy / Pass'), ('fa-thumbs-up', 'Thumbs Up'), ('fa-thumbs-down', 'Thumbs Down'), ('fa-star', 'Star'), ('fa-bookmark', 'Bookmark'), ('fa-certificate', 'Certificate'), # Time ('fa-clock-o', 'Clock / Wait'), ('fa-hourglass-half', 'Hourglass'), ('fa-hourglass-end', 'Hourglass End'), ('fa-calendar', 'Calendar'), ('fa-calendar-check-o', 'Scheduled'), ('fa-history', 'History'), # Safety / handling ('fa-hand-paper-o', 'Hand / Manual'), ('fa-hand-stop-o', 'Stop Hand'), ('fa-life-ring', 'Safety / Life Ring'), ('fa-medkit', 'First Aid'), ('fa-user-md', 'Inspector'), ('fa-lock', 'Lock / Hold'), ('fa-unlock', 'Unlock / Release'), ('fa-key', 'Key'), # Documentation / certs ('fa-file-text-o', 'Document'), ('fa-file-pdf-o', 'PDF'), ('fa-file-image-o', 'Image File'), ('fa-clipboard', 'Clipboard'), ('fa-list-alt', 'Checklist'), ('fa-list-ul', 'List'), ('fa-tags', 'Tags'), ('fa-tag', 'Tag'), ('fa-barcode', 'Barcode'), ('fa-qrcode', 'QR Code'), ('fa-pencil', 'Pencil'), ('fa-edit', 'Edit'), ('fa-print', 'Print'), ('fa-paperclip', 'Attach'), # Shipping / logistics ('fa-truck', 'Truck / Receiving'), ('fa-paper-plane', 'Ship / Send'), ('fa-plane', 'Plane / Airfreight'), ('fa-ship', 'Ship'), ('fa-shopping-cart', 'Cart'), ('fa-shopping-bag', 'Bag / Pack'), ('fa-gift', 'Gift / Package'), ('fa-suitcase', 'Suitcase'), ('fa-globe', 'Global'), ('fa-map-marker', 'Location'), ('fa-road', 'In Transit'), # Status / process flow ('fa-play-circle', 'Start'), ('fa-pause-circle', 'Pause / Hold'), ('fa-stop-circle', 'Stop'), ('fa-step-forward', 'Step Forward'), ('fa-fast-forward', 'Fast Forward'), ('fa-refresh', 'Refresh / Repeat'), ('fa-undo', 'Undo / Rework'), ('fa-share', 'Hand-off'), ('fa-arrow-right', 'Arrow Right'), ('fa-arrow-down', 'Arrow Down'), ('fa-long-arrow-right', 'Long Arrow Right'), ('fa-random', 'Random / Mix'), ('fa-exchange', 'Exchange'), ('fa-sort-amount-asc', 'Sort Asc'), ('fa-sort-amount-desc', 'Sort Desc'), ('fa-tasks', 'Tasks'), # Misc useful ('fa-sun-o', 'Sun / Dry'), ('fa-moon-o', 'Moon / Night'), ('fa-cloud', 'Cloud'), ('fa-leaf', 'Leaf / Eco'), ('fa-tree', 'Tree'), ('fa-bell', 'Bell / Alert'), ('fa-bullhorn', 'Announce'), ('fa-trash', 'Trash / Discard'), ('fa-plus-circle', 'Add'), ('fa-minus-circle', 'Remove'), ('fa-circle', 'Circle'), ('fa-square', 'Square'), ('fa-asterisk', 'Asterisk'), ('fa-cutlery', 'Cutlery / Bend'), ('fa-link', 'Link / Adhesion'), ('fa-chain-broken', 'Broken Chain'), ('fa-anchor', 'Anchor'), ('fa-ban', 'Ban / Forbidden'), ] @api.model def _get_icon_selection(self): return self._ICON_SELECTION @api.depends() def _compute_template_count(self): Tpl = self.env['fp.step.template'] for k in self: k.template_count = Tpl.search_count([('kind_id', '=', k.id)]) @api.model_create_multi def create(self, vals_list): for v in vals_list: if v.get('code'): v['code'] = v['code'].lower().strip().replace(' ', '_') return super().create(vals_list) def write(self, vals): if vals.get('code'): vals['code'] = vals['code'].lower().strip().replace(' ', '_') return super().write(vals) def action_open_templates(self): self.ensure_one() return { 'type': 'ir.actions.act_window', 'name': _('Step Templates — %s') % self.name, 'res_model': 'fp.step.template', 'view_mode': 'list,form', 'domain': [('kind_id', '=', self.id)], 'context': {'default_kind_id': self.id}, } class FpStepKindDefaultInput(models.Model): """Default input prototype attached to a step kind. When a recipe author picks a kind on a step template and clicks 'Seed Defaults', these get copied into the template's input list (idempotent — skips by name). """ _name = 'fp.step.kind.default.input' _description = 'Fusion Plating — Step Kind Default Input' _order = 'sequence, name' name = fields.Char(string='Name', required=True, translate=True) kind_id = fields.Many2one( 'fp.step.kind', string='Kind', required=True, ondelete='cascade', index=True, ) input_type = fields.Selection([ ('text', 'Text'), ('number', 'Number'), ('boolean', 'Yes/No'), ('selection', 'Selection'), ('date', 'Date / Time'), ('signature', 'Signature'), ('time_hms', 'Time (HH:MM:SS)'), ('time_seconds', 'Time (seconds)'), ('temperature', 'Temperature'), ('thickness', 'Thickness'), ('pass_fail', 'Pass / Fail'), ('photo', 'Photo'), ('multi_point_thickness', 'Multi-Point Thickness (avg)'), ('bath_chemistry_panel', 'Bath Chemistry Panel'), ('ph', 'pH'), ], string='Input Type', required=True, default='text') target_unit = fields.Selection(FP_UOM_SELECTION, string='Target Unit') required = fields.Boolean(string='Required', default=False) hint = fields.Char(string='Hint') selection_options = fields.Text(string='Selection Options', help='Comma-separated when input_type is "selection".') sequence = fields.Integer(string='Sequence', default=10)