diff --git a/default_warehouse_from_sale_team/README.rst b/default_warehouse_from_sale_team/README.rst new file mode 100644 index 00000000000..de999598427 --- /dev/null +++ b/default_warehouse_from_sale_team/README.rst @@ -0,0 +1,123 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License: AGPL-3 + +================================= +Default Warehouse from Sales Team +================================= + +Usage +===== + +Add a field `Default Warehouse` in sales team. + +Create sort kind of api where any object with field called warehouse_id will +take the default value from the sales team field +this taking into account the team defined in `Default Sale Teams` field defined +in the res.user model. + +To improve, consistency and usability we add the following features: + +- If you try to add a default sales team where the user doesn't belongs wil throw + you an error message: Can not set default team is do not belongs to sale team +- Add an automated action/server action to update the user default sales team + every time that a sales teams by: + + - add user's default sale team if empty. + - remove default sale team from user if not longer in sale team. + - dummy write to update m2m in users to make new feature for filtering + records. + +Currently this default warehouse feature applies for: +- Sale orders +- Transfers (``stock.picking``) +- Operation type (``stock.picking.type``) + +Also add a new feature for Permissions and Security: Taking advantage of the +default_warehouse field in the sales team model now we can filter the +records (picking type and picking model) to only show those records that match +with the user sale teams default_warehouse. To accomplish this I: + +- add new groups to manage the records access by user: + + * Default Warehouse / Limited access to transfers (filtered by sales team) + * Default Warehouse / Limited access to operation types (filtered by sales team) + * Default Warehouse / Access to all operation types + * Default Warehouse / Access to all transfers + +- add new m2m field in the res.user model used in the new ir.rules. + this onw is showed as a readonly field (only informative) to know + the teams were the sale user belongs. +- add new ir.rule records, one for each default warehouse group. This + one will let us to only show the records for the current user sale + teams default_warehouse or to do not take into account the sale teams + and show all the records to the user. + +To add more models use it simple do this: + +1. Inherit the class that you want to set the field warehouse_id:: + + class SomeClass(models.Model): + _name = 'some.class' + _inherit = ['some.class', 'default.warehouse'] + warehouse_id = fields.Many2one('stock.warehouse', help='Warehouse were' + 'this object will belong to') + +2. Create two ir.rule to filter stock.picking and stock.picking.type taking + into account the current user warehouses. When a user is part of warehouse + teams will be able to access only the records related to that warehouses:: + + + Limited access to model (filtered by sales teams) + + + [('warehouse_id', 'in', [team.default_warehouse.id for team in user.sale_team_ids if team.default_warehouse])] + + + Access to all model + + + [(1, '=', 1)] + + +3. Don't forget depends of this module adding it to the list into `__openerp__.py`:: + + The default value from this field will be the warehouse setted in the section. + If the user is not related to a sales team or not warehouse setted on the + section the default warehouse will be set using the default behavior of the + system which is assign the main warehouse. + +Bug Tracker +=========== + +Bugs are tracked on +`GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and +welcomed feedback +`here `_ + +Credits +======= + +**Contributors** + +* Nhomar Hernández (Planner/Developer) +* Katherine Zaoral (Planner/Developer) +* Luis González (Developer) +* Rolando Duarte (Developer) + +Maintainer +========== + +.. image:: https://s3.amazonaws.com/s3.vauxoo.com/description_logo.png + :alt: Vauxoo + :target: https://www.vauxoo.com + :width: 200 + +This module is maintained by Vauxoo. + +To contribute to this module, please visit https://www.vauxoo.com. diff --git a/default_warehouse_from_sale_team/__init__.py b/default_warehouse_from_sale_team/__init__.py new file mode 100644 index 00000000000..cc6b6354ad8 --- /dev/null +++ b/default_warehouse_from_sale_team/__init__.py @@ -0,0 +1,2 @@ +from . import models +from .hooks import post_init_hook diff --git a/default_warehouse_from_sale_team/__manifest__.py b/default_warehouse_from_sale_team/__manifest__.py new file mode 100644 index 00000000000..ab6e40484c0 --- /dev/null +++ b/default_warehouse_from_sale_team/__manifest__.py @@ -0,0 +1,35 @@ +{ + "name": "Default Warehouse from Sales Team", + "summary": """Adding a field for the default user warehouse and modifying + the global default method for assign in any related field the correct + default warehouse. + """, + "author": "Vauxoo", + "website": "http://www.vauxoo.com", + "license": "LGPL-3", + "category": "Inventory/Inventory", + "version": "18.0.1.0.0", + "depends": [ + "sale_stock", + "purchase_requisition", + ], + "test": [], + "data": [ + "views/crm_team_views.xml", + "views/ir_sequence_views.xml", + "views/res_users_views.xml", + "views/stock_picking_type_views.xml", + "views/stock_picking_views.xml", + "security/res_groups_security.xml", + "security/ir_rule_security.xml", + ], + "demo": [ + "demo/account_journal_demo.xml", + "demo/stock_warehouse_demo.xml", + "demo/crm_team_demo.xml", + ], + "post_init_hook": "post_init_hook", + "installable": True, + "application": False, + "auto_install": False, +} diff --git a/default_warehouse_from_sale_team/demo/account_journal_demo.xml b/default_warehouse_from_sale_team/demo/account_journal_demo.xml new file mode 100644 index 00000000000..f268f586ca0 --- /dev/null +++ b/default_warehouse_from_sale_team/demo/account_journal_demo.xml @@ -0,0 +1,10 @@ + + + + + Journal Default Warehouse + JDW + general + + + diff --git a/default_warehouse_from_sale_team/demo/crm_team_demo.xml b/default_warehouse_from_sale_team/demo/crm_team_demo.xml new file mode 100644 index 00000000000..3cc9c7487a5 --- /dev/null +++ b/default_warehouse_from_sale_team/demo/crm_team_demo.xml @@ -0,0 +1,10 @@ + + + + + Default Team + + + + + diff --git a/default_warehouse_from_sale_team/demo/stock_warehouse_demo.xml b/default_warehouse_from_sale_team/demo/stock_warehouse_demo.xml new file mode 100644 index 00000000000..d19567b6de5 --- /dev/null +++ b/default_warehouse_from_sale_team/demo/stock_warehouse_demo.xml @@ -0,0 +1,9 @@ + + + + + Team Default Warehouse + TDW + + + diff --git a/default_warehouse_from_sale_team/hooks.py b/default_warehouse_from_sale_team/hooks.py new file mode 100644 index 00000000000..80bf56f9b35 --- /dev/null +++ b/default_warehouse_from_sale_team/hooks.py @@ -0,0 +1,25 @@ +import logging + +_logger = logging.getLogger(__name__) + + +def post_init_hook(env): + fill_user_allowed_salesteams(env) + + +def fill_user_allowed_salesteams(env): + """Fill allowed sales teams in users that are already members of any team. + + Since this module implements a feature to restrict which sales teams a user may be a member of, users that already + belong to any team are configured to be allowed for those teams, to avoid inconsistencies between allowed and + already-configured memberships. In other words, if a user already belongs to a team, it most likely means they + should be allowed to belong to it, so allowance is granted. + """ + teams_per_user = env["crm.team.member"]._read_group( + domain=[], + groupby=["user_id"], + aggregates=["crm_team_id:recordset"], + ) + for user, teams in teams_per_user: + user.sale_team_ids |= teams + _logger.info("Field 'Allowed sales Teams' has been set to %d users.", len(teams_per_user)) diff --git a/default_warehouse_from_sale_team/i18n/es.po b/default_warehouse_from_sale_team/i18n/es.po new file mode 100644 index 00000000000..8f3f9651924 --- /dev/null +++ b/default_warehouse_from_sale_team/i18n/es.po @@ -0,0 +1,257 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * default_warehouse_from_sale_team +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-03-09 01:08+0000\n" +"PO-Revision-Date: 2023-03-09 01:08+0000\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: default_warehouse_from_sale_team +#: model:res.groups,comment:default_warehouse_from_sale_team.group_limited_default_warehouse_journal +msgid "" +"\n" +" View only the Journal whose warehouse match the configured for the user as default warehouse\n" +" " +msgstr "" +"Ver sólo el Diario cuyo almacén coincide con los almacenes configurados por " +"el usuario como almacén por defecto" + +#. module: default_warehouse_from_sale_team +#: model:res.groups,comment:default_warehouse_from_sale_team.group_limited_default_warehouse_spt +msgid "" +"\n" +" View only the operation types which warehouses match with the user sales teams default warehouses\n" +" " +msgstr "" +"Ver solamente los tipos de operación que corresponden con el almacén por " +"defecto de los equipos de venta del usuario" + +#. module: default_warehouse_from_sale_team +#: model:res.groups,comment:default_warehouse_from_sale_team.group_limited_default_warehouse_sp +msgid "" +"\n" +" View only the transfers which warehouses match with the user sales teams default warehouses\n" +" " +msgstr "" +"Ver solamente las transferencias que corresponden con el almacén por defecto" +" de los equipos de venta del usuario" + +#. module: default_warehouse_from_sale_team +#: model:res.groups,name:default_warehouse_from_sale_team.group_manager_default_journal +msgid "Access to all Journals" +msgstr "Acceso a todos los Diarios" + +#. module: default_warehouse_from_sale_team +#: model:res.groups,name:default_warehouse_from_sale_team.group_manager_default_warehouse_spt +msgid "Access to all operation types" +msgstr "Acceso a todos los tipos de operación" + +#. module: default_warehouse_from_sale_team +#: model:res.groups,name:default_warehouse_from_sale_team.group_manager_default_warehouse_sp +msgid "Access to all transfers" +msgstr "Acceso a todas las transferencias" + +#. module: default_warehouse_from_sale_team +#: model:res.groups,comment:default_warehouse_from_sale_team.group_manager_default_journal +msgid "Allow the user to see all journals, regardless of sales teams" +msgstr "" +"Permite al usuario poder ver todos los diarios, sin importar su equipo de " +"ventas" + +#. module: default_warehouse_from_sale_team +#: model:res.groups,comment:default_warehouse_from_sale_team.group_manager_default_warehouse_spt +msgid "Allow the user to see all operation types, regardless of sales teams" +msgstr "" +"Permite al usuario poder ver todas las transferencias, sin importar sus " +"equipos de ventas" + +#. module: default_warehouse_from_sale_team +#: model:res.groups,comment:default_warehouse_from_sale_team.group_manager_default_warehouse_sp +msgid "Allow the user to see all transfers, regardless of sales teams" +msgstr "" +"Permite al usuario poder ver todos los tippos de operación, sin importar sus" +" equipos de ventas" + +#. module: default_warehouse_from_sale_team +#: model:ir.model.fields,field_description:default_warehouse_from_sale_team.field_res_users__sale_team_ids +msgid "Allowed sales teams" +msgstr "Equipos de ventas permitidos" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_default_picking_type_mixin +msgid "Default Operation Type" +msgstr "Tipo de Operación por defecto" + +#. module: default_warehouse_from_sale_team +#: model:crm.team,name:default_warehouse_from_sale_team.section_sales_default_team +msgid "Default Team" +msgstr "Equipo por defecto" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_default_warehouse_mixin +#: model:ir.model.fields,field_description:default_warehouse_from_sale_team.field_crm_team__default_warehouse_id +#: model:ir.module.category,name:default_warehouse_from_sale_team.default_warehouse_module +msgid "Default Warehouse" +msgstr "Almacén por defecto" + +#. module: default_warehouse_from_sale_team +#: model:ir.model.fields,help:default_warehouse_from_sale_team.field_crm_team__default_warehouse_id +msgid "" +"In this field can be defined a default warehouse for the related users to " +"the sales team." +msgstr "" +"En este campo se puede definir el almacén por defecto para los usuarios " +"relacionados al equipo de ventas." + +#. module: default_warehouse_from_sale_team +#: model:ir.model.fields,help:default_warehouse_from_sale_team.field_crm_team__journal_stock_id +msgid "" +"It indicates the journal to be used when a move line is created with the " +"warehouse of this sales team" +msgstr "" +"Indica el diario que se utilizará cuando se cree la línea de movimiento con " +"el almacén de este equipo de ventas" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_account_journal +msgid "Journal" +msgstr "Diario" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_account_move +msgid "Journal Entry" +msgstr "Asiento contable" + +#. module: default_warehouse_from_sale_team +#: model:ir.model.fields,field_description:default_warehouse_from_sale_team.field_crm_team__journal_stock_id +msgid "Journal stock valuation" +msgstr "Diario para valoración de existencias" + +#. module: default_warehouse_from_sale_team +#: model:ir.model.fields,field_description:default_warehouse_from_sale_team.field_crm_team__journal_team_ids +#: model_terms:ir.ui.view,arch_db:default_warehouse_from_sale_team.crm_team_view_form +msgid "Journal's sales teams" +msgstr "Diarios de equipos de ventas" + +#. module: default_warehouse_from_sale_team +#: model:res.groups,name:default_warehouse_from_sale_team.group_limited_default_warehouse_journal +msgid "Limited access to Journals (filtered by sales team)" +msgstr "Acceso limitado a diarios(Filtrados por equipo de ventas)" + +#. module: default_warehouse_from_sale_team +#: model:res.groups,name:default_warehouse_from_sale_team.group_limited_default_warehouse_spt +msgid "Limited access to operation types (filtered by sales team)" +msgstr "" +"Acceso limitado a los tipos de operación (filtrados por equipo de ventas)" + +#. module: default_warehouse_from_sale_team +#: model:res.groups,name:default_warehouse_from_sale_team.group_limited_default_warehouse_sp +msgid "Limited access to transfers (filtered by sales team)" +msgstr "Acceso limitado a las transferencias (filtrados por equipo de ventas)" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_stock_picking_type +msgid "Picking Type" +msgstr "Tipo de albarán" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_procurement_group +msgid "Procurement Group" +msgstr "Grupo de abastecimiento" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_purchase_order +msgid "Purchase Order" +msgstr "Pedido de compra" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_purchase_requisition +msgid "Purchase Requisition" +msgstr "Solicitud de compra" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_sale_order +msgid "Sales Order" +msgstr "Pedido de venta" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línea de pedido de venta" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_crm_team +#: model:ir.model.fields,field_description:default_warehouse_from_sale_team.field_account_journal__section_id +#: model:ir.model.fields,field_description:default_warehouse_from_sale_team.field_ir_sequence__section_id +#: model_terms:ir.ui.view,arch_db:default_warehouse_from_sale_team.view_users_form +msgid "Sales Team" +msgstr "Equipo de ventas" + +#. module: default_warehouse_from_sale_team +#: model:ir.model.fields,field_description:default_warehouse_from_sale_team.field_stock_warehouse__sale_team_ids +msgid "Sales teams" +msgstr "Equipos de ventas" + +#. module: default_warehouse_from_sale_team +#: model:ir.model.fields,help:default_warehouse_from_sale_team.field_res_users__sale_team_ids +msgid "" +"Sales teams allowed for the user, to give access to warehouses and their " +"related documents." +msgstr "" +"Equipos de venta permitidos para el usuario, para dar acceso a almacenes y " +"sus documentos relacionados." + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_ir_sequence +msgid "Sequence" +msgstr "Secuencia" + +#. module: default_warehouse_from_sale_team +#: model:ir.model.fields,help:default_warehouse_from_sale_team.field_crm_team__journal_team_ids +msgid "Specify what journals a member of this sales team can see." +msgstr "" +"Especifica cuales diarios puede ver un miembro de este equipo de ventas" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_stock_move +msgid "Stock Move" +msgstr "Movimiento de existencias" + +#. module: default_warehouse_from_sale_team +#. odoo-python +#: code:addons/default_warehouse_from_sale_team/models/res_users.py:0 +#, python-format +msgid "The chosen team (%s) is not in the allowed sales teams for this user" +msgstr "" +"El equipo de venta seleccionado (%s) no está entre los equipos permitidos " +"para este usuario" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_stock_picking +msgid "Transfer" +msgstr "Albarán" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_res_users +msgid "User" +msgstr "Usuario" + +#. module: default_warehouse_from_sale_team +#: model:ir.model,name:default_warehouse_from_sale_team.model_stock_warehouse +#: model:ir.model.fields,field_description:default_warehouse_from_sale_team.field_stock_picking__warehouse_id +msgid "Warehouse" +msgstr "Almacén" + +#. module: default_warehouse_from_sale_team +#: model_terms:ir.ui.view,arch_db:default_warehouse_from_sale_team.crm_team_view_form +msgid "Warehouse settings for the sales team" +msgstr "Configuración de almacén para el equipo de ventas" diff --git a/default_warehouse_from_sale_team/models/__init__.py b/default_warehouse_from_sale_team/models/__init__.py new file mode 100644 index 00000000000..396589a3c32 --- /dev/null +++ b/default_warehouse_from_sale_team/models/__init__.py @@ -0,0 +1,16 @@ +from . import account_journal +from . import account_move +from . import crm_team +from . import default_warehouse_mixin +from . import default_picking_type_mixin +from . import ir_sequence +from . import procurement_group +from . import purchase_order +from . import purchase_requisition +from . import res_users +from . import sale_order +from . import sale_order_line +from . import stock_move +from . import stock_picking +from . import stock_picking_type +from . import stock_warehouse diff --git a/default_warehouse_from_sale_team/models/account_journal.py b/default_warehouse_from_sale_team/models/account_journal.py new file mode 100644 index 00000000000..24955cfea80 --- /dev/null +++ b/default_warehouse_from_sale_team/models/account_journal.py @@ -0,0 +1,10 @@ +from odoo import fields, models + + +class AccountJournal(models.Model): + _inherit = "account.journal" + + section_id = fields.Many2one("crm.team", string="Sales Team") + # Avoid access errors when user has access to a journal item but + # not to its journal, e.g. when reconciling bank statements. + display_name = fields.Char(compute="_compute_display_name", compute_sudo=True) diff --git a/default_warehouse_from_sale_team/models/account_move.py b/default_warehouse_from_sale_team/models/account_move.py new file mode 100644 index 00000000000..fa0f6257b37 --- /dev/null +++ b/default_warehouse_from_sale_team/models/account_move.py @@ -0,0 +1,31 @@ +from odoo import api, models + + +class AccountMove(models.Model): + _inherit = "account.move" + + def _search_default_journal(self): + """If a team is provided and it has a sales journal set, take it as 1st alternative""" + journal = super()._search_default_journal() + team = ( + self.env.context.get("salesteam") + # If the team_id value (ID) is in the cache on a existing account.move record, it must be + # converted to a record from the cached value to avoid triggering the field's compute + # method when it has not yet been computed. + or (self and self._fields["team_id"].convert_to_record(self._cache.get("team_id"), self)) + or self.env.user.sale_team_id + ) + journal_on_team = team._get_default_journal([journal.type or "general"]) + return journal_on_team or journal + + @api.onchange("team_id") + def _onchange_team_id(self): + if self.state != "draft" or not self.is_invoice(include_receipts=True) or not self.team_id.journal_team_ids: + return {} + self = self.with_company(self.company_id) + default_journal_ctx = { + "default_move_type": self.move_type, + "default_currency_id": self.currency_id.id, + } + default_journal = self.with_context(**default_journal_ctx)._search_default_journal() + self.journal_id = default_journal diff --git a/default_warehouse_from_sale_team/models/crm_team.py b/default_warehouse_from_sale_team/models/crm_team.py new file mode 100644 index 00000000000..51bc96968d3 --- /dev/null +++ b/default_warehouse_from_sale_team/models/crm_team.py @@ -0,0 +1,44 @@ +from odoo import fields, models + + +class CrmTeam(models.Model): + _inherit = "crm.team" + + default_warehouse_id = fields.Many2one( + "stock.warehouse", + help="In this field can be defined a default warehouse for the related users to the sales team.", + ) + journal_team_ids = fields.One2many( + "account.journal", + "section_id", + string="Journal's sales teams", + help="Specify what journals a member of this sales team can see.", + ) + journal_stock_id = fields.Many2one( + "account.journal", + "Journal stock valuation", + help="It indicates the journal to be used when a move line is created with the warehouse of this sales team", + ) + + def _get_default_team_id(self, user_id=None, domain=None): + """When specified by context, ensure the sales team is taken from the current user""" + if user_id is not None and self.env.context.get("keep_current_user_salesteam") and self.env.user.sale_team_id: + user_id = self.env.uid + return super()._get_default_team_id(user_id=user_id, domain=domain) + + def _get_default_journal(self, journal_types): + journal = self.env["account.journal"] + if not self or set(journal_types) == {"general"}: + return journal + company = self.env["res.company"].browse(self.env.context.get("default_company_id")) or self.env.company + company_currency = company.currency_id + currency_ids = [self.env.context.get("default_currency_id") or company_currency.id] + if currency_ids == company_currency.ids: + currency_ids.append(False) + domain = [ + ("section_id", "=", self.id), + ("company_id", "=", company.id), + ("type", "in", journal_types), + ] + domain_currency = domain + [("currency_id", "in", currency_ids)] + return journal.search(domain_currency, limit=1) or journal.search(domain, limit=1) diff --git a/default_warehouse_from_sale_team/models/default_picking_type_mixin.py b/default_warehouse_from_sale_team/models/default_picking_type_mixin.py new file mode 100644 index 00000000000..fcc9269b75c --- /dev/null +++ b/default_warehouse_from_sale_team/models/default_picking_type_mixin.py @@ -0,0 +1,31 @@ +from odoo import api, models + + +class DefaultPickingTypeMixin(models.AbstractModel): + _name = "default.picking.type.mixin" + _inherit = "default.warehouse.mixin" + _description = "Default Operation Type" + + @api.model + def default_get(self, fields_list): + """Fill a default picking type + + Get by default the picking type depending + on the sales team warehouse in models where picking type is required, + e.g. purchase order or purchase requisition. + returning the first picking type that match with the warehouse + of the sales team. + """ + res = super().default_get(fields_list) + default_warehouse = self.env.user._get_default_warehouse_id() + if default_warehouse: + pick_type = self.env["stock.picking.type"].search( + [ + ("code", "=", "incoming"), + ("warehouse_id", "=", default_warehouse.id), + ], + limit=1, + ) + res.update({"picking_type_id": pick_type.id}) + + return res diff --git a/default_warehouse_from_sale_team/models/default_warehouse_mixin.py b/default_warehouse_from_sale_team/models/default_warehouse_mixin.py new file mode 100644 index 00000000000..b6f904cc9dc --- /dev/null +++ b/default_warehouse_from_sale_team/models/default_warehouse_mixin.py @@ -0,0 +1,80 @@ +from odoo import api, models + + +class DefaultWarehouseMixin(models.AbstractModel): + """If you inherit from this model and add a field called warehouse_id into + the model, then the default value for such model will be the one + set into the sales team. + + Make sure to put this mixin at the top of inheritance (before the base model), e.g. + + _inherit = ["default.warehouse.mixin", "sale.order"] + """ + + _name = "default.warehouse.mixin" + _description = "Default Warehouse" + + @api.model + def default_get(self, fields_list): + """Force that if model has a field called warehouse_id the default + value is the one set in the user's sales team. + """ + defaults = super().default_get(fields_list) + default_warehouse = self.env.user._get_default_warehouse_id() + if not default_warehouse: + return defaults + warehouse_fields = ( + self.env["ir.model.fields"] + .sudo() + .search( + [ + ("model", "=", self._name), + ("relation", "=", "stock.warehouse"), + ("ttype", "=", "many2one"), + ("name", "in", fields_list), + ] + ) + ) + defaults.update({fld.name: default_warehouse.id for fld in warehouse_fields}) + return defaults + + @api.model_create_multi + def create(self, vals_list): + """Pass sales team by context so it's taken into account when computing sequence name""" + if not vals_list: + return super().create(vals_list) + result = self.browse() + salesteams = self._get_salesteam_from_vals_list(vals_list) + for salesteam, salesteam_vals_list in salesteams.items(): + self = self.with_context(sequence_salesteam_id=salesteam.id) + result |= super().create(salesteam_vals_list) + return result + + def _get_salesteam_from_vals(self, vals): + """Determine sales team from creation values""" + if "name" in vals: + # Already has a name, so salesteam won't be used anyway + return self.env["crm.team"] + warehouse = ( + self.env["stock.warehouse"].browse(vals.get("warehouse_id")) + or self.env["stock.picking.type"].browse(vals.get("picking_type_id")).warehouse_id + ) + salesteam = warehouse.sale_team_ids[:1] + return salesteam + + def _get_salesteam_from_vals_list(self, vals_list): + """Determine sales team from the list of creation values""" + salesteams = {} + for vals in vals_list: + salesteam = self._get_salesteam_from_vals(vals) + salesteams.setdefault(salesteam, []).append(vals) + return salesteams + + def onchange(self, values, field_name, field_onchange): + """Add an extra context to prevent current user's salesteam from being overwritten by onchanges + + This is useful in e.g. sale orders, where sales person's sales team has priority over current + user's sales team. + """ + self_team_ctx = self.with_context(keep_current_user_salesteam=True) + return super(DefaultWarehouseMixin, self_team_ctx).onchange(values, field_name, field_onchange) diff --git a/default_warehouse_from_sale_team/models/ir_sequence.py b/default_warehouse_from_sale_team/models/ir_sequence.py new file mode 100644 index 00000000000..f6406a7c7f6 --- /dev/null +++ b/default_warehouse_from_sale_team/models/ir_sequence.py @@ -0,0 +1,28 @@ +from odoo import api, fields, models + + +class IrSequence(models.Model): + _inherit = "ir.sequence" + + section_id = fields.Many2one("crm.team", string="Sales Team") + + @api.model + def next_by_code(self, sequence_code, sequence_date=None): + """If a sales team is provided by context, give priority to sequences having it set""" + if "sequence_salesteam_id" not in self.env.context: + return super().next_by_code(sequence_code, sequence_date) + salesteam_id = self.env.context["sequence_salesteam_id"] + self.check_access("read") + company_id = self.env.company.id + sequence = self.search( + [ + ("code", "=", sequence_code), + ("company_id", "in", [company_id, False]), + ("section_id", "in", [salesteam_id, False]), + ], + limit=1, + order="section_id, company_id", + ) + if sequence: + return sequence._next(sequence_date=sequence_date) + return super().next_by_code(sequence_code, sequence_date) diff --git a/default_warehouse_from_sale_team/models/procurement_group.py b/default_warehouse_from_sale_team/models/procurement_group.py new file mode 100644 index 00000000000..731c029fa1b --- /dev/null +++ b/default_warehouse_from_sale_team/models/procurement_group.py @@ -0,0 +1,12 @@ +from odoo import api, models + + +class ProcurementGroup(models.Model): + _inherit = "procurement.group" + + @api.model + def _search_rule(self, route_ids, packaging_id, product_id, warehouse_id, domain): + """Grant access to provided warehouse if it's not allowed to the current user""" + if warehouse_id and warehouse_id._access_unallowed_current_user_salesteams(): + warehouse_id = warehouse_id.sudo() + return super()._search_rule(route_ids, packaging_id, product_id, warehouse_id, domain) diff --git a/default_warehouse_from_sale_team/models/purchase_order.py b/default_warehouse_from_sale_team/models/purchase_order.py new file mode 100644 index 00000000000..8817d6281d6 --- /dev/null +++ b/default_warehouse_from_sale_team/models/purchase_order.py @@ -0,0 +1,6 @@ +from odoo import models + + +class PurchaseOrder(models.Model): + _name = "purchase.order" + _inherit = ["default.picking.type.mixin", "purchase.order"] diff --git a/default_warehouse_from_sale_team/models/purchase_requisition.py b/default_warehouse_from_sale_team/models/purchase_requisition.py new file mode 100644 index 00000000000..6014ba1920c --- /dev/null +++ b/default_warehouse_from_sale_team/models/purchase_requisition.py @@ -0,0 +1,15 @@ +from odoo import models + + +class PurchaseRequisition(models.Model): + _name = "purchase.requisition" + _inherit = ["purchase.requisition", "default.picking.type.mixin"] + + def action_in_progress(self): + """Pass team by context so sequence number is computed + + Since requisition name is not assigned when it's created but when confirmed, + we need to pass the sales team by context also here. + """ + self_team = self.with_context(sequence_salesteam_id=self.warehouse_id.sale_team_ids[:1].id) + return super(PurchaseRequisition, self_team).action_in_progress() diff --git a/default_warehouse_from_sale_team/models/res_users.py b/default_warehouse_from_sale_team/models/res_users.py new file mode 100644 index 00000000000..9e621106e3f --- /dev/null +++ b/default_warehouse_from_sale_team/models/res_users.py @@ -0,0 +1,38 @@ +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class ResUsers(models.Model): + _inherit = "res.users" + + sale_team_ids = fields.Many2many( + "crm.team", + "sale_member_rel", + "member_id", + "section_id", + string="Allowed sales teams", + help="Sales teams allowed for the user, to give access to warehouses and their related documents.", + ) + + @api.constrains("sale_team_id") + def _check_section_id(self): + """Can only set the Default Sales team if the user has that team on its + Sales Teams (`sale_team_ids`). + """ + user_wrong_team = self.filtered(lambda u: u.sale_team_id - u.sale_team_ids) + if user_wrong_team: + raise ValidationError( + self.env._( + "The chosen team (%s) is not in the allowed sales teams for this user", + user_wrong_team[0].sale_team_id.name, + ) + ) + + def _get_default_warehouse_id(self): + """Take the warehouse set in sales team as default one, otherwise fallback on user's one""" + return self.env.user.sale_team_id.default_warehouse_id or super()._get_default_warehouse_id() + + @api.model + def _get_invalidation_fields(self): + invalidation_fields = super()._get_invalidation_fields() + return invalidation_fields | {"sale_team_ids"} diff --git a/default_warehouse_from_sale_team/models/sale_order.py b/default_warehouse_from_sale_team/models/sale_order.py new file mode 100644 index 00000000000..3d43980ac7e --- /dev/null +++ b/default_warehouse_from_sale_team/models/sale_order.py @@ -0,0 +1,10 @@ +from odoo import models + + +class SaleOrder(models.Model): + _name = "sale.order" + _inherit = ["default.warehouse.mixin", "sale.order"] + + def _prepare_invoice(self): + """Add team by context so it's taken into account when choosing default journal""" + return super(SaleOrder, self.with_context(salesteam=self.team_id))._prepare_invoice() diff --git a/default_warehouse_from_sale_team/models/sale_order_line.py b/default_warehouse_from_sale_team/models/sale_order_line.py new file mode 100644 index 00000000000..d9bb87c7ba6 --- /dev/null +++ b/default_warehouse_from_sale_team/models/sale_order_line.py @@ -0,0 +1,12 @@ +from odoo import models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + def _action_launch_stock_rule(self, previous_product_uom_qty=False): + """Allow to run procurement in warehouses not allowed on current user's sales teams""" + warehouses = self.route_id.rule_ids.warehouse_id + if warehouses._access_unallowed_current_user_salesteams(): + self = self.sudo() + return super()._action_launch_stock_rule(previous_product_uom_qty) diff --git a/default_warehouse_from_sale_team/models/stock_move.py b/default_warehouse_from_sale_team/models/stock_move.py new file mode 100644 index 00000000000..29a68a46d59 --- /dev/null +++ b/default_warehouse_from_sale_team/models/stock_move.py @@ -0,0 +1,15 @@ +from odoo import models + + +class StockMove(models.Model): + _inherit = "stock.move" + + def _get_accounting_data_for_valuation(self): + journal_id, acc_src, acc_dest, acc_valuation = super()._get_accounting_data_for_valuation() + warehouse_id = self.picking_id.picking_type_id.warehouse_id or self.warehouse_id + sale_team = self.env["crm.team"].search([("default_warehouse_id", "=", warehouse_id.id)], limit=1) + + if sale_team.journal_stock_id: + journal_id = sale_team.journal_stock_id.id + + return journal_id, acc_src, acc_dest, acc_valuation diff --git a/default_warehouse_from_sale_team/models/stock_picking.py b/default_warehouse_from_sale_team/models/stock_picking.py new file mode 100644 index 00000000000..2f56b256b06 --- /dev/null +++ b/default_warehouse_from_sale_team/models/stock_picking.py @@ -0,0 +1,23 @@ +from odoo import api, fields, models + + +class StockPicking(models.Model): + _name = "stock.picking" + _inherit = ["default.warehouse.mixin", "stock.picking"] + + warehouse_id = fields.Many2one(related="picking_type_id.warehouse_id") + is_editable = fields.Boolean(compute="_compute_is_editable") + + @api.depends_context("uid") + def _compute_is_editable(self): + # Using intersection operator to keep original env + editable_records = self & self._filtered_access("write") + editable_records.is_editable = True + (self - editable_records).is_editable = False + + @api.depends("is_editable") + def _compute_show_check_availability(self): + res = super()._compute_show_check_availability() + non_editable = self - self.filtered("is_editable") + non_editable.show_check_availability = False + return res diff --git a/default_warehouse_from_sale_team/models/stock_picking_type.py b/default_warehouse_from_sale_team/models/stock_picking_type.py new file mode 100644 index 00000000000..8a4daed7c68 --- /dev/null +++ b/default_warehouse_from_sale_team/models/stock_picking_type.py @@ -0,0 +1,6 @@ +from odoo import models + + +class StockPickingType(models.Model): + _name = "stock.picking.type" + _inherit = ["default.warehouse.mixin", "stock.picking.type"] diff --git a/default_warehouse_from_sale_team/models/stock_warehouse.py b/default_warehouse_from_sale_team/models/stock_warehouse.py new file mode 100644 index 00000000000..2af3dcc0798 --- /dev/null +++ b/default_warehouse_from_sale_team/models/stock_warehouse.py @@ -0,0 +1,32 @@ +from odoo import api, fields, models + + +class StockWarehouse(models.Model): + _inherit = "stock.warehouse" + + sale_team_ids = fields.One2many( + "crm.team", + "default_warehouse_id", + string="Sales teams", + ) + + def _access_unallowed_current_user_salesteams(self): + """Check if access will be denied because warehouse is not allowed on current user's sales teams + + In some cases, it's required to grant access to warehouses not allowed for the current user, e.g. + when an inventory rule is triggered that involves other warehouses. + """ + warehouses = self.exists() + allowed_rules = self._get_salesteam_record_rules() + failed_rules = self.env["ir.rule"]._get_failing(warehouses, mode="read") + return ( + warehouses + and not self.env.su + and warehouses.check_access("read", raise_exception=False) + and failed_rules + and not failed_rules - allowed_rules + ) + + @api.model + def _get_salesteam_record_rules(self): + return self.env.ref("default_warehouse_from_sale_team.rule_default_warehouse_wh") diff --git a/default_warehouse_from_sale_team/security/ir_rule_security.xml b/default_warehouse_from_sale_team/security/ir_rule_security.xml new file mode 100644 index 00000000000..eca463da650 --- /dev/null +++ b/default_warehouse_from_sale_team/security/ir_rule_security.xml @@ -0,0 +1,71 @@ + + + + + + Limited access to picking types (filtered by sale teams) + + [ + ('warehouse_id', 'in', user.sale_team_ids.default_warehouse_id.ids), + ] + + + + Access to all picking types + + [(1, '=', 1)] + + + + + + Limited access to pickings (filtered by sale teams) + + + [ + ('warehouse_id', 'in', user.sale_team_ids.default_warehouse_id.ids), + ] + + + + Access to all pickings + + [(1, '=', 1)] + + + + + + Limited access to journal (filtered by sale teams) + + [ + '|', + ('id', 'in', user.sale_team_ids.journal_team_ids.ids), + ('section_id', '=', False ), + ] + + + + Access to all journal + + [(1, '=', 1)] + + + + + + Limited access to warehouse (filtered by sale teams) + + [ + ('id', 'in', user.sale_team_ids.default_warehouse_id.ids), + ] + + + + Access to all warehouse + + [(1, '=', 1)] + + + + diff --git a/default_warehouse_from_sale_team/security/res_groups_security.xml b/default_warehouse_from_sale_team/security/res_groups_security.xml new file mode 100644 index 00000000000..25602978d9e --- /dev/null +++ b/default_warehouse_from_sale_team/security/res_groups_security.xml @@ -0,0 +1,48 @@ + + + + + Default Warehouse + + + + + Limited access to operation types (filtered by sales team) + + View only the operation types which warehouses match with the user sales teams default warehouses + + + + + Access to all operation types + Allow the user to see all operation types, regardless of sales teams + + + + + + Limited access to transfers (filtered by sales team) + + View only the transfers which warehouses match with the user sales teams default warehouses + + + + + Access to all transfers + Allow the user to see all transfers, regardless of sales teams + + + + + Limited access to Journals (filtered by sales team) + + View only the Journal whose warehouse match the configured for the user as default warehouse + + + + + Access to all Journals + Allow the user to see all journals, regardless of sales teams + + + diff --git a/default_warehouse_from_sale_team/static/description/icon.png b/default_warehouse_from_sale_team/static/description/icon.png new file mode 100644 index 00000000000..58dd9665bc2 Binary files /dev/null and b/default_warehouse_from_sale_team/static/description/icon.png differ diff --git a/default_warehouse_from_sale_team/static/description/index.html b/default_warehouse_from_sale_team/static/description/index.html new file mode 100644 index 00000000000..ad91c6c07b7 --- /dev/null +++ b/default_warehouse_from_sale_team/static/description/index.html @@ -0,0 +1,259 @@ + + + + + +
+ + License: AGPL-3 + +
+

