Sale Semaphore

sale_semaphore
REPOSITORY
REPOSITORYOCA/sale-workflow
GIT
GIThttps://github.com/OCA/sale-workflow.git
GIT FOLDER
GIT FOLDERhttps://github.com/OCA/sale-workflow/tree/18.0/sale_semaphore
VERSION
VERSION 1.0.2
CATEGORY
CATEGORYSale
LICENSE
LICENSEAGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSTecnativa, Odoo Community Association (OCA)
MAINTAINERS
MAINTAINERSTecnativa, Odoo Community Association (OCA)
COMMITTERS
COMMITTERSWeblate, OCA-git-bot, oca-ci, Carlos Roca, christian-ramos-tecnativa
WEBSITE
WEBSITEhttps://github.com/OCA/sale-workflow
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 19:30:11
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - sale
    - sales_team
    - base
    - mail
    - base_setup
    - web
    - bus
    - web_tour
    - html_editor
    - account_payment
    - account
    - onboarding
    - product
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
    - resource
    - payment
    - utm
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
This module adds a commercial semaphore to help sales teams assess
whether a quoted price stays within the discount policy defined for a
product or product category.

For each product you can define three discount thresholds, expressed as
a percentage over the public sale price:

- **Green**: price is within the preferred commercial margin.
- **Yellow**: price is still acceptable, but already close to the limit.
- **Red**: price is below the warning threshold and should be reviewed.

The semaphore is displayed directly on sales order lines and propagated
to customer invoice lines and reporting models, so the same visual
indicator is available during the full sales flow.

When no product-specific configuration exists, the module can also reuse
the thresholds defined on the product category.

Code Analysis

Views touched (7)
XML IDNameModelTypeStatus
product_category_form_view product.category xpath Inherits product.product_category_form_view
product_template_form_view product.template xpath Inherits product.product_template_form_view
sale_order_line_view_form_readonly sale.order.line field Inherits sale.sale_order_line_view_form_readonly
view_account_invoice_report_search account.invoice.report filter Inherits account.view_account_invoice_report_search
view_move_form account.move xpath Inherits account.view_move_form
view_order_form sale.order xpath Inherits sale.view_order_form
view_order_product_search sale.report filter Inherits sale.view_order_product_search
Models touched (8)

New fields (1)
  • semaphore Selection
    readonly=True args: [('success', '🟢'), ('warning', '🟡'), ('danger', '🔴')]
Public methods (0)

No public methods.

New fields (1)
  • semaphore Selection
    args: [('success', '🟢'), ('warning', '🟡'), ('danger', '🔴')]
Public methods (0)

No public methods.

New fields (4)
  • semaphore_active Boolean
  • semaphore_discount_danger Float
    string='Discount Danger'
  • semaphore_discount_success Float
    string='Discount Success'
  • semaphore_discount_warning Float
    string='Discount Warning'
Public methods (0)

No public methods.

New fields (4)
  • semaphore_active Selection
    help='If the value is blank, it will be inherited from the category.' selection=[('yes', 'Yes'), ('no', 'No')]
  • semaphore_discount_danger Float
    string='Discount Danger'
  • semaphore_discount_success Float
    string='Discount Success'
  • semaphore_discount_warning Float
    string='Discount Warning'
Public methods (0)

No public methods.

New fields (4)
  • semaphore_active Selection
    compute='_compute_semaphore_values' help='If the value is blank, it will be inherited from the category.' inverse='_inverse_semaphore_active' selection=[('yes', 'Yes'), ('no', 'No')]
  • semaphore_discount_danger Float
    compute='_compute_semaphore_values' inverse='_inverse_semaphore_discount_danger' string='Discount Danger'
  • semaphore_discount_success Float
    compute='_compute_semaphore_values' inverse='_inverse_semaphore_discount_success' string='Discount Success'
  • semaphore_discount_warning Float
    compute='_compute_semaphore_values' inverse='_inverse_semaphore_discount_warning' string='Discount Warning'
Public methods (0)

No public methods.

New fields (1)
  • price_below_semaphore Boolean
    compute='_compute_price_below_semaphore'
Public methods (1)
  • action_confirm(self)

