Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

View File

@@ -0,0 +1,151 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association
===================================
Sale Product Identification Numbers
===================================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:c5dc989deb9296162ec75e40eded8c208b8f3f4923214175a78f16c6d17b9fef
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/sale-workflow/tree/18.0/sale_product_identification
:alt: OCA/sale-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/sale-workflow-18-0/sale-workflow-18-0-sale_product_identification
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=18.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
Sometimes, when selling a product, it is necessary to require
documentation (authorization, ID, etc.) from the customer to justify the
sale. Examples: Sale of a corrosive product: A document authorizing its
handling is required. To control this process, we configure a Corrosive
category in the product listing.
Sale of alcohol: In this case, sales to minors are not permitted. To
control this process, we configure a Minor category in the product
listing.
This module allows you to control these processes through
identifications in the product listing.
Please note that this functionality depends on the
partner_identification module. For detailed setup and configuration,
please refer to that addon's documentation.
**Table of contents**
.. contents::
:local:
Usage
=====
Add identifications to the product
----------------------------------
1. Go to Sales -> Products -> Products
2. Create a new product
3. Go to the Sales tab and select the Required Identification option.
4. A tree will be enabled in which you must configure the required
identification category (ies) for the product without repeating them.
|ADD_IDENTIFICATION|
5. If you define any category as optional, a wizard will appear when
confirming the order to confirm whether the identifications are
correct and continue with the process.
6. Save
Validate order with identification products
-------------------------------------------
1. Go to Sales -> Orders -> Quotations
2. Create a new order and add any products that require identification
to the lines.
3. Once the order is confirmed, the selected customer's ID numbers will
be validated to see if they have the categories required for the
added product(s).
4. If the partner does not have all the categories in their valid
identification number (Validity Date), a message will be displayed
with the missing categories (and their message defined in the
product) to validate.
|CATEGORY_REQUIRED|
5. When validating the required categories, the optional ones are
validated, for which a wizard will be displayed to confirm.
|CONFIRM_IDENTIFICATION|
6. If the customer has all the correct identifications, then the order
confirmation follows its normal flow.
.. |ADD_IDENTIFICATION| image:: https://raw.githubusercontent.com/OCA/sale-workflow/18.0/sale_product_identification/static/img/readme/ADD_IDENTIFICATION.png
.. |CATEGORY_REQUIRED| image:: https://raw.githubusercontent.com/OCA/sale-workflow/18.0/sale_product_identification/static/img/readme/CATEGORIES_REQUIRED.png
.. |CONFIRM_IDENTIFICATION| image:: https://raw.githubusercontent.com/OCA/sale-workflow/18.0/sale_product_identification/static/img/readme/CONFIRM_IDENTIFICATION.png
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/sale-workflow/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_product_identification%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* Binhex
Contributors
------------
- `Binhex <https://www.binhex.cloud/>`__:
- Edilio Escalona Almira e.escalona@binhex.cloud
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/sale-workflow <https://github.com/OCA/sale-workflow/tree/18.0/sale_product_identification>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,5 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import models
from . import wizards

View File

@@ -0,0 +1,15 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Sale Product Identification Numbers",
"author": "Binhex,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/sale-workflow",
"version": "18.0.1.0.0",
"license": "AGPL-3",
"depends": ["sale", "partner_identification"],
"data": [
"security/ir.model.access.csv",
"views/product_template_views.xml",
"wizards/wizard_confirm_identification.xml",
],
}

View File