+ Sales Team Warehouse +

+
+

+ Usage +

+

+ Add a field + + default_warehouse + + in sales team. +

+

+ Create sort kind of api where any object with field called warehouse_id will +take the default value from the sales team field +this taking into account the team defined in + + Default Sale Teams + + field defined +in the res.user model. +

+

+ To improve, consistency and usability we add the next features: +

+
    +
  • +

    + If you try to add a default sale team where the user do not belongs wil throw +you an error message: Can not set default team is do not belongs to sale team +

    +
  • +
  • +

    + Add an automated action/server action to update the user default sales team +every time that a sales teams by: +

    +
    +
      +
    • + add user's default sale team if empty. +
    • +
    • + remove default sale team from user if not longer in sale team. +
    • +
    • + dummy write to update m2m in users to make new feature for filtering +records. +
    • +
    +
    +
  • +
+

+ Currently this default warehouse feature applies for sale.order, +stock.picking.type and stock.picking. +

+

+ Also add a new feature for Permissions and Security: Taking advantage of the +default_warehouse field in the sales team model now we can filter the +records (picking type and picking model) to only show those records that match +with the user sale teams default_warehouse. To accomplish this I: +

+
    +
  • + add new groups to manage the records access by user: +
      +
    • + Default Warehouse / Limited access to transfers (filtered by sale teams) +
    • +
    • + Default Warehouse / Limited access to stock pickings (filtered by sale teams) +
    • +
    • + Default Warehouse / Access to all operation types +
    • +
    • + Default Warehouse / Access to all transfers +
    • +
    +
  • +
  • + add new m2m field in the res.user model used in the new ir.rules. +this onw is showed as a readonly field (only informative) to know +the teams were the sale user belongs. +
  • +
  • + add new ir.rule records, one for each default warehouse group. This +one will let us to only show the records for the current user sale +teams default_warehouse or to do not take into account the sale teams +and show all the records to the user. +
  • +
