From effce68e0931bb5941828841e74b2a201105ea6c Mon Sep 17 00:00:00 2001 From: Kunjal Date: Thu, 18 Jan 2018 15:27:39 +0530 Subject: [PATCH] [FIX]: Multi Branch : Purchase, Sale stock. --- .../tests/test_journal_entries_branch.py | 12 +-- addons/base_branch_company/__manifest__.py | 1 + .../base_branch_company/demo/branch_data.xml | 24 ++++++ .../base_branch_company/demo/branch_demo.xml | 8 +- .../base_branch_company/models/res_branch.py | 20 ++++- .../views/res_branch_view.xml | 14 ++-- addons/crm/models/crm_team.py | 2 +- addons/hr_timesheet/tests/test_timesheet.py | 4 +- addons/purchase/data/purchase_order_demo.yml | 7 ++ addons/purchase/models/purchase.py | 39 ++++++++- .../report/purchase_order_templates.xml | 4 + .../report/purchase_quotation_templates.xml | 7 ++ addons/purchase/report/purchase_report.py | 3 + .../purchase/security/purchase_security.xml | 25 ++++++ addons/purchase/tests/__init__.py | 2 + addons/purchase/tests/test_purchase_branch.py | 70 ++++++++++++++++ .../purchase/tests/test_purchase_lead_time.py | 8 ++ addons/purchase/tests/test_purchase_order.py | 11 ++- .../purchase/tests/test_user_authorization.py | 22 +++++ addons/purchase/views/purchase_views.xml | 3 + .../sale/report/report_all_channels_sales.py | 3 - addons/sale/tests/test_sale_branch.py | 40 ++++++++- addons/sale_stock/models/sale_order.py | 2 +- addons/stock/data/stock_data.xml | 1 + addons/stock/data/stock_data.yml | 17 ++++ addons/stock/data/stock_demo.xml | 36 ++++++++ addons/stock/data/stock_demo.yml | 24 ++++++ addons/stock/data/stock_demo_pre.yml | 2 + .../stock/data/stock_location_demo_cpu3.yml | 4 + addons/stock/models/res_company.py | 8 +- addons/stock/models/stock_inventory.py | 7 ++ addons/stock/models/stock_location.py | 57 ++++++++++++- addons/stock/models/stock_move.py | 29 +++++++ addons/stock/models/stock_picking.py | 36 +++++++- addons/stock/models/stock_quant.py | 2 + addons/stock/models/stock_scrap.py | 9 +- addons/stock/models/stock_warehouse.py | 16 ++++ addons/stock/report/report_deliveryslip.xml | 4 + addons/stock/report/report_stockinventory.xml | 4 + .../report/report_stockpicking_operations.xml | 4 + addons/stock/security/stock_security.xml | 44 ++++++++++ addons/stock/tests/__init__.py | 1 + addons/stock/tests/common2.py | 2 + addons/stock/tests/test_move2.py | 4 +- addons/stock/tests/test_stock_branch.py | 84 +++++++++++++++++++ addons/stock/views/stock_inventory_views.xml | 1 + addons/stock/views/stock_location_views.xml | 1 + addons/stock/views/stock_move_views.xml | 8 ++ addons/stock/views/stock_picking_views.xml | 1 + addons/stock/views/stock_quant_views.xml | 1 + addons/stock/views/stock_scrap_views.xml | 1 + addons/stock/views/stock_warehouse_views.xml | 1 + flectra/addons/base/res/res_users.py | 1 + 53 files changed, 698 insertions(+), 43 deletions(-) create mode 100644 addons/base_branch_company/demo/branch_data.xml create mode 100644 addons/purchase/tests/test_purchase_branch.py create mode 100644 addons/purchase/tests/test_user_authorization.py create mode 100644 addons/stock/tests/test_stock_branch.py diff --git a/addons/account/tests/test_journal_entries_branch.py b/addons/account/tests/test_journal_entries_branch.py index f73de1e1..7809d40b 100644 --- a/addons/account/tests/test_journal_entries_branch.py +++ b/addons/account/tests/test_journal_entries_branch.py @@ -5,27 +5,29 @@ from . import test_account_branch class TestJournalEntryBranch(test_account_branch.TestAccountBranch): def test_journal_entries_branch(self): - journal_ids = self.model_account_journal.search([('code', '=', 'MISC')], - limit=1) + self.sale_journal_id = \ + self.env['account.journal'].search([('type', '=', 'sale')])[0] + self.account_id = self.env['account.account'].search( + [('internal_type', '=', 'receivable')])[0] move_vals = self.env['account.move'].default_get([]) lines = [ (0, 0, { 'name': 'Test', - 'account_id': self.asset_account.id, + 'account_id': self.account_id.id, 'debit': 0, 'credit': 100, 'branch_id': self.branch_1.id, }), (0, 0, { 'name': 'Test', - 'account_id': self.asset_account.id, + 'account_id': self.account_id.id, 'debit': 100, 'credit': 0, 'branch_id': self.branch_1.id, }) ] move_vals.update({ - 'journal_id': journal_ids and journal_ids.id, + 'journal_id': self.sale_journal_id.id, 'line_ids': lines, }) move = self.env['account.move'].sudo(self.user_id.id).create(move_vals) diff --git a/addons/base_branch_company/__manifest__.py b/addons/base_branch_company/__manifest__.py index 4774f7fa..4e4ba66e 100644 --- a/addons/base_branch_company/__manifest__.py +++ b/addons/base_branch_company/__manifest__.py @@ -23,6 +23,7 @@ Main Features 'website': '', 'depends': ['base'], 'data': [ + 'demo/branch_data.xml', 'security/branch_security.xml', 'security/ir.model.access.csv', 'views/res_branch_view.xml', diff --git a/addons/base_branch_company/demo/branch_data.xml b/addons/base_branch_company/demo/branch_data.xml new file mode 100644 index 00000000..449982bf --- /dev/null +++ b/addons/base_branch_company/demo/branch_data.xml @@ -0,0 +1,24 @@ + + + + + + your company + YC + + 80 Broad St + Scranton + 10004 + + + +1 485 123 8989 + + + + + + + + + + diff --git a/addons/base_branch_company/demo/branch_demo.xml b/addons/base_branch_company/demo/branch_demo.xml index e9d580fc..e685b500 100644 --- a/addons/base_branch_company/demo/branch_demo.xml +++ b/addons/base_branch_company/demo/branch_demo.xml @@ -1,9 +1,8 @@ - - + New York NY @@ -34,10 +33,7 @@ (372) 587-2335 - - - - + diff --git a/addons/base_branch_company/models/res_branch.py b/addons/base_branch_company/models/res_branch.py index 31335a74..943bd50d 100644 --- a/addons/base_branch_company/models/res_branch.py +++ b/addons/base_branch_company/models/res_branch.py @@ -88,18 +88,34 @@ class Users(models.Model): self.env['res.users'].browse(user).company_id.branch_id return branch_id + @api.model + def _get_branch(self): + return self.env.user.default_branch_id + @api.model def _get_default_branch(self): return self.branch_default_get(self._uid) + def _branches_count(self): + return self.env['res.branch'].sudo().search_count([]) + branch_ids = fields.Many2many('res.branch', 'res_branch_users_rel', 'user_id', 'branch_id', - 'Branches') + 'Branches', default=_get_branch, + domain="[('company_id','=',company_id)]") default_branch_id = fields.Many2one('res.branch', 'Default branch', - default=_get_default_branch, + default=_get_branch, domain="[('company_id','=',company_id)" "]") + branches_count = fields.Integer(compute='_compute_branches_count', + string="Number of Companies", + default=_branches_count) + @api.multi + def _compute_branches_count(self): + branches_count = self._branches_count() + for user in self: + user.branches_count = branches_count diff --git a/addons/base_branch_company/views/res_branch_view.xml b/addons/base_branch_company/views/res_branch_view.xml index 0f792d3a..9c508303 100644 --- a/addons/base_branch_company/views/res_branch_view.xml +++ b/addons/base_branch_company/views/res_branch_view.xml @@ -87,16 +87,12 @@ res.users - + - - - - - - - - + + + + diff --git a/addons/crm/models/crm_team.py b/addons/crm/models/crm_team.py index b5793902..164cca04 100644 --- a/addons/crm/models/crm_team.py +++ b/addons/crm/models/crm_team.py @@ -78,7 +78,7 @@ class Team(models.Model): values['alias_defaults'] = defaults = safe_eval(self.alias_defaults or "{}") defaults['type'] = 'lead' if has_group_use_lead and self.use_leads else 'opportunity' defaults['team_id'] = self.id - defaults['branch_id'] = self.branch_id and self.branch_id.id + defaults['branch_id'] = self.branch_id.id return values @api.onchange('use_leads', 'use_opportunities') diff --git a/addons/hr_timesheet/tests/test_timesheet.py b/addons/hr_timesheet/tests/test_timesheet.py index a8a25f4d..159d69e4 100644 --- a/addons/hr_timesheet/tests/test_timesheet.py +++ b/addons/hr_timesheet/tests/test_timesheet.py @@ -16,13 +16,13 @@ class TestTimesheet(TransactionCase): }) self.task1 = self.env['project.task'].create({ 'name': 'Task One', - 'priority': '0', + 'priority': 'l', 'kanban_state': 'normal', 'project_id': self.project_customer.id, }) self.task2 = self.env['project.task'].create({ 'name': 'Task Two', - 'priority': '1', + 'priority': 'm', 'kanban_state': 'done', 'project_id': self.project_customer.id, }) diff --git a/addons/purchase/data/purchase_order_demo.yml b/addons/purchase/data/purchase_order_demo.yml index 8b7ed7c4..290610ba 100644 --- a/addons/purchase/data/purchase_order_demo.yml +++ b/addons/purchase/data/purchase_order_demo.yml @@ -12,6 +12,7 @@ product_qty: 15.0 product_uom: product.product_uom_unit date_planned: !eval time.strftime('%Y-%m-%d') + branch_id: base_branch_company.data_branch_2 - !record {model: purchase.order.line, id: purchase_order_line_2}: order_id: purchase_order_1 @@ -21,6 +22,7 @@ product_qty: 5.0 product_uom: product.product_uom_unit date_planned: !eval time.strftime('%Y-%m-%d') + branch_id: base_branch_company.data_branch_2 - !record {model: purchase.order.line, id: purchase_order_line_3}: order_id: purchase_order_1 @@ -30,11 +32,13 @@ product_qty: 4.0 product_uom: product.product_uom_unit date_planned: !eval time.strftime('%Y-%m-%d') + branch_id: base_branch_company.data_branch_2 - Creating second order - !record {model: purchase.order, id: purchase_order_2}: partner_id: base.res_partner_3 + branch_id: base_branch_company.data_branch_1 order_line: - product_id: product.product_delivery_02 name: Multimedia Speakers @@ -52,6 +56,7 @@ - !record {model: purchase.order, id: purchase_order_3}: partner_id: base.res_partner_12 + branch_id: base_branch_company.data_branch_1 order_line: - product_id: product.product_product_2 name: Support Services @@ -63,6 +68,7 @@ - !record {model: purchase.order, id: purchase_order_4}: partner_id: base.res_partner_4 + branch_id: base_branch_company.data_branch_1 order_line: - product_id: product.product_delivery_02 name: RAM SR2 (kit) @@ -86,6 +92,7 @@ - !record {model: purchase.order, id: purchase_order_5}: partner_id: base.res_partner_2 + branch_id: base_branch_company.data_branch_1 order_line: - product_id: product.product_product_22 name: Processor Core i5 2.70 Ghz diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 9260a986..911ce11f 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -7,7 +7,7 @@ from dateutil.relativedelta import relativedelta from flectra import api, fields, models, SUPERUSER_ID, _ from flectra.tools import DEFAULT_SERVER_DATETIME_FORMAT from flectra.tools.float_utils import float_is_zero, float_compare -from flectra.exceptions import UserError, AccessError +from flectra.exceptions import UserError, AccessError, ValidationError from flectra.tools.misc import formatLang from flectra.addons.base.res.res_partner import WARNING_MESSAGE, WARNING_HELP from flectra.addons import decimal_precision as dp @@ -15,7 +15,7 @@ from flectra.addons import decimal_precision as dp class PurchaseOrder(models.Model): _name = "purchase.order" - _inherit = ['mail.thread', 'mail.activity.mixin'] + _inherit = ['mail.thread', 'mail.activity.mixin', 'ir.branch.company.mixin'] _description = "Purchase Order" _order = 'date_order desc, id desc' @@ -95,6 +95,31 @@ class PurchaseOrder(models.Model): if order.picking_ids and all([x.state == 'done' for x in order.picking_ids]): order.is_shipped = True + @api.constrains('picking_type_id', 'branch_id') + def _check_branch(self): + for order in self: + warehouse_branch_id = order.picking_type_id.warehouse_id.branch_id + if order.branch_id and warehouse_branch_id != order.branch_id: + raise ValidationError( + _('Configuration Error of Branch:\n' + 'The Purchase Order Branch (%s) and ' + 'the Warehouse Branch (%s) of Deliver To must ' + 'be the same branch!') % (order.branch_id.name, + warehouse_branch_id.name) + ) + + @api.constrains('company_id', 'branch_id') + def _check_company(self): + for order in self: + if order.branch_id and order.company_id != order.branch_id.company_id: + raise ValidationError( + _('Configuration Error of Company:\n' + 'The Purchase Order Company (%s) and ' + 'the Company (%s) of Branch must ' + 'be the same company!') % (order.company_id.name, + order.branch_id.company_id.name) + ) + READONLY_STATES = { 'purchase': [('readonly', True)], 'done': [('readonly', True)], @@ -493,7 +518,11 @@ class PurchaseOrder(models.Model): result = action.read()[0] #override the context to get rid of the default filtering - result['context'] = {'type': 'in_invoice', 'default_purchase_id': self.id} + result['context'] = { + 'type': 'in_invoice', + 'default_purchase_id': self.id, + 'default_branch_id': self.branch_id.id + } if not self.invoice_ids: # Choose a default account journal in the same currency in case a new invoice is created @@ -640,6 +669,10 @@ class PurchaseOrderLine(models.Model): orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint') move_dest_ids = fields.One2many('stock.move', 'created_purchase_line_id', 'Downstream Moves') + branch_id = fields.Many2one( + related='order_id.branch_id', string='Branch', store=True, + readonly=True + ) @api.multi def _create_or_update_picking(self): diff --git a/addons/purchase/report/purchase_order_templates.xml b/addons/purchase/report/purchase_order_templates.xml index 9f1a54dd..5990449c 100644 --- a/addons/purchase/report/purchase_order_templates.xml +++ b/addons/purchase/report/purchase_order_templates.xml @@ -42,6 +42,10 @@ Order Date:

+

+ Branch: +

+

diff --git a/addons/purchase/report/purchase_quotation_templates.xml b/addons/purchase/report/purchase_quotation_templates.xml index f9eb58f4..28839a73 100644 --- a/addons/purchase/report/purchase_quotation_templates.xml +++ b/addons/purchase/report/purchase_quotation_templates.xml @@ -26,6 +26,13 @@ +
+
+ Branch: +

+

+
+

Request for Quotation

diff --git a/addons/purchase/report/purchase_report.py b/addons/purchase/report/purchase_report.py index 4b534332..4ef248ba 100644 --- a/addons/purchase/report/purchase_report.py +++ b/addons/purchase/report/purchase_report.py @@ -10,6 +10,7 @@ from flectra import api, fields, models, tools class PurchaseReport(models.Model): _name = "purchase.report" + _inherit = ['ir.branch.company.mixin'] _description = "Purchases Orders" _auto = False _order = 'date_order desc, price_total desc' @@ -64,6 +65,7 @@ class PurchaseReport(models.Model): s.partner_id as partner_id, s.create_uid as user_id, s.company_id as company_id, + s.branch_id as branch_id, s.fiscal_position_id as fiscal_position_id, l.product_id, p.product_tmpl_id, @@ -99,6 +101,7 @@ class PurchaseReport(models.Model): (cr.date_end is null or cr.date_end > coalesce(s.date_order, now()))) group by s.company_id, + s.branch_id, s.create_uid, s.partner_id, u.factor, diff --git a/addons/purchase/security/purchase_security.xml b/addons/purchase/security/purchase_security.xml index debd620a..c66be93b 100644 --- a/addons/purchase/security/purchase_security.xml +++ b/addons/purchase/security/purchase_security.xml @@ -75,5 +75,30 @@ [('order_id.message_partner_ids','child_of',[user.commercial_partner_id.id])] + + + + Purchase Order multi-branch + + + ['|', ('branch_id', '=', False), '|', ('branch_id', '=', user.default_branch_id.id), + ('branch_id', 'in', user.branch_ids.ids)] + + + + Purchase Order Line multi-branch + + + ['|', ('branch_id', '=', False), '|', ('branch_id', '=', user.default_branch_id.id), + ('branch_id', 'in', user.branch_ids.ids)] + + + + Purchase Order Analysis multi-branch + + + ['|', ('branch_id', '=', False), '|', ('branch_id', '=', user.default_branch_id.id), + ('branch_id', 'in', user.branch_ids.ids)] + diff --git a/addons/purchase/tests/__init__.py b/addons/purchase/tests/__init__.py index 25e5da8b..b342e705 100644 --- a/addons/purchase/tests/__init__.py +++ b/addons/purchase/tests/__init__.py @@ -6,3 +6,5 @@ from . import test_purchase_order from . import test_create_picking from . import test_purchase_lead_time from . import test_stockvaluation +from . import test_purchase_branch +from . import test_user_authorization diff --git a/addons/purchase/tests/test_purchase_branch.py b/addons/purchase/tests/test_purchase_branch.py new file mode 100644 index 00000000..bb74f059 --- /dev/null +++ b/addons/purchase/tests/test_purchase_branch.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# Part of Flectra. See LICENSE file for full copyright and licensing details. +import time +from flectra.addons.purchase.tests import test_purchase_order + + +class TestPurchaseBranchAuthentication(test_purchase_order.TestPurchaseOrder): + def setUp(self): + super(TestPurchaseBranchAuthentication, self).setUp() + self.user_1 = self.create_test_users(self.main_company, 'user_1', self.branch_1, [self.branch_1], + [self.purchase_user_group, self.stock_user_group]) + self.user_2 = self.create_test_users(self.child_company, 'user_2', self.branch_2, [self.branch_2], + [self.purchase_user_group, self.stock_user_group]) + product_list = [(self.product_id_1, 500), + (self.product_id_2, 1000), + (self.product_id_3, 800)] + order_lines = [] + for product_id, quantity in product_list: + line_values = { + 'name': product_id.name, + 'product_id': product_id.id, + 'price_unit': 80, + 'product_qty': quantity, + 'product_uom': product_id.uom_id.id, + 'date_planned': self.current_time, + } + order_lines.append((0, 0, line_values)) + self.purchase = self.PurchaseOrder.sudo(self.user_1).create({ + 'partner_id': self.partner_id.id, + 'company_id': self.main_company.id, + 'branch_id': self.branch_1.id, + 'order_line': order_lines, + + }) + + self.purchase.sudo(self.user_1).button_confirm() + self.create_purchase_invoice(self.partner_id, self.purchase, self.account) + + def create_test_users(self, company_id, login, branch_id, branch_ids, group_ids): + group_ids = [group_id.id for group_id in group_ids] + user_obj = \ + self.env['res.users'].with_context({'no_reset_password': True}). \ + create({ + 'company_id': company_id.id, + 'company_ids': [(4, company_id.id)], + 'default_branch_id': branch_id.id, + 'branch_ids': [(4, branch.id) for branch in branch_ids], + 'name': 'Alex Purchase User', + 'login': login, + 'password': '123', + 'email': 'alex@yourcompany.com', + 'groups_id': [(6, 0, group_ids)] + }) + return user_obj.id + + def create_purchase_invoice(self, partner_id, purchase, account): + context = { + 'active_model': 'purchase.order', + 'active_ids': purchase.ids, + 'active_id': purchase.id, + + } + invoice_vals = { + 'type': 'in_invoice', + 'partner_id': partner_id.id, + 'purchase_id': purchase.id, + 'account_id': self.partner_id.property_account_payable_id.id, + } + self.env['account.invoice'].with_context(context).create(invoice_vals) + return True diff --git a/addons/purchase/tests/test_purchase_lead_time.py b/addons/purchase/tests/test_purchase_lead_time.py index b0d8ca4f..a9f607d1 100644 --- a/addons/purchase/tests/test_purchase_lead_time.py +++ b/addons/purchase/tests/test_purchase_lead_time.py @@ -13,6 +13,9 @@ class TestPurchaseLeadTime(TestPurchase): """ To check dates, set product's Delivery Lead Time and company's Purchase Lead Time.""" + self.branch_1 = self.env.ref('base_branch_company.data_branch_1') + self.warehouse_1.write({'branch_id': self.branch_1.id}) + company = self.env.ref('base.main_company') # Update company with Purchase Lead Time @@ -49,6 +52,9 @@ class TestPurchaseLeadTime(TestPurchase): and different Delivery Lead Time.""" # Make procurement request from product_1's form view, create procurement and check it's state + self.branch_1 = self.env.ref('base_branch_company.data_branch_1') + self.warehouse_1.write({'branch_id': self.branch_1.id}) + date_planned1 = fields.Datetime.to_string(fields.datetime.now() + timedelta(days=10)) self._create_make_procurement(self.product_1, 10.00, date_planned=date_planned1) purchase1 = self.env['purchase.order.line'].search([('product_id', '=', self.product_1.id)], limit=1).order_id @@ -96,6 +102,8 @@ class TestPurchaseLeadTime(TestPurchase): and warehouse route's delay.""" # Update warehouse_1 with Incoming Shipments 3 steps + self.branch_1 = self.env.ref('base_branch_company.data_branch_1') + self.warehouse_1.write({'branch_id': self.branch_1.id}) self.warehouse_1.write({'reception_steps': 'three_steps'}) # Set delay on push rule diff --git a/addons/purchase/tests/test_purchase_order.py b/addons/purchase/tests/test_purchase_order.py index 8661a832..7b04854b 100644 --- a/addons/purchase/tests/test_purchase_order.py +++ b/addons/purchase/tests/test_purchase_order.py @@ -2,7 +2,7 @@ # Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details. from datetime import datetime - +import time from flectra.tools import DEFAULT_SERVER_DATETIME_FORMAT from flectra.addons.account.tests.account_test_classes import AccountingTestCase @@ -19,6 +19,15 @@ class TestPurchaseOrder(AccountingTestCase): self.partner_id = self.env.ref('base.res_partner_1') self.product_id_1 = self.env.ref('product.product_product_8') self.product_id_2 = self.env.ref('product.product_product_11') + self.product_id_3 = self.env.ref('product.product_product_9') + self.current_time = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + self.main_company = self.env.ref('base.main_company') + self.child_company = self.env.ref('stock.res_company_1') + self.purchase_user_group = self.env.ref('purchase.group_purchase_user') + self.stock_user_group = self.env.ref('stock.group_stock_user') + self.branch_1 = self.env.ref('base_branch_company.data_branch_1') + self.branch_2 = self.env.ref('base_branch_company.data_branch_2') + self.account = self.env.ref('l10n_generic_coa.conf_a_pay') (self.product_id_1 | self.product_id_2).write({'purchase_method': 'purchase'}) self.po_vals = { diff --git a/addons/purchase/tests/test_user_authorization.py b/addons/purchase/tests/test_user_authorization.py new file mode 100644 index 00000000..315c6890 --- /dev/null +++ b/addons/purchase/tests/test_user_authorization.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Part of Odoo,Flectra. See LICENSE file for full copyright and licensing details. +from flectra.addons.purchase.tests import \ + test_purchase_branch + + +class TestUserAuthorization(test_purchase_branch.TestPurchaseBranchAuthentication): + def test_user_authorization(self): + + purchase_ids = self.PurchaseOrder.sudo(self.user_2).search([('branch_id', '=', self.branch_1.id)]).ids + self.assertEqual(purchase_ids, [], 'User %s wrongly accessed Purchase order %s ' % (self.user_2, str(purchase_ids))) + picking_ids = self.env['stock.picking'].sudo(self.user_2).search([('id', 'in', self.purchase.picking_ids.ids)]).ids + self.assertEqual(picking_ids, [], 'User %s wrongly accessed Pickings %s ' % (self.user_2, str(picking_ids))) + invoice_ids = self.AccountInvoice.sudo(self.user_2).search([('purchase_id', '=', self.purchase.id)]).ids + self.assertEqual(invoice_ids, [], 'User %s wrongly accessed Invoices %s ' % (self.user_2, str(invoice_ids))) + + purchase_ids = self.PurchaseOrder.sudo(self.user_1).search([('branch_id', '=', self.branch_1.id)]).ids + self.assertNotEqual(purchase_ids, [], 'User %s Should have accessed to Purchase Orders %s ' % (self.user_1, str(purchase_ids))) + picking_ids = self.env['stock.picking'].sudo(self.user_1).search([('id', 'in', self.purchase.picking_ids.ids)]).ids + self.assertNotEqual(picking_ids, [], 'User %s Should have accessed to Pickings %s ' % (self.user_1, str(picking_ids))) + invoice_ids = self.AccountInvoice.sudo(self.user_1).search([('purchase_id', '=', self.purchase.id)]).ids + self.assertNotEqual(invoice_ids, [], 'User %s Should have accessed to Invoices %s ' % (self.user_1, str(invoice_ids))) diff --git a/addons/purchase/views/purchase_views.xml b/addons/purchase/views/purchase_views.xml index 6cdf8392..f8236770 100644 --- a/addons/purchase/views/purchase_views.xml +++ b/addons/purchase/views/purchase_views.xml @@ -284,6 +284,7 @@ + @@ -336,6 +337,7 @@ + @@ -388,6 +390,7 @@ + diff --git a/addons/sale/report/report_all_channels_sales.py b/addons/sale/report/report_all_channels_sales.py index aff06e38..2ae32b0e 100644 --- a/addons/sale/report/report_all_channels_sales.py +++ b/addons/sale/report/report_all_channels_sales.py @@ -6,7 +6,6 @@ from flectra import api, fields, models, tools class PosSaleReport(models.Model): _name = "report.all.channels.sales" - _inherit = ['ir.branch.company.mixin'] _description = "All sales orders grouped by sales channels" _auto = False @@ -38,7 +37,6 @@ class PosSaleReport(models.Model): so.user_id AS user_id, pt.categ_id AS categ_id, so.company_id AS company_id, - so.branch_id AS branch_id, sol.price_total / COALESCE(cr.rate, 1.0) AS price_total, so.pricelist_id AS pricelist_id, rp.country_id AS country_id, @@ -77,7 +75,6 @@ class PosSaleReport(models.Model): user_id, categ_id, company_id, - branch_id, price_total, pricelist_id, analytic_account_id, diff --git a/addons/sale/tests/test_sale_branch.py b/addons/sale/tests/test_sale_branch.py index 1e9dba62..1ca785c8 100644 --- a/addons/sale/tests/test_sale_branch.py +++ b/addons/sale/tests/test_sale_branch.py @@ -12,17 +12,49 @@ class TestSaleBranch(common.TransactionCase): self.payment_model_obj = self.env['sale.advance.payment.inv'] + IrModelData = self.env['ir.model.data'] + journal_obj = self.env['account.journal'] + account_obj = self.env['account.account'] + + user_type_id = IrModelData.xmlid_to_res_id( + 'account.data_account_type_revenue') + account_rev_id = account_obj.create( + {'code': 'X2020', 'name': 'Sales - Test Sales Account', + 'user_type_id': user_type_id, 'reconcile': True}) + user_type_id = IrModelData.xmlid_to_res_id( + 'account.data_account_type_receivable') + account_recv_id = account_obj.create( + {'code': 'X1012', 'name': 'Sales - Test Reicv Account', + 'user_type_id': user_type_id, 'reconcile': True}) + + self.apple_product = self.env.ref('product.product_product_7') + self.apple_product.write({'invoice_policy': 'order'}) + + # Add account to product + product_template_id = self.apple_product.product_tmpl_id + product_template_id.write( + {'property_account_income_id': account_rev_id}) + + self.sale_customer = self.env.ref('base.res_partner_2') + self.sale_pricelist = self.env.ref('product.list0') + + # Create Sales Journal + company_id = IrModelData.xmlid_to_res_id('base.main_company') or False + journal_obj.create( + {'name': 'Sales Journal - Test', 'code': 'STSJ', 'type': 'sale', + 'company_id': company_id}) + self.sale_customer.write({'property_account_receivable_id': account_recv_id}) + self.sale_user_group = self.env.ref('sales_team.group_sale_manager') self.account_user_group = self.env.ref('account.group_account_invoice') self.branch_1 = self.env.ref('base_branch_company.data_branch_1') self.branch_2 = self.env.ref('base_branch_company.data_branch_2') self.branch_3 = self.env.ref('base_branch_company.data_branch_3') - self.sale_customer = self.env.ref('base.res_partner_2') - self.sale_pricelist = self.env.ref('product.list0') - self.apple_product = self.env.ref('product.product_product_7') - self.apple_product.write({'invoice_policy': 'order'}) + + # self.apple_product = self.env.ref('product.product_product_7') + # self.apple_product.write({'invoice_policy': 'order'}) self.user_1 = self.create_sale_user( self.main_company, 'user_1', self.branch_1, diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py index 92a0f4d1..fe46c3b7 100644 --- a/addons/sale_stock/models/sale_order.py +++ b/addons/sale_stock/models/sale_order.py @@ -5,7 +5,7 @@ from datetime import datetime, timedelta from flectra import api, fields, models, _ from flectra.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_compare -from flectra.exceptions import UserError +from flectra.exceptions import UserError, ValidationError class SaleOrder(models.Model): diff --git a/addons/stock/data/stock_data.xml b/addons/stock/data/stock_data.xml index 4811ba1f..09945386 100644 --- a/addons/stock/data/stock_data.xml +++ b/addons/stock/data/stock_data.xml @@ -133,6 +133,7 @@ WH + diff --git a/addons/stock/data/stock_data.yml b/addons/stock/data/stock_data.yml index bf419b36..eee354ed 100644 --- a/addons/stock/data/stock_data.yml +++ b/addons/stock/data/stock_data.yml @@ -1,3 +1,9 @@ +- + !python {model: stock.warehouse, id: warehouse0}: | + warehouse = self.env['stock.warehouse'].browse(ref('warehouse0')) + branch_id = self.env['res.branch'].browse(ref('base_branch_company.data_branch_1')) + warehouse.write({'branch_id': branch_id.id}) + - !python {model: res.partner, id: base.main_partner}: | main_warehouse = self.env['stock.warehouse'].browse(ref('warehouse0')) @@ -30,4 +36,15 @@ companies = self.search([('internal_transit_location_id', '=', False)]) for company in companies: company.create_transit_location() +- + !python {model: stock.location, id: False}: | + branch_id = self.env['res.branch'].browse(ref('base_branch_company.data_branch_1')) + self.browse([ + ref('stock_location_stock'), + ref('stock_location_output'), + ref('stock_location_company'), + + ]).write({'branch_id': branch_id.id}) + + diff --git a/addons/stock/data/stock_demo.xml b/addons/stock/data/stock_demo.xml index 5db56f4e..b11d9693 100644 --- a/addons/stock/data/stock_demo.xml +++ b/addons/stock/data/stock_demo.xml @@ -167,13 +167,49 @@ My Company, Chicago + + Chicago + CH + + + Chicago Warehouse + Chic + + + + + + + + + + + + + + New York Warehouse + NY + + + + + + + + Washington Warehouse + WA + + + + + diff --git a/addons/stock/data/stock_demo.yml b/addons/stock/data/stock_demo.yml index 697bd8a2..77b74952 100644 --- a/addons/stock/data/stock_demo.yml +++ b/addons/stock/data/stock_demo.yml @@ -14,10 +14,18 @@ self.create(xml_record) #avoid the xml id and the associated resource being dropped by the orm by manually making a hit on it self._update_dummy(xml_record['model'], xml_record['module'], xml_record['name']) + +- + !python {model: stock.location, id: stock_location_shop0}: | + picking = self.env['stock.location'].browse(ref('stock_location_shop0')) + branch_id = self.env['res.branch'].browse(ref('branch_shop0')) + picking.write({'branch_id': branch_id.id}) + - !record {model: stock.location, id: location_refrigerator_small}: name: Small Refrigerator usage: internal + branch_id: base_branch_company.data_branch_1 location_id: stock_location_14 - !record {model: product.product, id: product_icecream}: @@ -201,6 +209,8 @@ picking_type_id: chi_picking_type_in origin: 'incoming_chicago_warehouse' partner_id: base.res_partner_1 + company_id: stock.res_company_1 + branch_id: stock.branch_shop0 location_id: stock.stock_location_suppliers location_dest_id: stock.stock_location_shop0 move_lines: @@ -215,7 +225,9 @@ !record {model: stock.picking, id: incomming_chicago_warehouse1}: picking_type_id: chi_picking_type_in partner_id: base.res_partner_1 + branch_id: stock.branch_shop0 location_id: stock.stock_location_suppliers + company_id: stock.res_company_1 location_dest_id: stock.stock_location_shop0 move_lines: - product_id: product.product_delivery_01 @@ -235,6 +247,8 @@ !record {model: stock.picking, id: incomming_chicago_warehouse2}: picking_type_id: chi_picking_type_in partner_id: base.res_partner_1 + branch_id: stock.branch_shop0 + company_id: stock.res_company_1 location_id: stock.stock_location_suppliers location_dest_id: stock.stock_location_shop0 move_lines: @@ -247,6 +261,8 @@ - !record {model: stock.picking, id: incomming_chicago_warehouse3}: picking_type_id: chi_picking_type_in + branch_id: stock.branch_shop0 + company_id: stock.res_company_1 origin: 'chicago_warehouse' partner_id: base.res_partner_1 date: !eval "'%s-%s-2' % ((datetime.now()-timedelta(days=30)).month,(datetime.now()-timedelta(days=30)).month)" @@ -265,6 +281,8 @@ !record {model: stock.picking, id: outgoing_chicago_warehouse}: picking_type_id: chi_picking_type_out origin: 'outgoing_chicago_warehouse' + branch_id: stock.branch_shop0 + company_id: stock.res_company_1 partner_id: base.res_partner_1 location_id: stock.stock_location_shop0 location_dest_id: stock.stock_location_customers @@ -279,6 +297,8 @@ !record {model: stock.picking, id: outgoing_chicago_warehouse1}: picking_type_id: chi_picking_type_out origin: 'outgoing_shipment_chicago_warehouse' + company_id: stock.res_company_1 + branch_id: stock.branch_shop0 partner_id: base.res_partner_1 location_id: stock.stock_location_shop0 location_dest_id: stock.stock_location_customers @@ -299,6 +319,8 @@ !record {model: stock.picking, id: outgoing_chicago_warehouse2}: picking_type_id: chi_picking_type_out origin: 'chicago_warehouse' + company_id: stock.res_company_1 + branch_id: stock.branch_shop0 partner_id: base.res_partner_1 location_id: stock.stock_location_shop0 location_dest_id: stock.stock_location_customers @@ -312,6 +334,8 @@ - !record {model: stock.picking, id: outgoing_chicago_warehouse3}: picking_type_id: chi_picking_type_out + company_id: stock.res_company_1 + branch_id: stock.branch_shop0 origin: 'outgoing chicago warehouse' location_id: stock.stock_location_shop0 location_dest_id: stock.stock_location_customers diff --git a/addons/stock/data/stock_demo_pre.yml b/addons/stock/data/stock_demo_pre.yml index bcec8635..48885b25 100644 --- a/addons/stock/data/stock_demo_pre.yml +++ b/addons/stock/data/stock_demo_pre.yml @@ -1,11 +1,13 @@ - !record {model: stock.location, id: stock_location_14}: name: Shelf 2 + branch_id: base_branch_company.data_branch_1 posx: 0 - !record {model: stock.location, id: stock_location_components}: name: Shelf 1 + branch_id: base_branch_company.data_branch_1 posx: 0 - diff --git a/addons/stock/data/stock_location_demo_cpu3.yml b/addons/stock/data/stock_location_demo_cpu3.yml index c4323d65..1a904f3d 100644 --- a/addons/stock/data/stock_location_demo_cpu3.yml +++ b/addons/stock/data/stock_location_demo_cpu3.yml @@ -2,19 +2,23 @@ !record {model: stock.location, id: location_order}: name: Order Processing usage: internal + branch_id: base_branch_company.data_branch_1 location_id: stock.stock_location_company - !record {model: stock.location, id: location_dispatch_zone}: name: Dispatch Zone + branch_id: base_branch_company.data_branch_1 usage: internal location_id: location_order - !record {model: stock.location, id: location_gate_a}: name: Gate A usage: internal + branch_id: base_branch_company.data_branch_1 location_id: location_dispatch_zone - !record {model: stock.location, id: location_gate_b}: name: Gate B + branch_id: base_branch_company.data_branch_1 usage: internal location_id: location_dispatch_zone diff --git a/addons/stock/models/res_company.py b/addons/stock/models/res_company.py index 30aef791..5da1ca52 100644 --- a/addons/stock/models/res_company.py +++ b/addons/stock/models/res_company.py @@ -34,6 +34,12 @@ class Company(models.Model): # multi-company rules prevents creating warehouse and sub-locations self.env['stock.warehouse'].check_access_rights('create') - self.env['stock.warehouse'].sudo().create({'name': company.name, 'code': company.name[:5], 'company_id': company.id, 'partner_id': company.partner_id.id}) + self.env['stock.warehouse'].sudo().create({'name': company.name, + 'code': company.name[:5], + 'company_id': company.id, + 'partner_id': + company.partner_id.id, + 'branch_id': + company.branch_id.id}) company.create_transit_location() return company diff --git a/addons/stock/models/stock_inventory.py b/addons/stock/models/stock_inventory.py index 3d0f7a03..08ed1922 100644 --- a/addons/stock/models/stock_inventory.py +++ b/addons/stock/models/stock_inventory.py @@ -54,6 +54,10 @@ class Inventory(models.Model): readonly=True, required=True, states={'draft': [('readonly', False)]}, default=_default_location_id) + + branch_id = fields.Many2one('res.branch', 'Branch', + related='location_id.branch_id', index=True, readonly=True, + store=True) product_id = fields.Many2one( 'product.product', 'Inventoried Product', readonly=True, @@ -295,6 +299,9 @@ class InventoryLine(models.Model): inventory_id = fields.Many2one( 'stock.inventory', 'Inventory', index=True, ondelete='cascade') + branch_id = fields.Many2one('res.branch', 'Branch', + related='inventory_id.branch_id', index=True, + readonly=True, store=True) partner_id = fields.Many2one('res.partner', 'Owner') product_id = fields.Many2one( 'product.product', 'Product', diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py index 3ced6f64..5f20bf2e 100644 --- a/addons/stock/models/stock_location.py +++ b/addons/stock/models/stock_location.py @@ -3,7 +3,7 @@ from datetime import datetime from dateutil import relativedelta -from flectra.exceptions import UserError +from flectra.exceptions import UserError, ValidationError from flectra import api, fields, models, _ from flectra.tools import DEFAULT_SERVER_DATETIME_FORMAT @@ -66,6 +66,7 @@ class Location(models.Model): removal_strategy_id = fields.Many2one('product.removal', 'Removal Strategy', help="Defines the default method used for suggesting the exact location (shelf) where to take the products from, which lot etc. for this location. This method can be enforced at the product category level, and a fallback is made on the parent locations if none is set here.") putaway_strategy_id = fields.Many2one('product.putaway', 'Put Away Strategy', help="Defines the default method used for suggesting the exact location (shelf) where to store the products. This method can be enforced at the product category level, and a fallback is made on the parent locations if none is set here.") barcode = fields.Char('Barcode', copy=False, oldname='loc_barcode') + branch_id = fields.Many2one('res.branch', 'Branch', ondelete="restrict") quant_ids = fields.One2many('stock.quant', 'location_id') _sql_constraints = [('barcode_company_uniq', 'unique (barcode,company_id)', 'The barcode for a location must be unique per company !')] @@ -85,6 +86,60 @@ class Location(models.Model): raise UserError(_("This location's usage cannot be changed to view as it contains products.")) return super(Location, self).write(values) + @api.multi + @api.constrains('branch_id', 'location_id') + def _check_parent_branch(self): + for record in self: + if ( + record.location_id and + record.location_id.usage == 'internal' and + record.branch_id and record.branch_id != record.location_id.branch_id + ): + raise UserError( + _('Configuration Error of Branch:\n' + 'The Location Branch (%s) and ' + 'the Branch (%s) of Parent Location must ' + 'be the same branch!') % (recordord.branch_id.name, + recordord.location_id.branch_id.name) + ) + + @api.multi + @api.constrains('branch_id') + def _check_warehouse_branch(self): + for record in self: + warehouse_obj = self.env['stock.warehouse'] + warehouses_ids = warehouse_obj.search( + ['|', '|', ('wh_input_stock_loc_id', '=', record.ids[0]), + ('lot_stock_id', 'in', record.ids), + ('wh_output_stock_loc_id', 'in', record.ids)]) + for warehouse_id in warehouses_ids: + if record.branch_id and record.branch_id != warehouse_id.branch_id: + raise ValidationError( + _('Configuration Error of Branch:\n' + 'The Location Branch (%s) and ' + 'the Branch (%s) of Warehouse must ' + 'be the same branch!') % (record.branch_id.name, + warehouse_id.branch_id.name) + ) + if record.usage != 'internal' and record.branch_id: + raise UserError( + _('Configuration error of Branch:\n' + 'The branch (%s) should be assigned to internal locations' + ) % (record.branch_id.name)) + + @api.multi + @api.constrains('company_id', 'branch_id') + def _check_company_branch(self): + for record in self: + if record.branch_id and record.company_id != record.branch_id.company_id: + raise UserError( + _('Configuration Error of Company:\n' + 'The Company (%s) in the Stock Location and ' + 'the Company (%s) of Branch must ' + 'be the same company!') % (record.company_id.name, + record.branch_id.company_id.name) + ) + def name_get(self): ret_list = [] for location in self: diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 787402f2..a8205ec2 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -158,6 +158,35 @@ class StockMove(models.Model): is_initial_demand_editable = fields.Boolean('Is initial demand editable', compute='_compute_is_initial_demand_editable') is_quantity_done_editable = fields.Boolean('Is quantity done editable', compute='_compute_is_quantity_done_editable') reference = fields.Char(compute='_compute_reference', string="Reference", store=True) + branch_id = fields.Many2one( + related='location_id.branch_id', store=True, + string='Source Location Branch', + ) + branch_dest_id = fields.Many2one( + related='location_dest_id.branch_id', store=True, + string='Dest. Location Branch', + ) + + @api.multi + @api.constrains('branch_id', 'location_id', 'picking_id', + 'branch_dest_id', 'location_dest_id') + def _check_stock_move_branch(self): + for record in self: + if not record.branch_id: + return True + if (record.location_id and + record.location_id.branch_id and + record.picking_id and record.branch_id != record.picking_id.branch_id + ) and ( + record.location_dest_id and + record.location_dest_id.branch_id and + record.picking_id and record.branch_dest_id != record.picking_id.branch_id + ): + raise UserError( + _('Configuration error of Branch:\nThe Stock moves must ' + 'be related to a source or destination location ' + 'that belongs to the requesting Branch.') + ) @api.depends('picking_id.is_locked') def _compute_is_locked(self): diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index daaae1db..1683f51f 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -9,7 +9,7 @@ from itertools import groupby from flectra import api, fields, models, _ from flectra.tools import DEFAULT_SERVER_DATETIME_FORMAT from flectra.tools.float_utils import float_compare, float_round -from flectra.exceptions import UserError +from flectra.exceptions import UserError, ValidationError from flectra.addons.stock.models.stock_move import PROCUREMENT_PRIORITIES from operator import itemgetter @@ -263,6 +263,11 @@ class Picking(models.Model): index=True, required=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}) + branch_id = fields.Many2one('res.branch', 'Branch', ondelete="restrict", + default=lambda self: self.env['res.users']._get_default_branch(), + states={'done': [('readonly', True)], + 'cancel': [('readonly', True)]}) + move_line_ids = fields.One2many('stock.move.line', 'picking_id', 'Operations') move_line_exist = fields.Boolean( @@ -305,6 +310,31 @@ class Picking(models.Model): ('name_uniq', 'unique(name, company_id)', 'Reference must be unique per company!'), ] + @api.constrains('picking_type_id', 'branch_id') + def _check_picking_type_branch(self): + for order in self: + warehouse_branch_id = order.picking_type_id.warehouse_id.branch_id + if warehouse_branch_id and order.branch_id and warehouse_branch_id != order.branch_id: + raise ValidationError( + _('Configuration Error of Branch:\n' + 'The Picking Branch (%s) and ' + 'the Warehouse Branch (%s) of Picking Type must ' + 'be the same branch!') % (order.branch_id.name, + warehouse_branch_id.name) + ) + + @api.constrains('company_id', 'branch_id') + def _check_company(self): + for order in self: + if order.branch_id and order.company_id != order.branch_id.company_id: + raise ValidationError( + _('Configuration Error of Company:\n' + 'The Stock Picking Company (%s) and ' + 'the Company (%s) of Branch must ' + 'be the same company!') % (order.company_id.name, + order.branch_id.company_id.name) + ) + @api.depends('picking_type_id.show_operations') def _compute_show_operations(self): for picking in self: @@ -873,7 +903,9 @@ class Picking(models.Model): 'res_model': 'stock.scrap', 'view_id': self.env.ref('stock.stock_scrap_form_view2').id, 'type': 'ir.actions.act_window', - 'context': {'default_picking_id': self.id, 'product_ids': products.ids}, + 'context': {'default_picking_id': self.id, + 'default_branch_id': self.branch_id and self.branch_id.id, + 'product_ids': products.ids}, 'target': 'new', } diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py index 7322b778..ab1026cf 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -33,6 +33,8 @@ class StockQuant(models.Model): location_id = fields.Many2one( 'stock.location', 'Location', auto_join=True, ondelete='restrict', readonly=True, required=True) + branch_id = fields.Many2one(related='location_id.branch_id', + string='Branch', ) lot_id = fields.Many2one( 'stock.production.lot', 'Lot/Serial Number', ondelete='restrict', readonly=True) diff --git a/addons/stock/models/stock_scrap.py b/addons/stock/models/stock_scrap.py index 2db1db97..4a146ff3 100644 --- a/addons/stock/models/stock_scrap.py +++ b/addons/stock/models/stock_scrap.py @@ -9,6 +9,7 @@ from flectra.tools import float_compare class StockScrap(models.Model): _name = 'stock.scrap' _order = 'id desc' + _inherit = ['ir.branch.company.mixin'] def _get_default_scrap_location_id(self): return self.env['stock.location'].search([('scrap_location', '=', True)], limit=1).id @@ -76,6 +77,11 @@ class StockScrap(models.Model): def _prepare_move_values(self): self.ensure_one() + branch_id = False + if self.picking_id.branch_id: + branch_id = self.picking_id.branch_id + if not branch_id: + branch_id = self.location_id.branch_id return { 'name': self.name, 'origin': self.origin or self.picking_id.name or self.name, @@ -94,7 +100,8 @@ class StockScrap(models.Model): 'owner_id': self.owner_id.id, 'lot_id': self.lot_id.id, })], # 'restrict_partner_id': self.owner_id.id, - 'picking_id': self.picking_id.id + 'picking_id': self.picking_id.id, + 'branch_id': branch_id and branch_id.id } @api.multi diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py index d66490d8..eef44e1f 100644 --- a/addons/stock/models/stock_warehouse.py +++ b/addons/stock/models/stock_warehouse.py @@ -69,6 +69,7 @@ class Warehouse(models.Model): default_resupply_wh_id = fields.Many2one( 'stock.warehouse', 'Default Resupply Warehouse', help="Goods will always be resupplied from this warehouse") + branch_id = fields.Many2one('res.branch', 'Branch', ondelete="restrict") _sql_constraints = [ ('warehouse_name_uniq', 'unique(name, company_id)', 'The name of the warehouse must be unique per company!'), @@ -80,6 +81,18 @@ class Warehouse(models.Model): # If we are removing the default resupply, we don't have default_resupply_wh_id # TDE note: and we want one self.resupply_wh_ids |= self.default_resupply_wh_id + @api.constrains('company_id', 'branch_id') + def _check_company_branch(self): + for record in self: + if record.branch_id and record.company_id != record.branch_id.company_id: + raise ValidationError( + _('Configuration Error of Company:\n' + 'The Company (%s) in the Warehouse and ' + 'the Company (%s) of Branch must ' + 'be the same company!') % (record.company_id.name, + record.branch_id.company_id.name) + ) + @api.model def create(self, vals): # create view location for warehouse then create all locations @@ -749,6 +762,9 @@ class Orderpoint(models.Model): location_id = fields.Many2one( 'stock.location', 'Location', ondelete="cascade", required=True) + branch_id = fields.Many2one('res.branch', 'Branch', + related='location_id.branch_id', index=True, + readonly=True, store=True) product_id = fields.Many2one( 'product.product', 'Product', domain=[('type', '=', 'product')], ondelete='cascade', required=True) diff --git a/addons/stock/report/report_deliveryslip.xml b/addons/stock/report/report_deliveryslip.xml index 679db550..1de1244f 100644 --- a/addons/stock/report/report_deliveryslip.xml +++ b/addons/stock/report/report_deliveryslip.xml @@ -31,6 +31,7 @@ + @@ -46,6 +47,9 @@ +
Date Branch
+ +
diff --git a/addons/stock/report/report_stockinventory.xml b/addons/stock/report/report_stockinventory.xml index 23c1479f..d0d65880 100644 --- a/addons/stock/report/report_stockinventory.xml +++ b/addons/stock/report/report_stockinventory.xml @@ -13,6 +13,7 @@ Inventory Date + Branch @@ -23,6 +24,9 @@ + + + diff --git a/addons/stock/report/report_stockpicking_operations.xml b/addons/stock/report/report_stockpicking_operations.xml index 8574398d..e259d1d3 100644 --- a/addons/stock/report/report_stockpicking_operations.xml +++ b/addons/stock/report/report_stockpicking_operations.xml @@ -51,6 +51,7 @@ State Commitment Date Scheduled Date + Branch @@ -67,6 +68,9 @@ + + + diff --git a/addons/stock/security/stock_security.xml b/addons/stock/security/stock_security.xml index 986c20e4..af5762ac 100644 --- a/addons/stock/security/stock_security.xml +++ b/addons/stock/security/stock_security.xml @@ -156,5 +156,49 @@ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+ + + Locations multi-branch + + + ['|', ('branch_id', '=', False), '|', ('branch_id', '=', user.default_branch_id.id), + ('branch_id', 'in', user.branch_ids.ids)] + + + + + + + + + + + + + Warehouse multi-branch + + + ['|', ('branch_id', '=', False), '|', ('branch_id', '=', user.default_branch_id.id), + ('branch_id', 'in', user.branch_ids.ids)] + + + + + Picking multi-branch + + + ['|', ('branch_id', '=', False), '|', ('branch_id', '=', user.default_branch_id.id), + ('branch_id', 'in', user.branch_ids.ids)] + + + + + Stock Move multi-branch + + + ['|', '|', ('location_id.branch_id','=',False),('location_id.branch_id','in',[branch for branch in user.branch_ids.ids]), ('location_id.branch_id','=',user.default_branch_id.id), + '|', '|', ('location_dest_id.branch_id','=',False),('location_dest_id.branch_id','in',[branch for branch in user.branch_ids.ids]), ('location_dest_id.branch_id','=',user.default_branch_id.id)] + +
diff --git a/addons/stock/tests/__init__.py b/addons/stock/tests/__init__.py index a9a18e27..c652dff9 100644 --- a/addons/stock/tests/__init__.py +++ b/addons/stock/tests/__init__.py @@ -9,3 +9,4 @@ from . import test_quant from . import test_inventory from . import test_move from . import test_move2 +from . import test_stock_branch diff --git a/addons/stock/tests/common2.py b/addons/stock/tests/common2.py index c4b902d2..ff0e1cd1 100644 --- a/addons/stock/tests/common2.py +++ b/addons/stock/tests/common2.py @@ -69,9 +69,11 @@ class TestStockCommon(common.TestProductCommon): 'groups_id': [(6, 0, [user_group_stock_manager.id])]}) # Warehouses + branch_id = cls.env.ref('base_branch_company.data_branch_1') cls.warehouse_1 = cls.env['stock.warehouse'].create({ 'name': 'Base Warehouse', 'reception_steps': 'one_step', + 'branch_id': branch_id.id, 'delivery_steps': 'ship_only', 'code': 'BWH'}) diff --git a/addons/stock/tests/test_move2.py b/addons/stock/tests/test_move2.py index 99ff9856..e28f21d0 100644 --- a/addons/stock/tests/test_move2.py +++ b/addons/stock/tests/test_move2.py @@ -1468,8 +1468,9 @@ class TestSinglePicking(TestStockCommon): Move Input-> QC - Move QC -> Stock Move receipt 2 / """ + branch_id = self.env.ref('base_branch_company.data_branch_1') warehouse = self.env['stock.warehouse'].create({ - 'name': 'TEST WAREHOUSE', + 'name': 'TEST WAREHOUSE', 'branch_id': branch_id.id, 'code': 'TEST1', 'reception_steps': 'three_steps', }) @@ -1478,6 +1479,7 @@ class TestSinglePicking(TestStockCommon): 'location_dest_id': warehouse.wh_input_stock_loc_id.id, 'partner_id': self.partner_delta_id, 'picking_type_id': warehouse.in_type_id.id, + 'branch_id': branch_id.id }) move_receipt_1 = self.MoveObj.create({ 'name': self.productA.name, diff --git a/addons/stock/tests/test_stock_branch.py b/addons/stock/tests/test_stock_branch.py new file mode 100644 index 00000000..2e7d81cd --- /dev/null +++ b/addons/stock/tests/test_stock_branch.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +from flectra.addons.stock.tests import common + + +class TestStockBranch(common.TestStockCommon): + + def setUp(self): + super(TestStockBranch, self).setUp() + + self.branch_1 = self.env.ref('base_branch_company.data_branch_1') + self.branch_2 = self.env.ref('base_branch_company.data_branch_2') + self.main_company = self.env.ref('base.main_company') + self.stock_manager_group = self.env.ref('stock.group_stock_manager') + warehouse_wt = self.env.ref('stock.stock_warehouse_WW') + + self.destination_loc_id = warehouse_wt.lot_stock_id.id + self.picking_type_id = warehouse_wt.in_type_id + + self.user_1 = self.create_stock_user( + self.main_company, 'Test Stock User 1', self.branch_1, + [self.branch_1, self.branch_2]) + self.user_2 = self.create_stock_user( + self.main_company, 'Test Stock User 2', self.branch_2, + [self.branch_2]) + self.picking_1 = self.pickings_create( + self.picking_type_id, self.branch_2, self.user_1, + self.supplier_location, self.stock_location) + self.picking_2 = self.pickings_create( + self.picking_type_id, self.branch_2, self.user_2, + self.supplier_location, self.destination_loc_id) + self.internal_picking = self.pickings_create( + self.picking_type_id, self.branch_2, self.user_1, + self.stock_location, self.destination_loc_id) + + def pickings_create(self, picking_type_id, branch_id, user_id, + source_loc_id, destination_loc_id): + picking_id = self.PickingObj.sudo(user_id).create( + { + 'location_id': source_loc_id, + 'picking_type_id': picking_type_id.id, + 'branch_id': branch_id.id, + 'location_dest_id': destination_loc_id, + } + ) + self.MoveObj.sudo(user_id).create( + { + 'picking_id': picking_id.id, + 'product_uom_qty': 6.0, + 'name': 'Test Move of Picking', + 'location_id': source_loc_id, + 'location_dest_id': destination_loc_id, + 'product_id': self.productC.id, + 'product_uom': self.productC.uom_id.id, + } + ) + return picking_id + + def create_stock_user(self, company_id, login_name, branch_id, branch_ids): + user_obj = \ + self.env['res.users'].with_context( + {'no_reset_password': True}).create( + { + 'company_id': company_id.id, + 'default_branch_id': branch_id.id, + 'branch_ids': [(4, branch.id) for branch in branch_ids], + 'company_ids': [(4, company_id.id)], + 'login': login_name, + 'groups_id': [(6, 0, [self.stock_manager_group.id])], + 'name': 'Stock User ' + login_name, + 'email': 'demo@yourcompany.com', + 'password': '123' + } + ) + return user_obj.id + + def test_stock_picking_branch(self): + picking_ids = self.PickingObj.sudo(self.user_1).search([('id', '=', self.picking_1.id)]).ids + self.assertNotEqual(picking_ids, [], '') + + picking_ids = self.PickingObj.sudo(self.user_2).search([('id', '=', self.picking_2.id)]).ids + self.assertNotEqual(picking_ids, []) + + picking_ids = self.PickingObj.sudo(self.user_1).search([('id', '=', self.internal_picking.id)]).ids + self.assertNotEqual(picking_ids, []) diff --git a/addons/stock/views/stock_inventory_views.xml b/addons/stock/views/stock_inventory_views.xml index 0032ca9a..248031ee 100644 --- a/addons/stock/views/stock_inventory_views.xml +++ b/addons/stock/views/stock_inventory_views.xml @@ -140,6 +140,7 @@ + diff --git a/addons/stock/views/stock_location_views.xml b/addons/stock/views/stock_location_views.xml index 9e132a33..7eb3d0fa 100644 --- a/addons/stock/views/stock_location_views.xml +++ b/addons/stock/views/stock_location_views.xml @@ -36,6 +36,7 @@ + diff --git a/addons/stock/views/stock_move_views.xml b/addons/stock/views/stock_move_views.xml index 5d4b64a0..8d6fb208 100644 --- a/addons/stock/views/stock_move_views.xml +++ b/addons/stock/views/stock_move_views.xml @@ -12,7 +12,9 @@ + + @@ -58,7 +60,9 @@ + + @@ -112,6 +116,7 @@ + @@ -119,6 +124,7 @@ + @@ -218,7 +224,9 @@ + + diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml index 7661a709..73aad012 100644 --- a/addons/stock/views/stock_picking_views.xml +++ b/addons/stock/views/stock_picking_views.xml @@ -319,6 +319,7 @@ + diff --git a/addons/stock/views/stock_quant_views.xml b/addons/stock/views/stock_quant_views.xml index f89db65a..47c17565 100644 --- a/addons/stock/views/stock_quant_views.xml +++ b/addons/stock/views/stock_quant_views.xml @@ -48,6 +48,7 @@ + diff --git a/addons/stock/views/stock_scrap_views.xml b/addons/stock/views/stock_scrap_views.xml index f8ccfaaa..e60d7d7e 100644 --- a/addons/stock/views/stock_scrap_views.xml +++ b/addons/stock/views/stock_scrap_views.xml @@ -52,6 +52,7 @@ + diff --git a/addons/stock/views/stock_warehouse_views.xml b/addons/stock/views/stock_warehouse_views.xml index f4261cf9..8b7616d2 100644 --- a/addons/stock/views/stock_warehouse_views.xml +++ b/addons/stock/views/stock_warehouse_views.xml @@ -26,6 +26,7 @@ + diff --git a/flectra/addons/base/res/res_users.py b/flectra/addons/base/res/res_users.py index e488b72d..a1e7c2d8 100644 --- a/flectra/addons/base/res/res_users.py +++ b/flectra/addons/base/res/res_users.py @@ -376,6 +376,7 @@ class Users(models.Model): for id in self.ids: self.__uid_cache[db].pop(id, None) + self.clear_caches() return res @api.multi