@@ -0,0 +1,218 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_product_identification
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-16 01:31+0000\n"
"PO-Revision-Date: 2025-09-16 01:31+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/res_partner_id_number.py:0
msgid ""
"\n"
"%(product)s\n"
"%(categories)s"
msgstr ""
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/sale_order.py:0
msgid "%(order)s%(message)s"
msgstr ""
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/res_partner_id_number.py:0
msgid "A client is required to verify identifications."
msgstr "Se requiere un cliente para verificar identificaciones."
#. module: sale_product_identification
#: model:ir.model.fields,help:sale_product_identification.field_product_template_id_category__message
msgid ""
"Allows you to define a description of why this identification is being "
"added.\n"
"Example: Asking the customer for identification"
msgstr ""
"Permite definir una descripción del motivo por el que se agrega esta "
"identificación.\n"
"Ejemplo: Solicitar identificación al cliente"
#. module: sale_product_identification
#: model_terms:ir.ui.view,arch_db:sale_product_identification.confirm_identification_view
msgid "Cancel"
msgstr "Cancelar"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__category_id
msgid "Category"
msgstr "Categoría"
#. module: sale_product_identification
#: model_terms:ir.ui.view,arch_db:sale_product_identification.confirm_identification_view
msgid "Confirm"
msgstr "Confirmar"
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_confirm_identification
msgid "Confirm Identification Wizard"
msgstr "Wizard de confirmación de identificación"
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/sale_order.py:0
msgid "Confirm identification"
msgstr "Confirmar identificación"
#. module: sale_product_identification
#: model_terms:ir.ui.view,arch_db:sale_product_identification.confirm_identification_view
msgid "Confirm identifications"
msgstr "Confirmar identificaciones"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__create_uid
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__create_uid
msgid "Created by"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__create_date
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__create_date
msgid "Created on"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,help:sale_product_identification.field_product_template_id_category__is_mandatory
#, fuzzy
msgid "Defines whether identification is mandatory."
msgstr "Define si la identificación es requerida."
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__display_name
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__display_name
msgid "Display Name"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__id
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__id
msgid "ID"
msgstr ""
#. module: sale_product_identification
#: model_terms:ir.ui.view,arch_db:sale_product_identification.product_template_only_form_view
msgid "Identification"
msgstr "Identificación"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__is_mandatory
msgid "Is Mandatory"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__write_uid
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__write_uid
msgid "Last Updated by"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__write_date
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__write_date
msgid "Last Updated on"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__message
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__message
msgid "Message"
msgstr "Mensaje"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__order_ids
msgid "Orders"
msgstr "Órdenes"
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_res_partner_id_number
msgid "Partner ID Number"
msgstr "Número empresa"
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_product_template
msgid "Product"
msgstr "Producto"
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_product_template_id_category
msgid "Product Template Identification Category"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__product_tmpl_id
msgid "Product Tmpl"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_product__product_tmpl_category_ids
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template__product_tmpl_category_ids
msgid "Product Tmpl Category"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_product__required_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template__required_identification
msgid "Required Identification"
msgstr "Requiere identificación"
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_sale_order
msgid "Sales Order"
msgstr "Pedido de venta"
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/sale_order.py:0
msgid ""
"The following identifications are required for partner, please verify.\n"
" %(identifications)s"
msgstr ""
"Se requieren las siguientes identificaciones para el cliente, por favor "
"verifique.\n"
" %(identifications)s"
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/sale_order.py:0
msgid ""
"The following identifications require verification, please validate before "
"continuing:\n"
" %(identifications)s"
msgstr ""
"Las siguientes identificaciones requieren verificación. Por favor, valide "
"antes de continuar:\n"
" %(identifications)s"
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/product_template.py:0
msgid ""
"There are repeated categories in the identifications configuration, the "
"quantities are shown below.\n"
"%(categories)s"
msgstr ""
"Hay categorías repetidas en la configuración de identificaciones; las "
"cantidades se muestran a continuación.\n"
"%(categories)s"
#~ msgid "Is Required"
#~ msgstr "Es requerido"

View File