+

+ To add more models use it simple do this: +

+
    +
  1. +

    + inherit the class that you want to set the field warehouse_id: +

    +
    +class SomeClass(models.Model):
    +    _name = 'some.class'
    +    _inherit = ['some.class', 'default.warehouse']
    +    warehouse_id = fields.Many2one('stock.warehouse', help='Warehouse were'
    +    'this object will belong to')
    +
    +
  2. +
+

+ 2. Create two ir.rule to filter stock.picking and stock.picking.type taking +into account the current user warehouses. When a user is part of warehouse +teams will be able to access only the records related to that warehouses: +

+
+<record id="rule_group_model" model="ir.rule">
+    <field name="name">Limited access to model (filtered by sales teams)</field>
+    <field name="model_id" search="[('model','=','model')]" model="ir.model"/>
+    <field name="groups" eval"[Command.set([ref('xml_id_group')])]/>
+    <field name="domain_force">[('warehouse_id', 'in', [team.default_warehouse.id for team in user.sale_team_ids if team.default_warehouse])]</field>
+</record>
+<record id="rule_group_model_2" model="ir.rule">
+    <field name="name">Access to all model</field>
+    <field name="model_id" search="[('model','=','model')]" model="ir.model"/>
+    <field name="groups" eval"[Command.set([ref('xml_id_group')])]/>
+    <field name="domain_force">[(1, '=', 1)]</field>
+</record>
+
+
    +
  1. + Don't forget depends of this module adding it to the list into + + __openerp__.py + +
  2. +