New fields (6)
  • price_below_semaphore Boolean
    compute='_compute_price_below_semaphore'
  • semaphore Selection
    compute='_compute_semaphore' store=True args: [('success', '🟢'), ('warning', '🟡'), ('danger', '🔴')]
  • semaphore_active Boolean
    compute='_compute_semaphore' store=True
  • semaphore_max_price_danger Float
    compute='_compute_semaphore_max_prices'
  • semaphore_max_price_success Float
    compute='_compute_semaphore_max_prices'
  • semaphore_max_price_warning Float
    compute='_compute_semaphore_max_prices'
Public methods (1)
  • write(self, vals)

New fields (1)
  • semaphore Selection
    args: [('success', '🟢'), ('warning', '🟡'), ('danger', '🔴')]
Public methods (0)

No public methods.

REPOSITORY
REPOSITORYOCA/sale-workflow
GIT
GIThttps://github.com/OCA/sale-workflow.git
GIT FOLDER
GIT FOLDERhttps://github.com/OCA/sale-workflow/tree/15.0/sale_semaphore
VERSION
VERSION 1.0.0
CATEGORY
CATEGORYSale
LICENSE
LICENSEAGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSTecnativa, Odoo Community Association (OCA)
MAINTAINERS
MAINTAINERSTecnativa, Odoo Community Association (OCA)
COMMITTERS
COMMITTERSOCA-git-bot, oca-ci, Carlos Roca
WEBSITE
WEBSITEhttps://github.com/OCA/sale-workflow
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:46:33
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - sale
    - sales_team
    - base
    - mail
    - base_setup
    - web
    - bus
    - web_tour
    - payment
    - account
    - product
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
    - resource
    - utm
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION

Code Analysis

Views touched (6)
XML IDNameModelTypeStatus
product_category_form_view product.category xpath Inherits product.product_category_form_view
product_template_form_view product.template xpath Inherits product.product_template_form_view
view_account_invoice_report_search account.invoice.report filter Inherits account.view_account_invoice_report_search
view_move_form account.move xpath Inherits account.view_move_form
view_order_form sale.order xpath Inherits sale.view_order_form
view_order_product_search sale.report filter Inherits sale.view_order_product_search
Models touched (8)

New fields (1)
  • semaphore Selection
    readonly=True args: [('success', '🟢'), ('warning', '🟡'), ('danger', '🔴')]
Public methods (0)

No public methods.

New fields (1)
  • semaphore Selection
    args: [('success', '🟢'), ('warning', '🟡'), ('danger', '🔴')]
Public methods (0)

No public methods.

New fields (4)
  • semaphore_active Boolean
  • semaphore_discount_danger Float
    string='Discount Danger'
  • semaphore_discount_success Float
    string='Discount Success'
  • semaphore_discount_warning Float
    string='Discount Warning'
Public methods (0)

No public methods.

New fields (4)
  • semaphore_active Boolean
  • semaphore_discount_danger Float
    string='Discount Danger'
  • semaphore_discount_success Float
    string='Discount Success'
  • semaphore_discount_warning Float
    string='Discount Warning'
Public methods (0)

No public methods.

New fields (4)
  • semaphore_active Boolean
    compute='_compute_semaphore_values' inverse='_inverse_semaphore_active'
  • semaphore_discount_danger Float
    compute='_compute_semaphore_values' inverse='_inverse_semaphore_discount_danger' string='Discount Danger'
  • semaphore_discount_success Float
    compute='_compute_semaphore_values' inverse='_inverse_semaphore_discount_success' string='Discount Success'
  • semaphore_discount_warning Float
    compute='_compute_semaphore_values' inverse='_inverse_semaphore_discount_warning' string='Discount Warning'
Public methods (0)

No public methods.

New fields (1)
  • price_below_semaphore Boolean
    compute='_compute_price_below_semaphore'
Public methods (1)
  • action_confirm(self)

New fields (6)
  • price_below_semaphore Boolean
    compute='_compute_price_below_semaphore'
  • semaphore Selection
    compute='_compute_semaphore' store=True args: [('success', '🟢'), ('warning', '🟡'), ('danger', '🔴')]
  • semaphore_active Boolean
    compute='_compute_semaphore' store=True
  • semaphore_max_price_danger Float
    compute='_compute_semaphore_max_prices'
  • semaphore_max_price_success Float
    compute='_compute_semaphore_max_prices'
  • semaphore_max_price_warning Float
    compute='_compute_semaphore_max_prices'
Public methods (2)
  • get_reset_price_unit(self)
  • write(self, vals)

New fields (1)
  • semaphore Selection
    args: [('success', '🟢'), ('warning', '🟡'), ('danger', '🔴')]
Public methods (0)

No public methods.