@@ -0,0 +1,213 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_product_identification
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-02 17:04+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.10.4\n"
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/res_partner_id_number.py:0
msgid ""
"\n"
"%(product)s\n"
"%(categories)s"
msgstr ""
"\n"
"%(product)s\n"
"%(categories)s"
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/sale_order.py:0
msgid "%(order)s%(message)s"
msgstr "%(order)s%(message)s"
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/res_partner_id_number.py:0
msgid "A client is required to verify identifications."
msgstr "Il cliente è tenuto a verificare i documenti d'identità."
#. module: sale_product_identification
#: model:ir.model.fields,help:sale_product_identification.field_product_template_id_category__message
msgid ""
"Allows you to define a description of why this identification is being added.\n"
"Example: Asking the customer for identification"
msgstr ""
"Consente di definire una descrizione del motivo per cui viene aggiunto "
"questo identificativo.\n"
"Esempio: richiesta di identificazione al cliente"
#. module: sale_product_identification
#: model_terms:ir.ui.view,arch_db:sale_product_identification.confirm_identification_view
msgid "Cancel"
msgstr "Annulla"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__category_id
msgid "Category"
msgstr "Categoria"
#. module: sale_product_identification
#: model_terms:ir.ui.view,arch_db:sale_product_identification.confirm_identification_view
msgid "Confirm"
msgstr "Conferma"
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_confirm_identification
msgid "Confirm Identification Wizard"
msgstr "Procedura guidata conferma identificazione"
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/sale_order.py:0
msgid "Confirm identification"
msgstr "Conferma identificazione"
#. module: sale_product_identification
#: model_terms:ir.ui.view,arch_db:sale_product_identification.confirm_identification_view
msgid "Confirm identifications"
msgstr "Conferma identificazioni"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__create_uid
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__create_uid
msgid "Created by"
msgstr "Creato da"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__create_date
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__create_date
msgid "Created on"
msgstr "Creato il"
#. module: sale_product_identification
#: model:ir.model.fields,help:sale_product_identification.field_product_template_id_category__is_mandatory
msgid "Defines whether identification is mandatory."
msgstr "Definisce quando è obbligatoria l'identificazione."
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__display_name
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__id
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__id
msgid "ID"
msgstr "ID"
#. module: sale_product_identification
#: model_terms:ir.ui.view,arch_db:sale_product_identification.product_template_only_form_view
msgid "Identification"
msgstr "Identificazione"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__is_mandatory
msgid "Is Mandatory"
msgstr "È obbligatorio"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__write_uid
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__write_uid
msgid "Last Updated by"
msgstr "Ultimo aggiornamento di"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__write_date
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__write_date
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__message
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__message
msgid "Message"
msgstr "Messaggio"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__order_ids
msgid "Orders"
msgstr "Ordini"
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_res_partner_id_number
msgid "Partner ID Number"
msgstr "Numero ID partner"
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_product_template
msgid "Product"
msgstr "Prodotto"
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_product_template_id_category
msgid "Product Template Identification Category"
msgstr "Categoria identificazione modello prodotto"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__product_tmpl_id
msgid "Product Tmpl"
msgstr "Modello prodotto"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_product__product_tmpl_category_ids
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template__product_tmpl_category_ids
msgid "Product Tmpl Category"
msgstr "Categoria modello prodotto"
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_product__required_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template__required_identification
msgid "Required Identification"
msgstr "Identificazione richiesta"
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_sale_order
msgid "Sales Order"
msgstr "Ordine di vendita"
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/sale_order.py:0
msgid ""
"The following identifications are required for partner, please verify.\n"
" %(identifications)s"
msgstr ""
"L'identificazione seguente è richiesta per il partner, verificare.\n"
" %(identifications)s"
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/sale_order.py:0
msgid ""
"The following identifications require verification, please validate before continuing:\n"
" %(identifications)s"
msgstr ""
"L'identificazione seguente richiede la verifica, verificare prima di "
"continuare:\n"
" %(identifications)s"
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/product_template.py:0
msgid ""
"There are repeated categories in the identifications configuration, the quantities are shown below.\n"
"%(categories)s"
msgstr ""
"Ci sono delle categorie ripetute nella configurazione delle identificazioni, "
"le quantità sono visualizzate in calce.\n"
"%(categories)s"

View File

@@ -0,0 +1,196 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_product_identification
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/res_partner_id_number.py:0
msgid ""
"\n"
"%(product)s\n"
"%(categories)s"
msgstr ""
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/sale_order.py:0
msgid "%(order)s%(message)s"
msgstr ""
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/res_partner_id_number.py:0
msgid "A client is required to verify identifications."
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,help:sale_product_identification.field_product_template_id_category__message
msgid ""
"Allows you to define a description of why this identification is being added.\n"
"Example: Asking the customer for identification"
msgstr ""
#. module: sale_product_identification
#: model_terms:ir.ui.view,arch_db:sale_product_identification.confirm_identification_view
msgid "Cancel"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__category_id
msgid "Category"
msgstr ""
#. module: sale_product_identification
#: model_terms:ir.ui.view,arch_db:sale_product_identification.confirm_identification_view
msgid "Confirm"
msgstr ""
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_confirm_identification
msgid "Confirm Identification Wizard"
msgstr ""
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/sale_order.py:0
msgid "Confirm identification"
msgstr ""
#. module: sale_product_identification
#: model_terms:ir.ui.view,arch_db:sale_product_identification.confirm_identification_view
msgid "Confirm identifications"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__create_uid
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__create_uid
msgid "Created by"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__create_date
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__create_date
msgid "Created on"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,help:sale_product_identification.field_product_template_id_category__is_mandatory
msgid "Defines whether identification is mandatory."
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__display_name
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__display_name
msgid "Display Name"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__id
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__id
msgid "ID"
msgstr ""
#. module: sale_product_identification
#: model_terms:ir.ui.view,arch_db:sale_product_identification.product_template_only_form_view
msgid "Identification"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__is_mandatory
msgid "Is Mandatory"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__write_uid
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__write_uid
msgid "Last Updated by"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__write_date
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__write_date
msgid "Last Updated on"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__message
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__message
msgid "Message"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_confirm_identification__order_ids
msgid "Orders"
msgstr ""
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_res_partner_id_number
msgid "Partner ID Number"
msgstr ""
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_product_template
msgid "Product"
msgstr ""
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_product_template_id_category
msgid "Product Template Identification Category"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template_id_category__product_tmpl_id
msgid "Product Tmpl"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_product__product_tmpl_category_ids
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template__product_tmpl_category_ids
msgid "Product Tmpl Category"
msgstr ""
#. module: sale_product_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_product__required_identification
#: model:ir.model.fields,field_description:sale_product_identification.field_product_template__required_identification
msgid "Required Identification"
msgstr ""
#. module: sale_product_identification
#: model:ir.model,name:sale_product_identification.model_sale_order
msgid "Sales Order"
msgstr ""
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/sale_order.py:0
msgid ""
"The following identifications are required for partner, please verify.\n"
" %(identifications)s"
msgstr ""
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/sale_order.py:0
msgid ""
"The following identifications require verification, please validate before continuing:\n"
" %(identifications)s"
msgstr ""
#. module: sale_product_identification
#. odoo-python
#: code:addons/sale_product_identification/models/product_template.py:0
msgid ""
"There are repeated categories in the identifications configuration, the quantities are shown below.\n"
"%(categories)s"
msgstr ""