+

+ The default value from this field will be the warehouse setted in the section +If the user is not related to a sales team or not warehouse setted on the +section the default warehouse will be set using the default behavior of the +system which is assign the main warehouse. +

+
+
+

+ Bug Tracker +

+

+ Bugs are tracked on + + GitHub Issues + + . +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and +welcomed feedback + + here + +

+
+
+

+ Credits +

+

+ + Contributors + +

+ +
+
+
+
+
+
+

+ Do you need help? +

+

+ Let's offer you the best services! +

+

+ Contact us by our official channels. +

+
+ +
+
+
+
+ + + + +
+
+
+
+ + \ No newline at end of file diff --git a/default_warehouse_from_sale_team/static/src/img/icon.png b/default_warehouse_from_sale_team/static/src/img/icon.png new file mode 100644 index 00000000000..58dd9665bc2 Binary files /dev/null and b/default_warehouse_from_sale_team/static/src/img/icon.png differ diff --git a/default_warehouse_from_sale_team/tests/__init__.py b/default_warehouse_from_sale_team/tests/__init__.py new file mode 100644 index 00000000000..f3b6fe06079 --- /dev/null +++ b/default_warehouse_from_sale_team/tests/__init__.py @@ -0,0 +1 @@ +from . import test_default_warehouse diff --git a/default_warehouse_from_sale_team/tests/test_default_warehouse.py b/default_warehouse_from_sale_team/tests/test_default_warehouse.py new file mode 100644 index 00000000000..eb3df4cd5a9 --- /dev/null +++ b/default_warehouse_from_sale_team/tests/test_default_warehouse.py @@ -0,0 +1,125 @@ +from odoo import Command +from odoo.tests import Form, TransactionCase, tagged + + +@tagged("post_install", "-at_install") +class TestSalesTeamDefaultWarehouse(TransactionCase): + def setUp(self): + super().setUp() + self.company = self.env.ref("base.main_company") + self.partner = self.env.ref("base.res_partner_12") + self.purchase_obj = self.env["purchase.order"] + self.purchase_requisition_obj = self.env["purchase.requisition"] + self.res_user_obj = self.env["res.users"] + self.pick_type_obj = self.env["stock.picking.type"] + + # user with team + self.demo_user = self.env.ref("base.user_demo") + self.test_wh = self.env.ref("default_warehouse_from_sale_team.stock_warehouse_default_team") + self.sales_team = self.env.ref("sales_team.crm_team_1") + self.sales_team.write({"default_warehouse_id": self.test_wh.id}) + self.demo_user.write( + { + "sale_team_id": self.sales_team.id, + "sale_team_ids": [Command.link(self.sales_team.id)], + "company_id": self.company.id, + } + ) + + # Products + self.product = self.env.ref("product.product_product_11") + self.product_uom = self.env.ref("uom.product_uom_unit") + + def create_sale_order(self, partner=None, **line_kwargs): + if partner is None: + partner = self.partner + sale_order = Form(self.env["sale.order"]) + sale_order.partner_id = partner + sale_order = sale_order.save() + self.create_so_line(sale_order, **line_kwargs) + return sale_order + + def create_so_line(self, sale_order, product=None, quantity=1, price=100): + if product is None: + product = self.product + with Form(sale_order) as so: + with so.order_line.new() as line: + line.product_id = product + line.product_uom_qty = quantity + line.price_unit = price + + def test_01_default_picking_type_purchase_requisition(self): + """Validate the picking type by default from sale team warehouse in + purchase requisition + """ + values = self.purchase_requisition_obj.with_user(self.demo_user).default_get([]) + pick_type_id = self.pick_type_obj.browse(values.get("picking_type_id")) + + purchase_id = self.purchase_requisition_obj.with_user(self.demo_user).create({}) + + self.assertEqual( + purchase_id.picking_type_id, + pick_type_id, + "Default picking type is not the set on the sales team related to de user.", + ) + + def test_02_default_picking_type_purchase(self): + """Validate picking type by default from sale team warehouse in + purchase order""" + purchase_values = {"partner_id": self.partner.id, "name": "Purchase with sale team"} + + values = self.purchase_obj.with_user(self.demo_user).default_get([]) + pick_type_id = self.pick_type_obj.browse(values.get("picking_type_id")) + + purchase_id = self.purchase_obj.with_user(self.demo_user).create(purchase_values) + + self.assertEqual( + purchase_id.picking_type_id, + pick_type_id, + "Default picking type is not the set on the sales team related to de user.", + ) + + def test_03_proper_behavior(self): + """1.- Testing that the Demo User has not sales team set. + 2.-Testing that the sales order created by Demo User has + the main warehouse assigned by default. + 3.- Writing a sales team to Demo user and a default warehouse for + the sales team related to the Demo User. + 4.- Testing that the sales order created by Demo User after + the sales team assignation has the default warehouse set + on the user sales team. + """ + main_wh = self.env.ref("stock.warehouse0") + # user without team + user_without_team = self.demo_user.copy({"sale_team_id": False}) + self.uid = user_without_team + sale_order1 = self.create_sale_order() + self.uid = self.demo_user + sale_order2 = self.create_sale_order() + self.assertEqual(sale_order1.warehouse_id, main_wh, "Default warehouse is not the main warehouse.") + self.assertEqual( + sale_order2.warehouse_id, + self.test_wh, + "Default warehouse is not the warehouse set on the sales team related to de user.", + ) + + def test_04_warehouse_team_sale_policy(self): + """Verify that the policy is created with the daily defined in the + sales team for that warehouse + """ + account_id = self.env["account.account"].search([], limit=1) + self.product.categ_id.write( + { + "property_valuation": "real_time", + "property_stock_account_input_categ_id": account_id.id, + "property_stock_account_output_categ_id": account_id.id, + } + ) + sale = self.create_sale_order() + + # Confirm sale order + sale.sudo().action_confirm() + pick = sale.picking_ids + pick.action_assign() + pick.move_ids.write({"quantity": 1, "picked": True}) + pick.button_validate() diff --git a/default_warehouse_from_sale_team/views/crm_team_views.xml b/default_warehouse_from_sale_team/views/crm_team_views.xml new file mode 100644 index 00000000000..6451a390c94 --- /dev/null +++ b/default_warehouse_from_sale_team/views/crm_team_views.xml @@ -0,0 +1,26 @@ + + + + + crm.team.form.inherit.default.warehouse + crm.team + + + + + + + + + + + + + + + + + + + + diff --git a/default_warehouse_from_sale_team/views/ir_sequence_views.xml b/default_warehouse_from_sale_team/views/ir_sequence_views.xml new file mode 100644 index 00000000000..40a8f71c8df --- /dev/null +++ b/default_warehouse_from_sale_team/views/ir_sequence_views.xml @@ -0,0 +1,15 @@ + + + + + ir.sequence.form.inherit.default.warehouse + ir.sequence + + + + + + + + + diff --git a/default_warehouse_from_sale_team/views/res_users_views.xml b/default_warehouse_from_sale_team/views/res_users_views.xml new file mode 100644 index 00000000000..b50a1d9c4d3 --- /dev/null +++ b/default_warehouse_from_sale_team/views/res_users_views.xml @@ -0,0 +1,20 @@ + + + + + res.users.form.inherit.default.warehouse + res.users + + + + + + + + + + + + + + diff --git a/default_warehouse_from_sale_team/views/stock_picking_type_views.xml b/default_warehouse_from_sale_team/views/stock_picking_type_views.xml new file mode 100644 index 00000000000..ea0cd493ab2 --- /dev/null +++ b/default_warehouse_from_sale_team/views/stock_picking_type_views.xml @@ -0,0 +1,15 @@ + + + + + stock.picking.type.form.inherit.default.warehouse + stock.picking.type + + + + 1 + + + + + diff --git a/default_warehouse_from_sale_team/views/stock_picking_views.xml b/default_warehouse_from_sale_team/views/stock_picking_views.xml new file mode 100644 index 00000000000..481717fcac5 --- /dev/null +++ b/default_warehouse_from_sale_team/views/stock_picking_views.xml @@ -0,0 +1,27 @@ + + + + + stock.picking.form.inherit.default.warehouse + stock.picking + + + + + + + + + + + + + + +