View File

@@ -0,0 +1,7 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import product_template_id_category
from . import product_template
from . import res_partner_id_number
from . import sale_order

View File

@@ -0,0 +1,42 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class ProductTemplate(models.Model):
_inherit = "product.template"
required_identification = fields.Boolean(default=False)
product_tmpl_category_ids = fields.One2many(
"product.template.id_category", "product_tmpl_id"
)
@api.constrains("product_tmpl_category_ids")
def _check_product_tmpl_category_ids(self):
ProductTemplateIdCategory = self.env["product.template.id_category"]
for product_templ in self:
category_ids = ProductTemplateIdCategory.sudo()._read_group(
[
("product_tmpl_id", "=", product_templ.id),
],
["category_id"],
["category_id:count"],
order="category_id:count DESC",
)
if category_ids and category_ids[0][1] > 1:
category_ids = list(filter(lambda x: x[1] > 1, category_ids))
raise ValidationError(
_(
"There are repeated categories in the identifications "
"configuration, the quantities are shown below.\n%(categories)s"
)
% {
"categories": "\n".join(
f"{category[0].name}:\u2009{category[1]}"
for category in category_ids
)
}
)

View File

@@ -0,0 +1,20 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ProductTemplateIdcategory(models.Model):
_name = "product.template.id_category"
_description = "Product Template Identification Category"
product_tmpl_id = fields.Many2one("product.template")
category_id = fields.Many2one("res.partner.id_category")
is_mandatory = fields.Boolean(
default=True, help="Defines whether identification is mandatory."
)
message = fields.Text(
help="Allows you to define a description of why "
"this identification is being added.\n"
"Example: Asking the customer for identification"
)

View File

@@ -0,0 +1,109 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from datetime import datetime
import pytz
from odoo import _, api, models
from odoo.exceptions import ValidationError
class ResPartnerIdNumber(models.Model):
_inherit = "res.partner.id_number"
@api.model
def message_error_identifications(
self, product_tmpl_ids, diff_identification_ids, required=False
):
"""
Define a message identifications by product
:param product_tmpl_ids: Products that have at least one identification
category defined.
:param diff_identification_ids: Required or optional identifications.
:param required: Defines whether what is being validated is required or not.
:return: A message with the identification categories per product.
"""
message = ""
for product in product_tmpl_ids:
identifications = product.mapped("product_tmpl_category_ids").filtered(
lambda x: x.is_mandatory == required
and x.category_id.id in diff_identification_ids
)
if identifications:
message += _("\n%(product)s\n%(categories)s") % {
"product": product.name,
"categories": "\n".join(
identifications.mapped(
lambda x: f"\u2003\u2022\u2009"
f"{x.category_id.name}\u2009"
f"{f'({x.message})' if x.message else ''}"
)
),
}
return message
def _identification_domain(self, **params):
"""
Build a domain to filter valid identifications
:param params: Dictionary of expected parameters
:return: list: List of tuples representing the search domain
"""
user_tz = self.env.user.tz or self.env.context.get("tz")
user_pytz = pytz.timezone(user_tz) if user_tz else pytz.utc
now_dt = datetime.now().astimezone(user_pytz).date()
if not params.get("partner_id", False):
return []
return [
("partner_id", "=", params.get("partner_id")),
"|",
"|",
"|",
"&",
("valid_from", "=", False),
("valid_until", "=", False),
"&",
"&",
("valid_from", "!=", False),
("valid_until", "=", False),
("valid_from", "<=", now_dt),
"&",
"&",
"&",
("valid_from", "!=", False),
("valid_until", "!=", False),
("valid_from", "<=", now_dt),
("valid_until", ">=", now_dt),
"&",
"&",
("valid_from", "=", False),
("valid_until", "!=", False),
("valid_until", ">=", now_dt),
]
@api.model
def validate_identification(self, **params):
"""
Allows you to obtain the difference between 2 recordset IDs
:param params: A dictionary of values where at least
the value 'compare_identification_ids' and 'partner_id'
must be present to validate the identifications
:return: A set of records with the difference between submitted
identifications and those of the partner
"""
identification_ids = params.get("compare_identification_ids", set())
partner_id = params.get("partner_id", False)
if not partner_id:
raise ValidationError(_("A client is required to verify identifications."))
domain = self._identification_domain(**{"partner_id": partner_id})
partner_identification_ids = self.env["res.partner.id_category"]
for id_number in self.env["res.partner.id_number"].search(domain):
id_number.category_id.validate_id_number(id_number)
partner_identification_ids |= id_number.category_id
return (
identification_ids - partner_identification_ids
if identification_ids
else partner_identification_ids
)

View File

@@ -0,0 +1,115 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import Command, _, models
from odoo.exceptions import ValidationError
class SaleOrder(models.Model):
_inherit = "sale.order"
def _diff_identification(self, compare_identification_ids):
diff_identification = self.env["res.partner.id_number"].validate_identification(
**{
"compare_identification_ids": compare_identification_ids,
"partner_id": self.partner_id.id,
}
)
return diff_identification
def _message_error_identifications(self, diff_identification=False, required=False):
"""
Display messages created using the message_error_identifications
method in order of sale.
:param diff_identification: Required or optional identifications.
:return: Message by order
"""
message_error = ""
for order in self:
message_error = _("%(order)s%(message)s") % {
"order": f"\n{order.name}" if len(self) > 1 else "",
"message": self.env[
"res.partner.id_number"
].message_error_identifications(
order.mapped("order_line.product_template_id").filtered(
lambda product: product.required_identification
and product.product_tmpl_category_ids
),
diff_identification,
required,
),
}
return message_error
def _action_generate_confirm_identification(self, message):
view = self.env.ref("sale_product_identification.confirm_identification_view")
return {
"name": _("Confirm identification"),
"type": "ir.actions.act_window",
"view_mode": "form",
"res_model": "confirm.identification",
"views": [(view.id, "form")],
"view_id": view.id,
"target": "new",
"context": {
"default_order_ids": [Command.set(self.ids)],
"default_message": message,
},
}
def _get_domain_identifications(self, is_mandatory=False):
return [
("product_tmpl_id", "in", self.order_line.product_template_id.ids),
("product_tmpl_id.required_identification", "=", True),
("is_mandatory", "=", is_mandatory),
]
def _validate_opt_identification(self):
self.ensure_one()
products_opt_identification_ids = (
self.env["product.template.id_category"]
.search(self._get_domain_identifications())
.mapped("category_id")
)
if products_opt_identification_ids:
message = _(
"The following identifications require verification, "
"please validate before continuing:\n %(identifications)s"
) % {
"identifications": self._message_error_identifications(
products_opt_identification_ids.ids
)
}
return self._action_generate_confirm_identification(message)
return True
def _validate_identification(self):
self.ensure_one()
products_identification_ids = (
self.env["product.template.id_category"]
.search(self._get_domain_identifications(True))
.mapped("category_id")
)
if products_identification_ids:
diff_identification = self._diff_identification(products_identification_ids)
if diff_identification:
message = _(
"The following identifications are required for "
"partner, please verify.\n %(identifications)s"
) % {
"identifications": self._message_error_identifications(
diff_identification.ids, True
)
}
raise ValidationError(message)
def action_confirm(self):
for order in self:
order._validate_identification()
if not self.env.context.get("not_verify_optional_identification", False):
res = self[:1]._validate_opt_identification()
if res is not True:
return res
return super().action_confirm()

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1,2 @@
- [Binhex](https://www.binhex.cloud/):
- Edilio Escalona Almira <e.escalona@binhex.cloud>

View File

@@ -0,0 +1,10 @@
Sometimes, when selling a product, it is necessary to require documentation (authorization, ID, etc.) from the customer to justify the sale.
Examples:
Sale of a corrosive product: A document authorizing its handling is required. To control this process, we configure a Corrosive category in the product listing.
Sale of alcohol: In this case, sales to minors are not permitted. To control this process, we configure a Minor category in the product listing.
This module allows you to control these processes through identifications in the product listing.
Please note that this functionality depends on the partner_identification module. For detailed setup and configuration,
please refer to that addon's documentation.

View File

@@ -0,0 +1,34 @@
Add identifications to the product
-----------------------------
1. Go to Sales -> Products -> Products
2. Create a new product
3. Go to the Sales tab and select the Required Identification option.
4. A tree will be enabled in which you must configure the required identification
category (ies) for the product without repeating them.
![ADD_IDENTIFICATION](../static/img/readme/ADD_IDENTIFICATION.png)
5. If you define any category as optional, a wizard will appear when
confirming the order to confirm whether the identifications are correct and continue with the process.
6. Save
Validate order with identification products
---------------------------------------------
1. Go to Sales -> Orders -> Quotations
2. Create a new order and add any products that require identification to the lines.
3. Once the order is confirmed, the selected customer's ID numbers will be validated to see if they have the categories
required for the added product(s).
4. If the partner does not have all the categories in their valid identification number (Validity Date), a message will be displayed with the missing
categories (and their message defined in the product) to validate.
![CATEGORY_REQUIRED](../static/img/readme/CATEGORIES_REQUIRED.png)
5. When validating the required categories, the optional ones are validated, for which a wizard will be displayed to
confirm.
![CONFIRM_IDENTIFICATION](../static/img/readme/CONFIRM_IDENTIFICATION.png)
6. If the customer has all the correct identifications, then the order confirmation follows its normal flow.

View File

@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_confirm_identification_manager,sale_product_identification.confirm_identification,model_confirm_identification,base.group_user,1,1,1,1
access_product_template_id_category,sale_product_identification.product_template_id_category,model_product_template_id_category,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_confirm_identification_manager sale_product_identification.confirm_identification model_confirm_identification base.group_user 1 1 1 1
3 access_product_template_id_category sale_product_identification.product_template_id_category model_product_template_id_category base.group_user 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,500 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>README.rst</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document">
<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="sale-product-identification-numbers">
<h1>Sale Product Identification Numbers</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:c5dc989deb9296162ec75e40eded8c208b8f3f4923214175a78f16c6d17b9fef
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/sale-workflow/tree/18.0/sale_product_identification"><img alt="OCA/sale-workflow" src="https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/sale-workflow-18-0/sale-workflow-18-0-sale_product_identification"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Sometimes, when selling a product, it is necessary to require
documentation (authorization, ID, etc.) from the customer to justify the
sale. Examples: Sale of a corrosive product: A document authorizing its
handling is required. To control this process, we configure a Corrosive
category in the product listing.</p>
<p>Sale of alcohol: In this case, sales to minors are not permitted. To
control this process, we configure a Minor category in the product
listing.</p>
<p>This module allows you to control these processes through
identifications in the product listing.</p>
<p>Please note that this functionality depends on the
partner_identification module. For detailed setup and configuration,
please refer to that addons documentation.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a><ul>
<li><a class="reference internal" href="#add-identifications-to-the-product" id="toc-entry-2">Add identifications to the product</a></li>
<li><a class="reference internal" href="#validate-order-with-identification-products" id="toc-entry-3">Validate order with identification products</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-7">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-8">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
<div class="section" id="add-identifications-to-the-product">
<h3><a class="toc-backref" href="#toc-entry-2">Add identifications to the product</a></h3>
<ol class="arabic">
<li><p class="first">Go to Sales -&gt; Products -&gt; Products</p>
</li>
<li><p class="first">Create a new product</p>
</li>
<li><p class="first">Go to the Sales tab and select the Required Identification option.</p>
</li>
<li><p class="first">A tree will be enabled in which you must configure the required
identification category (ies) for the product without repeating them.</p>
<p><img alt="ADD_IDENTIFICATION" src="https://raw.githubusercontent.com/OCA/sale-workflow/18.0/sale_product_identification/static/img/readme/ADD_IDENTIFICATION.png" /></p>
</li>
<li><p class="first">If you define any category as optional, a wizard will appear when
confirming the order to confirm whether the identifications are
correct and continue with the process.</p>
</li>
<li><p class="first">Save</p>
</li>
</ol>
</div>
<div class="section" id="validate-order-with-identification-products">
<h3><a class="toc-backref" href="#toc-entry-3">Validate order with identification products</a></h3>
<ol class="arabic">
<li><p class="first">Go to Sales -&gt; Orders -&gt; Quotations</p>
</li>
<li><p class="first">Create a new order and add any products that require identification
to the lines.</p>
</li>
<li><p class="first">Once the order is confirmed, the selected customers ID numbers will
be validated to see if they have the categories required for the
added product(s).</p>
</li>
<li><p class="first">If the partner does not have all the categories in their valid
identification number (Validity Date), a message will be displayed
with the missing categories (and their message defined in the
product) to validate.</p>
<p><img alt="CATEGORY_REQUIRED" src="https://raw.githubusercontent.com/OCA/sale-workflow/18.0/sale_product_identification/static/img/readme/CATEGORIES_REQUIRED.png" /></p>
</li>
<li><p class="first">When validating the required categories, the optional ones are
validated, for which a wizard will be displayed to confirm.</p>
<p><img alt="CONFIRM_IDENTIFICATION" src="https://raw.githubusercontent.com/OCA/sale-workflow/18.0/sale_product_identification/static/img/readme/CONFIRM_IDENTIFICATION.png" /></p>
</li>
<li><p class="first">If the customer has all the correct identifications, then the order
confirmation follows its normal flow.</p>
</li>
</ol>
</div>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-4">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/sale-workflow/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_product_identification%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-5">Credits</a></h2>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-6">Authors</a></h3>
<ul class="simple">
<li>Binhex</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-7">Contributors</a></h3>
<ul class="simple">
<li><a class="reference external" href="https://www.binhex.cloud/">Binhex</a>:<ul>
<li>Edilio Escalona Almira <a class="reference external" href="mailto:e.escalona&#64;binhex.cloud">e.escalona&#64;binhex.cloud</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h3>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/sale-workflow/tree/18.0/sale_product_identification">OCA/sale-workflow</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View File

@@ -0,0 +1,5 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import sale_product_identification_common
from . import test_sale_product_identification

View File

@@ -0,0 +1,133 @@
# Copyright 2017 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import Command
from odoo.addons.base.tests.common import BaseCommon
class TestSaleOrderIdentificationCommon(BaseCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
ResPartnerIdCategory = cls.env["res.partner.id_category"]
SaleOrder = cls.env["sale.order"]
ProductTemplate = cls.env["product.template"]
ResPartner = cls.env["res.partner"]
cls.category_corrosive = ResPartnerIdCategory.create(
{"code": "id_corrosive", "name": "Corrosive"}
)
cls.category_bilogical = ResPartnerIdCategory.create(
{"code": "id_bilogical", "name": "Bilogical"}
)
cls.category_explosive = ResPartnerIdCategory.create(
{"code": "id_explosive", "name": "Explosive"}
)
cls.partner_id = ResPartner.create(
{
"name": "Partner Test",
"id_numbers": [
Command.create(
{
"name": "Bad ID",
"category_id": cls.category_corrosive.id,
}
)
],
}
)
cls.product_tmpl_with_iden = ProductTemplate.create(
{
"name": "Product Test Iden",
"required_identification": True,
"product_tmpl_category_ids": [
Command.create(
{
"category_id": cls.category_corrosive.id,
"is_mandatory": True,
}
),
Command.create(
{
"category_id": cls.category_bilogical.id,
"is_mandatory": True,
}
),
],
}
)
cls.product_with_iden = cls.product_tmpl_with_iden.product_variant_ids[:1]
cls.product_tmpl_with_iden_opt = ProductTemplate.create(
{
"name": "Product Test optional",
"required_identification": True,
"product_tmpl_category_ids": [
Command.create(
{
"category_id": cls.category_corrosive.id,
"is_mandatory": False,
}
),
Command.create(
{
"category_id": cls.category_bilogical.id,
"is_mandatory": True,
}
),
Command.create(
{
"category_id": cls.category_explosive.id,
"is_mandatory": False,
}
),
],
}
)
cls.product_with_iden_opt = cls.product_tmpl_with_iden_opt.product_variant_ids[
:1
]
cls.product_tmpl_without_iden = ProductTemplate.create(
{
"name": "Product Test",
}
)
cls.product_without_iden = cls.product_tmpl_without_iden.product_variant_ids[:1]
cls.order = SaleOrder.create(
{
"name": "Sale Order Test",
"partner_id": cls.partner_id.id,
"order_line": [
Command.create(
{
"name": cls.product_tmpl_with_iden.name,
"product_id": cls.product_with_iden.id,
"product_uom_qty": 5,
}
),
Command.create(
{
"name": cls.product_tmpl_without_iden.name,
"product_id": cls.product_without_iden.id,
"product_uom_qty": 6,
}
),
],
}
)
cls.order_opt = SaleOrder.create(
{
"name": "Sale Order Test Optional",
"partner_id": cls.partner_id.id,
"order_line": [
Command.create(
{
"name": cls.product_tmpl_with_iden_opt.name,
"product_id": cls.product_with_iden_opt.id,
"product_uom_qty": 5,
}
)
],
}
)

View File

@@ -0,0 +1,78 @@
# Copyright 2017 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import Command
from odoo.exceptions import ValidationError
from odoo.tests import Form
from .sale_product_identification_common import TestSaleOrderIdentificationCommon
class TestSaleOrderIdentification(TestSaleOrderIdentificationCommon):
def test_action_confirm(self):
with self.assertRaises(ValidationError):
self.order.action_confirm()
self.assertEqual(self.order.state, "draft")
self.partner_id.id_numbers = [
Command.create(
{
"name": "Bad X ID",
"category_id": self.category_bilogical.id,
}
)
]
self.order.action_confirm()
self.assertEqual(self.order.state, "sale")
self.order_opt.action_confirm()
self.assertEqual(self.order_opt.state, "draft")
message = self.order_opt._message_error_identifications(
[self.category_bilogical.id], True
)
self.assertIn(self.product_tmpl_with_iden_opt.name, message)
self.assertIn(self.category_bilogical.name, message)
ConfirmIdentification = self.env["confirm.identification"].with_context(
**{
"default_order_ids": [Command.set(self.order_opt.ids)],
"default_message": message,
}
)
wizard_confirm_identification_form = Form(ConfirmIdentification)
wizard_confirm_identification = wizard_confirm_identification_form.save()
wizard_confirm_identification.confirm_identification()
self.assertEqual(self.order_opt.state, "sale")
def test_validate_identification_id_number(self):
ResPartnerIdNumber = self.env["res.partner.id_number"]
with self.assertRaises(ValidationError):
ResPartnerIdNumber.validate_identification(
**{
"partner_id": False,
}
)
category_ids = ResPartnerIdNumber.validate_identification(
**{
"partner_id": self.partner_id.id,
}
)
self.assertEqual(len(category_ids), 1)
self.assertEqual(category_ids[:1], self.category_corrosive)
self.partner_id.id_numbers = [
Command.create(
{
"name": "Test category",
"category_id": self.category_bilogical.id,
}
)
]
category_ids = ResPartnerIdNumber.validate_identification(
**{
"partner_id": self.partner_id.id,
"compare_identification_ids": self.category_corrosive
+ self.category_bilogical,
}
)
self.assertEqual(len(category_ids), 0)

View File

@@ -0,0 +1,26 @@
<odoo>
<record id="product_template_only_form_view" model="ir.ui.view">
<field name="name">product.template.form.view.sale.product.identification
</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_only_form_view" />
<field name="arch" type="xml">
<xpath expr="//page[@name='sales']" position="inside">
<group string="Identification">
<field name="required_identification" />
<field
name="product_tmpl_category_ids"
invisible="not required_identification"
nolabel="1"
>
<list editable="bottom">
<field name="category_id" />
<field name="is_mandatory" />
<field name="message" />
</list>
</field>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,4 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import wizard_confirm_identification

View File

@@ -0,0 +1,17 @@
# Copyright 2025 Binhex <https://www.binhex.cloud>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ConfirmIdentification(models.TransientModel):
_name = "confirm.identification"
_description = "Confirm Identification Wizard"
order_ids = fields.Many2many("sale.order", string="Orders")
message = fields.Text()
def confirm_identification(self):
ctx = dict(self.env.context, not_verify_optional_identification=True)
self.order_ids.with_context(**ctx).action_confirm()

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="confirm_identification_view" model="ir.ui.view">
<field name="name">Confirm identifications</field>
<field name="model">confirm.identification</field>
<field name="arch" type="xml">
<form string="Confirm identifications">
<group>
<field name="order_ids" invisible="1" />
<field name="message" readonly="1" nolabel="1" />
</group>
<footer>
<button
string="Confirm"
name="confirm_identification"
type="object"
default_focus="1"
class="btn-primary"
data-hotkey="q"
/>
<button
string="Cancel"
class="btn-secondary"
special="cancel"
data-hotkey="x"
/>
</footer>
</form>
</field>
</record>
</odoo>