From e1de946fb22d571b237101fd277e63eb2d759926 Mon Sep 17 00:00:00 2001 From: Darshan Patel Date: Mon, 18 Jan 2016 14:37:44 +0530 Subject: [PATCH 1/4] Added stock_account OU module Added Test Cases. Improved code for test cases Migrated Valuation method of quants. Migrated Valuation method _account_entry_move of quants. Completed test cases and modified valuation method --- stock_account_operating_unit/README.rst | 75 ++++ stock_account_operating_unit/__init__.py | 6 + stock_account_operating_unit/__openerp__.py | 24 ++ .../model/__init__.py | 6 + stock_account_operating_unit/model/stock.py | 100 ++++++ .../tests/__init__.py | 6 + .../test_stock_account_operating_unit.py | 324 ++++++++++++++++++ 7 files changed, 541 insertions(+) create mode 100644 stock_account_operating_unit/README.rst create mode 100644 stock_account_operating_unit/__init__.py create mode 100644 stock_account_operating_unit/__openerp__.py create mode 100644 stock_account_operating_unit/model/__init__.py create mode 100644 stock_account_operating_unit/model/stock.py create mode 100644 stock_account_operating_unit/tests/__init__.py create mode 100644 stock_account_operating_unit/tests/test_stock_account_operating_unit.py diff --git a/stock_account_operating_unit/README.rst b/stock_account_operating_unit/README.rst new file mode 100644 index 0000000000..ec1701d799 --- /dev/null +++ b/stock_account_operating_unit/README.rst @@ -0,0 +1,75 @@ +.. image:: https://img.shields.io/badge/license-LGPLv3-blue.svg + :target: https://www.gnu.org/licenses/lgpl.html + :alt: License: LGPL-3 + +======================================= +Stock account moves with Operating Unit +======================================= + +This module introduces the following features: +- Creates account move lines when stock moves are posted between internal +locations within the same company, but different OU’s. + + +Configuration +============= + +If your company is required to generate a balanced balance sheet by +operating unit you can specify at company level that operating units should +be self-balanced, and then indicate a self-balancing clearing account. + +* Create an account for "Inter-OU Clearing" of type Regular. +* Go to *Settings / Companies / Configuration* and: + ** Set the "Operating Units are self-balanced" checkbox + ** Set the "Inter-OU Clearing" account in "Inter-operating unit clearing + account" field. +* Assign Operating Unit in Accounts. + + +Usage +===== + +Create stock moves between internal locations within the same company, but +different OU’s. The journal entries are created and they are self-balanced +within the OU when the journal entries are posted + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/213/9.0 + +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. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Eficent Business and IT Consulting Services S.L. +* Serpent Consulting Services Pvt. Ltd. + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: http://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/stock_account_operating_unit/__init__.py b/stock_account_operating_unit/__init__.py new file mode 100644 index 0000000000..209c94a68c --- /dev/null +++ b/stock_account_operating_unit/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# © 2015-17 Eficent Business and IT Consulting Services S.L. +# - Jordi Ballester Alomar +# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from . import model diff --git a/stock_account_operating_unit/__openerp__.py b/stock_account_operating_unit/__openerp__.py new file mode 100644 index 0000000000..88f3c14187 --- /dev/null +++ b/stock_account_operating_unit/__openerp__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# © 2015-17 Eficent Business and IT Consulting Services S.L. +# - Jordi Ballester Alomar +# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +{ + "name": "Stock account moves with Operating Unit", + "summary": "Create journal entries in moves between internal locations " + "with different operating units.", + "version": "9.0.1.0.0", + "category": "Generic Modules/Sales & Purchases", + "author": "Eficent Business and IT Consulting Services S.L., " + "Serpent Consulting Services Pvt. Ltd.," + "Odoo Community Association (OCA)", + "license": "LGPL-3", + "website": "http://www.eficent.com", + "depends": [ + 'stock_operating_unit', + 'account_operating_unit', + "stock_account" + ], + "installable": True, +} diff --git a/stock_account_operating_unit/model/__init__.py b/stock_account_operating_unit/model/__init__.py new file mode 100644 index 0000000000..5261ffb5ab --- /dev/null +++ b/stock_account_operating_unit/model/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# © 2015-17 Eficent Business and IT Consulting Services S.L. +# - Jordi Ballester Alomar +# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from . import stock diff --git a/stock_account_operating_unit/model/stock.py b/stock_account_operating_unit/model/stock.py new file mode 100644 index 0000000000..7a4088659a --- /dev/null +++ b/stock_account_operating_unit/model/stock.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# © 2015-17 Eficent Business and IT Consulting Services S.L. +# - Jordi Ballester Alomar +# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from openerp import _, api, exceptions, models + + +class StockQuant(models.Model): + _inherit = 'stock.quant' + + @api.model + def _prepare_account_move_line(self, move, qty, cost, credit_account_id, + debit_account_id): + res = super(StockQuant, self)._prepare_account_move_line( + move, qty, cost, credit_account_id, debit_account_id + ) + + debit_line_vals = res[0][2] + credit_line_vals = res[1][2] + + if ( + move.operating_unit_id and move.operating_unit_dest_id and + move.operating_unit_id != move.operating_unit_dest_id and + debit_line_vals['account_id'] != credit_line_vals['account_id'] + ): + raise exceptions.UserError( + _('You cannot create stock moves involving separate source ' + 'and destination accounts related to different operating ' + 'units.') + ) + + debit_line_vals['operating_unit_id'] = ( + move.operating_unit_dest_id.id or move.operating_unit_id.id + ) + credit_line_vals['operating_unit_id'] = ( + move.operating_unit_id.id or move.operating_unit_dest_id.id + ) + return [(0, 0, debit_line_vals), (0, 0, credit_line_vals)] + + @api.model + def _account_entry_move(self, quants, move): + """ + Generate an accounting moves if the product being moved is subject + to real_time valuation tracking, + and the source or destination location are + a transit location or is outside of the company or the source or + destination locations belong to different operating units. + """ + res = super(StockQuant, self)._account_entry_move(quants, move) + if move.product_id.valuation == 'real_time': + # Inter-operating unit moves do not accept to + # from/to non-internal location + if ( + move.location_id.company_id == + move.location_dest_id.company_id and + move.operating_unit_id != move.operating_unit_dest_id + ): + err = False + if (move.location_id.usage != 'internal' and + move.location_dest_id.usage == 'internal'): + err = True + if (move.location_id.usage == 'internal' and + move.location_dest_id.usage != 'internal'): + err = True + if (move.location_id.usage != 'internal' and + move.location_dest_id.usage != 'internal'): + err = True + if err: + raise exceptions.UserError( + _('Transfers between locations of different operating ' + 'unit locations is only allowed when both source ' + 'and destination locations are internal.') + ) + src_company_ctx = dict( + force_company=move.location_id.company_id.id + ) + company_ctx = dict(company_id=move.company_id.id) + self_c = self.with_context(src_company_ctx) + data = self_c._get_accounting_data_for_valuation(move) + (journal_id, acc_src, acc_dest, acc_valuation) = data + quant_cost_qty = {} + for quant in quants: + if quant_cost_qty.get(quant.cost): + quant_cost_qty[quant.cost] += quant.qty + else: + quant_cost_qty[quant.cost] = quant.qty + move_obj = self.env['account.move'] + for cost, qty in quant_cost_qty.items(): + move_lines = self._prepare_account_move_line(move, qty, + cost, + acc_valuation, + acc_valuation) + move_obj.with_context(company_ctx).create({ + 'journal_id': journal_id, + 'line_ids': move_lines, + 'company_id': move.company_id.id, + 'ref': move.picking_id and move.picking_id.name, + }) + return res diff --git a/stock_account_operating_unit/tests/__init__.py b/stock_account_operating_unit/tests/__init__.py new file mode 100644 index 0000000000..9f4fa86db7 --- /dev/null +++ b/stock_account_operating_unit/tests/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# © 2015-17 Eficent Business and IT Consulting Services S.L. - +# Jordi Ballester Alomar +# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from . import test_stock_account_operating_unit diff --git a/stock_account_operating_unit/tests/test_stock_account_operating_unit.py b/stock_account_operating_unit/tests/test_stock_account_operating_unit.py new file mode 100644 index 0000000000..5f7ef6a39f --- /dev/null +++ b/stock_account_operating_unit/tests/test_stock_account_operating_unit.py @@ -0,0 +1,324 @@ +# -*- coding: utf-8 -*- +# © 2015-17 Eficent Business and IT Consulting Services S.L. - +# Jordi Ballester Alomar +# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from openerp.addons.stock.tests import common + + +class TestStockAccountOperatingUnit(common.TestStockCommon): + + def setUp(self): + super(TestStockAccountOperatingUnit, self).setUp() + self.res_groups = self.env['res.groups'] + self.res_users_model = self.env['res.users'] + self.aml_model = self.env['account.move.line'] + self.account_model = self.env['account.account'] + self.product_model = self.env['product.product'] + self.product_cteg_model = self.env['product.category'] + self.inv_line_model = self.env['account.invoice.line'] + self.acc_type_model = self.env['account.account.type'] + self.operating_unit_model = self.env['operating.unit'] + self.company_model = self.env['res.company'] + self.move_model = self.env['stock.move'] + self.picking_model = self.env['stock.picking'] + self.ModelDataObj = self.env['ir.model.data'] + # company + self.company = self.ModelDataObj.xmlid_to_res_id('base.main_company') + # groups + self.group_stock_manager =\ + self.ModelDataObj.xmlid_to_res_id('stock.group_stock_manager') + self.grp_acc_user =\ + self.ModelDataObj.xmlid_to_res_id('account.group_account_invoice') + self.grp_stock_user =\ + self.ModelDataObj.xmlid_to_res_id('stock.group_stock_user') + # Main Operating Unit + self.ou1 = self.ModelDataObj.get_object('operating_unit', + 'main_operating_unit') + # B2B Operating Unit + self.b2b = self.ModelDataObj.get_object('operating_unit', + 'b2b_operating_unit') + # B2C Operating Unit + self.b2c = self.ModelDataObj.get_object('operating_unit', + 'b2c_operating_unit') + # Partner + self.partner1 = self.ModelDataObj.xmlid_to_res_id('base.res_partner_1') + self.stock_location_stock =\ + self.ModelDataObj.xmlid_to_res_id('stock.stock_location_stock') + self.supplier_location =\ + self.ModelDataObj.xmlid_to_res_id('stock.stock_location_suppliers') + # Create user1 + self.user1 =\ + self._create_user('stock_account_user_1', + [self.grp_stock_user, self.grp_acc_user, + self.group_stock_manager], + self.company, [self.ou1.id, self.b2c.id]) + # Create user2 + self.user2 =\ + self._create_user('stock_account_user_2', + [self.grp_stock_user, self.grp_acc_user, + self.group_stock_manager], + self.company, [self.b2c.id]) + # Create account for Goods Received Not Invoiced + name = 'Goods Received Not Invoiced' + code = 'grni' + acc_type = self.env.ref('account.data_account_type_equity') + self.account_grni = self._create_account(acc_type, name, code, + self.company) +# # Create account for Cost of Goods Sold + name = 'Cost of Goods Sold' + code = 'cogs' + acc_type = self.env.ref('account.data_account_type_expenses') + self.account_cogs_id = self._create_account(acc_type, name, code, + self.company) +# # Create account for Inventory + name = 'Inventory' + code = 'inventory' + acc_type = self.env.ref('account.data_account_type_fixed_assets') + self.account_inventory = self._create_account(acc_type, name, code, + self.company) +# # Create account for Inter-OU Clearing + name = 'Inter-OU Clearing' + code = 'inter_ou' + acc_type = self.env.ref('account.data_account_type_equity') + self.account_inter_ou_clearing = self._create_account(acc_type, + name, code, + self.company) + # Update company data + company = self.env.ref('base.main_company') + company.write({'inter_ou_clearing_account_id': + self.account_inter_ou_clearing.id, + 'ou_is_self_balanced': True}) +# Create Product + self.product = self._create_product() +# Create incoming stock picking type + self.incoming_id = self.env.ref('stock.warehouse0').in_type_id.id +# Create incoming and internal stock picking types + b2c_wh = self.env.ref('stock_operating_unit.stock_warehouse_b2c') + b2c_wh.lot_stock_id.write({'operating_unit_id': self.b2c.id}) + self.location_b2c_id = b2c_wh.lot_stock_id.id + self.b2c_type_in_id = b2c_wh.in_type_id.id + self.b2c_type_int_id = b2c_wh.int_type_id.id + + def _create_user(self, login, groups, company, operating_units): + """Create a user.""" + group_ids = [group for group in groups] + user = self.res_users_model.create({ + 'name': 'Test Stock Account User', + 'login': login, + 'password': 'demo', + 'email': 'example@yourcompany.com', + 'company_id': company, + 'company_ids': [(4, company)], + 'operating_unit_ids': [(4, ou) for ou in operating_units], + 'groups_id': [(6, 0, group_ids)] + }) + return user + + def _create_account(self, acc_type, name, code, company): + """Create an account.""" + account = self.account_model.create({ + 'name': name, + 'code': code, + 'user_type_id': acc_type.ids and acc_type.ids[0], + 'company_id': company + }) + return account + + def _create_product(self): + """Create a Product with inventory valuation set to auto.""" + product_cteg = self.product_cteg_model.create({ + 'name': 'test_product_ctg', + 'property_valuation': 'real_time', + 'property_stock_valuation_account_id': self.account_inventory.id, + 'property_stock_account_input_categ_id': self.account_grni.id, + 'property_stock_account_output_categ_id': self.account_cogs_id, + }) + product = self.product_model.create({ + 'name': 'test_product', + 'categ_id': product_cteg.id, + 'type': 'product', + 'list_price': 1.0, + 'standard_price': 1.0 + }) + return product + + def _create_picking(self, user_id, ou_id, picking_type, + src_loc_id, dest_loc_id): + """Create a Picking.""" + picking = self.picking_model.sudo(user_id).create({ + 'picking_type_id': picking_type, + 'location_id': src_loc_id, + 'location_dest_id': dest_loc_id, + 'operating_unit_id': ou_id, + }) + self.move_model.sudo(user_id).create({ + 'name': 'a move', + 'product_id': self.product.id, + 'product_uom_qty': 1.0, + 'product_uom': 1, + 'picking_id': picking.id, + 'location_id': src_loc_id, + 'location_dest_id': dest_loc_id, + }) + return picking + + def _confirm_receive(self, user_id, picking, picking_type=None): + """ + Checks the stock availability, validates and process the stock picking. + """ + picking.action_confirm() + picking.force_assign() + res = picking.sudo(user_id).do_new_transfer() + validate_id = res['res_id'] + validate = self.env['stock.immediate.transfer'].browse(validate_id) + validate.process() + + def _check_account_balance(self, account_id, operating_unit=None, + expected_balance=0.0): + """ + Check the balance of the account based on different operating units. + """ + domain = [('account_id', '=', account_id)] + if operating_unit: + domain.extend([('operating_unit_id', '=', operating_unit.id)]) + + balance = self._get_balance(domain) + if operating_unit: + self.assertEqual( + balance, expected_balance, + 'Balance is not %s for Operating Unit %s.' + % (str(expected_balance), operating_unit.name)) + else: + self.assertEqual( + balance, expected_balance, + 'Balance is not %s for all Operating Units.' + % str(expected_balance)) \ + + + def _get_balance(self, domain): + """ + Call read_group method and return the balance of particular account. + """ + aml_rec = self.aml_model.read_group(domain, + ['debit', 'credit', 'account_id'], + ['account_id']) + if aml_rec: + return aml_rec[0].get('debit', 0) - aml_rec[0].get('credit', 0) + else: + return 0.0 + + def test_pickings(self): + """Test account balances during receiving stock into the main + operating unit, then into b2c operating unit, and then transfer stock + from main ou to b2c.""" + # Create Incoming Shipment 1 + self.picking = self._create_picking( + self.user1.id, self.ou1.id, self.incoming_id, + self.supplier_location, self.stock_location_stock) + # Receive it + self._confirm_receive(self.user1.id, self.picking) + # GL account ‘Inventory’ has balance 1 irrespective of the OU + expected_balance = 1.0 + self._check_account_balance(self.account_inventory.id, + operating_unit=None, + expected_balance=expected_balance) + # GL account ‘Inventory’ has balance 1 on OU main_operating_unit + expected_balance = 1.0 + self._check_account_balance(self.account_inventory.id, + operating_unit=self.ou1, + expected_balance=expected_balance) + # GL account ‘Inventory’ has balance 0 on OU main_operating_unit + expected_balance = 0.0 + self._check_account_balance(self.account_inventory.id, + operating_unit=self.b2c, + expected_balance=expected_balance) + # GL account ‘Goods Received Not Invoiced’ has balance - 1 + # irrespective of the OU + expected_balance = -1.0 + self._check_account_balance(self.account_grni.id, + operating_unit=None, + expected_balance=expected_balance) + # GL account ‘Goods Received Not Invoiced’ has balance -1 on Main OU + expected_balance = -1.0 + self._check_account_balance(self.account_grni.id, + operating_unit=self.ou1, + expected_balance=expected_balance) + # GL account ‘Goods Received Not Invoiced’ has balance 0 on OU b2c + expected_balance = 0.0 + self._check_account_balance(self.account_grni.id, + operating_unit=self.b2c, + expected_balance=expected_balance) + + # Create Incoming Shipment 2 + self.picking =\ + self._create_picking(self.user2.id, self.b2c.id, + self.b2c_type_in_id, + self.supplier_location, + self.location_b2c_id) + +# # Receive it + self._confirm_receive(self.user2.id, self.picking) + + # GL account ‘Inventory’ has balance 2 irrespective of the OU + expected_balance = 2.0 + self._check_account_balance(self.account_inventory.id, + operating_unit=None, + expected_balance=expected_balance) + # GL account ‘Inventory’ has balance 1 on OU main_operating_unit + expected_balance = 1.0 + self._check_account_balance(self.account_inventory.id, + operating_unit=self.ou1, + expected_balance=expected_balance) + # GL account ‘Inventory’ has balance 1 on OU b2c + expected_balance = 1.0 + self._check_account_balance(self.account_inventory.id, + operating_unit=self.b2c, + expected_balance=expected_balance) + # GL account ‘Goods Received Not Invoiced’ has balance - 2 + # irrespective of the OU + expected_balance = -2.0 + self._check_account_balance(self.account_grni.id, + operating_unit=None, + expected_balance=expected_balance) + # GL account ‘Goods Received Not Invoiced’ has balance -1 on Main OU + expected_balance = -1.0 + self._check_account_balance(self.account_grni.id, + operating_unit=self.ou1, + expected_balance=expected_balance) + # GL account ‘Goods Received Not Invoiced’ has balance 0 on OU b2c + expected_balance = -1.0 + self._check_account_balance(self.account_grni.id, + operating_unit=self.b2c, + expected_balance=expected_balance) + + # Create Internal Transfer + self.picking =\ + self._create_picking(self.user1.id, self.b2c.id, + self.b2c_type_int_id, + self.stock_location_stock, + self.location_b2c_id) + # Receive it + picking_type = 'internal' + self._confirm_receive(self.user1.id, self.picking, + picking_type=picking_type) + # GL account ‘Inventory’ has balance 2 irrespective of the OU + expected_balance = 2.0 + self._check_account_balance(self.account_inventory.id, + operating_unit=None, + expected_balance=expected_balance) + # GL account ‘Inventory’ has balance 0 on OU main_operating_unit + expected_balance = 0.0 + self._check_account_balance(self.account_inventory.id, + operating_unit=self.ou1, + expected_balance=expected_balance) + # GL account ‘Inventory’ has balance 2 on OU b2c + expected_balance = 2.0 + self._check_account_balance(self.account_inventory.id, + operating_unit=self.b2c, + expected_balance=expected_balance) + # GL account ‘Inter-OU clearing’ has balance 0 irrespective of the OU + expected_balance = 0.0 + self._check_account_balance(self.account_inter_ou_clearing.id, + operating_unit=None, + expected_balance=expected_balance) From 39e09e365be291ce39b214bf8a58b1ba0e248dcc Mon Sep 17 00:00:00 2001 From: aheficent Date: Fri, 26 May 2017 17:00:38 +0200 Subject: [PATCH 2/4] [MIG] stock_account_operating_unit: Migration to 10.0 --- stock_account_operating_unit/README.rst | 20 ++-- .../{__openerp__.py => __manifest__.py} | 4 +- .../model/__init__.py | 3 +- stock_account_operating_unit/model/stock.py | 100 ------------------ .../model/stock_move.py | 39 +++++++ .../model/stock_quant.py | 54 ++++++++++ 6 files changed, 109 insertions(+), 111 deletions(-) rename stock_account_operating_unit/{__openerp__.py => __manifest__.py} (90%) delete mode 100644 stock_account_operating_unit/model/stock.py create mode 100644 stock_account_operating_unit/model/stock_move.py create mode 100644 stock_account_operating_unit/model/stock_quant.py diff --git a/stock_account_operating_unit/README.rst b/stock_account_operating_unit/README.rst index ec1701d799..32a6f2a5d1 100644 --- a/stock_account_operating_unit/README.rst +++ b/stock_account_operating_unit/README.rst @@ -7,8 +7,9 @@ Stock account moves with Operating Unit ======================================= This module introduces the following features: + - Creates account move lines when stock moves are posted between internal -locations within the same company, but different OU’s. + locations within the same company, but different OU’s. Configuration @@ -18,12 +19,15 @@ If your company is required to generate a balanced balance sheet by operating unit you can specify at company level that operating units should be self-balanced, and then indicate a self-balancing clearing account. -* Create an account for "Inter-OU Clearing" of type Regular. -* Go to *Settings / Companies / Configuration* and: - ** Set the "Operating Units are self-balanced" checkbox - ** Set the "Inter-OU Clearing" account in "Inter-operating unit clearing - account" field. -* Assign Operating Unit in Accounts. +#. Create an account for "Inter-OU Clearing" of type Regular. +#. Go to *Settings / Companies / Configuration* and: + + * Set the "Operating Units are self-balanced" checkbox. + + * Set the "Inter-OU Clearing" account in "Inter-operating unit clearing + account" field. + +#. Assign Operating Unit in Accounts. Usage @@ -35,7 +39,7 @@ within the OU when the journal entries are posted .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/213/9.0 + :target: https://runbot.odoo-community.org/runbot/213/10.0 Bug Tracker =========== diff --git a/stock_account_operating_unit/__openerp__.py b/stock_account_operating_unit/__manifest__.py similarity index 90% rename from stock_account_operating_unit/__openerp__.py rename to stock_account_operating_unit/__manifest__.py index 88f3c14187..abc1cc1d13 100644 --- a/stock_account_operating_unit/__openerp__.py +++ b/stock_account_operating_unit/__manifest__.py @@ -8,13 +8,13 @@ "name": "Stock account moves with Operating Unit", "summary": "Create journal entries in moves between internal locations " "with different operating units.", - "version": "9.0.1.0.0", + "version": "10.0.1.0.0", "category": "Generic Modules/Sales & Purchases", "author": "Eficent Business and IT Consulting Services S.L., " "Serpent Consulting Services Pvt. Ltd.," "Odoo Community Association (OCA)", "license": "LGPL-3", - "website": "http://www.eficent.com", + "website": "https://github.com/OCA/operating-unit", "depends": [ 'stock_operating_unit', 'account_operating_unit', diff --git a/stock_account_operating_unit/model/__init__.py b/stock_account_operating_unit/model/__init__.py index 5261ffb5ab..6c3815ec4c 100644 --- a/stock_account_operating_unit/model/__init__.py +++ b/stock_account_operating_unit/model/__init__.py @@ -3,4 +3,5 @@ # - Jordi Ballester Alomar # © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). -from . import stock +from . import stock_move +from . import stock_quant diff --git a/stock_account_operating_unit/model/stock.py b/stock_account_operating_unit/model/stock.py deleted file mode 100644 index 7a4088659a..0000000000 --- a/stock_account_operating_unit/model/stock.py +++ /dev/null @@ -1,100 +0,0 @@ -# -*- coding: utf-8 -*- -# © 2015-17 Eficent Business and IT Consulting Services S.L. -# - Jordi Ballester Alomar -# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya -# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). -from openerp import _, api, exceptions, models - - -class StockQuant(models.Model): - _inherit = 'stock.quant' - - @api.model - def _prepare_account_move_line(self, move, qty, cost, credit_account_id, - debit_account_id): - res = super(StockQuant, self)._prepare_account_move_line( - move, qty, cost, credit_account_id, debit_account_id - ) - - debit_line_vals = res[0][2] - credit_line_vals = res[1][2] - - if ( - move.operating_unit_id and move.operating_unit_dest_id and - move.operating_unit_id != move.operating_unit_dest_id and - debit_line_vals['account_id'] != credit_line_vals['account_id'] - ): - raise exceptions.UserError( - _('You cannot create stock moves involving separate source ' - 'and destination accounts related to different operating ' - 'units.') - ) - - debit_line_vals['operating_unit_id'] = ( - move.operating_unit_dest_id.id or move.operating_unit_id.id - ) - credit_line_vals['operating_unit_id'] = ( - move.operating_unit_id.id or move.operating_unit_dest_id.id - ) - return [(0, 0, debit_line_vals), (0, 0, credit_line_vals)] - - @api.model - def _account_entry_move(self, quants, move): - """ - Generate an accounting moves if the product being moved is subject - to real_time valuation tracking, - and the source or destination location are - a transit location or is outside of the company or the source or - destination locations belong to different operating units. - """ - res = super(StockQuant, self)._account_entry_move(quants, move) - if move.product_id.valuation == 'real_time': - # Inter-operating unit moves do not accept to - # from/to non-internal location - if ( - move.location_id.company_id == - move.location_dest_id.company_id and - move.operating_unit_id != move.operating_unit_dest_id - ): - err = False - if (move.location_id.usage != 'internal' and - move.location_dest_id.usage == 'internal'): - err = True - if (move.location_id.usage == 'internal' and - move.location_dest_id.usage != 'internal'): - err = True - if (move.location_id.usage != 'internal' and - move.location_dest_id.usage != 'internal'): - err = True - if err: - raise exceptions.UserError( - _('Transfers between locations of different operating ' - 'unit locations is only allowed when both source ' - 'and destination locations are internal.') - ) - src_company_ctx = dict( - force_company=move.location_id.company_id.id - ) - company_ctx = dict(company_id=move.company_id.id) - self_c = self.with_context(src_company_ctx) - data = self_c._get_accounting_data_for_valuation(move) - (journal_id, acc_src, acc_dest, acc_valuation) = data - quant_cost_qty = {} - for quant in quants: - if quant_cost_qty.get(quant.cost): - quant_cost_qty[quant.cost] += quant.qty - else: - quant_cost_qty[quant.cost] = quant.qty - move_obj = self.env['account.move'] - for cost, qty in quant_cost_qty.items(): - move_lines = self._prepare_account_move_line(move, qty, - cost, - acc_valuation, - acc_valuation) - move_obj.with_context(company_ctx).create({ - 'journal_id': journal_id, - 'line_ids': move_lines, - 'company_id': move.company_id.id, - 'ref': move.picking_id and move.picking_id.name, - }) - return res diff --git a/stock_account_operating_unit/model/stock_move.py b/stock_account_operating_unit/model/stock_move.py new file mode 100644 index 0000000000..f52722be15 --- /dev/null +++ b/stock_account_operating_unit/model/stock_move.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# © 2015-17 Eficent Business and IT Consulting Services S.L. +# - Jordi Ballester Alomar +# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from openerp import _, api, exceptions, models + + +class StockMove(models.Model): + _inherit = "stock.move" + + @api.model + def _prepare_account_move_line(self, qty, cost, credit_account_id, + debit_account_id): + res = super(StockMove, self)._prepare_account_move_line( + qty, cost, credit_account_id, debit_account_id) + if res: + debit_line_vals = res[0][2] + credit_line_vals = res[1][2] + + if ( + self.operating_unit_id and self.operating_unit_dest_id and + self.operating_unit_id != self.operating_unit_dest_id and + debit_line_vals['account_id'] != credit_line_vals['account_id'] + ): + raise exceptions.UserError( + _('You cannot create stock moves involving separate source' + ' and destination accounts related to different ' + 'operating units.') + ) + + debit_line_vals['operating_unit_id'] = ( + self.operating_unit_dest_id.id or self.operating_unit_id.id + ) + credit_line_vals['operating_unit_id'] = ( + self.operating_unit_id.id or self.operating_unit_dest_id.id + ) + return [(0, 0, debit_line_vals), (0, 0, credit_line_vals)] + return res diff --git a/stock_account_operating_unit/model/stock_quant.py b/stock_account_operating_unit/model/stock_quant.py new file mode 100644 index 0000000000..5026113852 --- /dev/null +++ b/stock_account_operating_unit/model/stock_quant.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# © 2015-17 Eficent Business and IT Consulting Services S.L. +# - Jordi Ballester Alomar +# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from openerp import api, models + + +class StockQuant(models.Model): + _inherit = 'stock.quant' + + @api.multi + def _account_entry_move(self, move): + """ + Generate accounting moves if the product being moved is subject + to real_time valuation tracking, + and the source or destination location are + a transit location or is outside of the company or the source or + destination locations belong to different operating units. + """ + res = super(StockQuant, self)._account_entry_move(move) + if move.product_id.valuation == 'real_time': + # Inter-operating unit moves do not accept to + # from/to non-internal location + if ( + move.location_id.company_id == + move.location_dest_id.company_id and + move.operating_unit_id != move.operating_unit_dest_id + ): + src_company_ctx = dict( + force_company=move.location_id.company_id.id + ) + company_ctx = dict(company_id=move.company_id.id) + self = self.with_context(src_company_ctx) + (journal_id, acc_src, acc_dest, acc_valuation) = \ + move._get_accounting_data_for_valuation() + quant_cost_qty = {} + for quant in self: + if quant_cost_qty.get(quant.cost): + quant_cost_qty[quant.cost] += quant.qty + else: + quant_cost_qty[quant.cost] = quant.qty + move_obj = self.env['account.move'] + for cost, qty in quant_cost_qty.items(): + move_lines = move._prepare_account_move_line(qty, cost, + acc_valuation, + acc_valuation) + move_obj.with_context(company_ctx).create({ + 'journal_id': journal_id, + 'line_ids': move_lines, + 'company_id': move.company_id.id, + 'ref': move.picking_id and move.picking_id.name, + }) + return res From c6767827155c8aefddbe3bd49951daf43afa107b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Jimenez?= Date: Fri, 8 Feb 2019 12:51:52 +0100 Subject: [PATCH 3/4] [MIG] stock_account_operating_unit: Migration to v12 --- stock_account_operating_unit/README.rst | 84 ++-- stock_account_operating_unit/__init__.py | 4 - stock_account_operating_unit/__manifest__.py | 7 +- .../model/__init__.py | 5 - .../model/stock_move.py | 83 +++- .../model/stock_quant.py | 54 --- .../readme/CONFIGURATION.rst | 11 + .../readme/CONTRIBUTORS.rst | 2 + .../readme/DESCRIPTION.rst | 4 + stock_account_operating_unit/readme/USAGE.rst | 3 + .../static/description/index.html | 432 ++++++++++++++++++ .../tests/__init__.py | 4 - .../test_stock_account_operating_unit.py | 153 +++---- 13 files changed, 637 insertions(+), 209 deletions(-) delete mode 100644 stock_account_operating_unit/model/stock_quant.py create mode 100644 stock_account_operating_unit/readme/CONFIGURATION.rst create mode 100644 stock_account_operating_unit/readme/CONTRIBUTORS.rst create mode 100644 stock_account_operating_unit/readme/DESCRIPTION.rst create mode 100644 stock_account_operating_unit/readme/USAGE.rst create mode 100644 stock_account_operating_unit/static/description/index.html diff --git a/stock_account_operating_unit/README.rst b/stock_account_operating_unit/README.rst index 32a6f2a5d1..0d3dd6638f 100644 --- a/stock_account_operating_unit/README.rst +++ b/stock_account_operating_unit/README.rst @@ -1,34 +1,39 @@ -.. image:: https://img.shields.io/badge/license-LGPLv3-blue.svg - :target: https://www.gnu.org/licenses/lgpl.html - :alt: License: LGPL-3 - ======================================= Stock account moves with Operating Unit ======================================= +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Foperating--unit-lightgray.png?logo=github + :target: https://github.com/OCA/operating-unit/tree/12.0/stock_account_operating_unit + :alt: OCA/operating-unit +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/operating-unit-12-0/operating-unit-12-0-stock_account_operating_unit + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/213/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + This module introduces the following features: - Creates account move lines when stock moves are posted between internal locations within the same company, but different OU’s. +**Table of contents** -Configuration -============= - -If your company is required to generate a balanced balance sheet by -operating unit you can specify at company level that operating units should -be self-balanced, and then indicate a self-balancing clearing account. - -#. Create an account for "Inter-OU Clearing" of type Regular. -#. Go to *Settings / Companies / Configuration* and: - - * Set the "Operating Units are self-balanced" checkbox. - - * Set the "Inter-OU Clearing" account in "Inter-operating unit clearing - account" field. - -#. Assign Operating Unit in Accounts. - +.. contents:: + :local: Usage ===== @@ -37,43 +42,44 @@ Create stock moves between internal locations within the same company, but different OU’s. The journal entries are created and they are self-balanced within the OU when the journal entries are posted -.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas - :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/213/10.0 - 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. +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 `_. + +Do not contact contributors directly about support or help with technical issues. Credits ======= -Images ------- +Authors +~~~~~~~ -* Odoo Community Association: `Icon `_. +* Eficent Business and IT Consulting Services S.L. +* Serpent Consulting Services Pvt. Ltd. Contributors ------------- +~~~~~~~~~~~~ * Eficent Business and IT Consulting Services S.L. * Serpent Consulting Services Pvt. Ltd. -Maintainer ----------- +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. .. image:: https://odoo-community.org/logo.png :alt: Odoo Community Association - :target: http://odoo-community.org - -This module is maintained by the OCA. + :target: https://odoo-community.org OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -To contribute to this module, please visit http://odoo-community.org. +This module is part of the `OCA/operating-unit `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_account_operating_unit/__init__.py b/stock_account_operating_unit/__init__.py index 209c94a68c..50170638d0 100644 --- a/stock_account_operating_unit/__init__.py +++ b/stock_account_operating_unit/__init__.py @@ -1,6 +1,2 @@ -# -*- coding: utf-8 -*- -# © 2015-17 Eficent Business and IT Consulting Services S.L. -# - Jordi Ballester Alomar -# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). from . import model diff --git a/stock_account_operating_unit/__manifest__.py b/stock_account_operating_unit/__manifest__.py index abc1cc1d13..82c3cc8b7c 100644 --- a/stock_account_operating_unit/__manifest__.py +++ b/stock_account_operating_unit/__manifest__.py @@ -1,14 +1,13 @@ -# -*- coding: utf-8 -*- -# © 2015-17 Eficent Business and IT Consulting Services S.L. +# © 2019 Eficent Business and IT Consulting Services S.L. # - Jordi Ballester Alomar -# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# © 2019 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). { "name": "Stock account moves with Operating Unit", "summary": "Create journal entries in moves between internal locations " "with different operating units.", - "version": "10.0.1.0.0", + "version": "12.0.1.0.0", "category": "Generic Modules/Sales & Purchases", "author": "Eficent Business and IT Consulting Services S.L., " "Serpent Consulting Services Pvt. Ltd.," diff --git a/stock_account_operating_unit/model/__init__.py b/stock_account_operating_unit/model/__init__.py index 6c3815ec4c..6ac287cbfc 100644 --- a/stock_account_operating_unit/model/__init__.py +++ b/stock_account_operating_unit/model/__init__.py @@ -1,7 +1,2 @@ -# -*- coding: utf-8 -*- -# © 2015-17 Eficent Business and IT Consulting Services S.L. -# - Jordi Ballester Alomar -# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). from . import stock_move -from . import stock_quant diff --git a/stock_account_operating_unit/model/stock_move.py b/stock_account_operating_unit/model/stock_move.py index f52722be15..d1f14b9618 100644 --- a/stock_account_operating_unit/model/stock_move.py +++ b/stock_account_operating_unit/model/stock_move.py @@ -1,22 +1,23 @@ -# -*- coding: utf-8 -*- -# © 2015-17 Eficent Business and IT Consulting Services S.L. +# © 2019 Eficent Business and IT Consulting Services S.L. # - Jordi Ballester Alomar -# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# © 2019 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). -from openerp import _, api, exceptions, models +from odoo import _, api, exceptions, models class StockMove(models.Model): _inherit = "stock.move" - @api.model - def _prepare_account_move_line(self, qty, cost, credit_account_id, - debit_account_id): - res = super(StockMove, self)._prepare_account_move_line( - qty, cost, credit_account_id, debit_account_id) + def _generate_valuation_lines_data( + self, partner_id, qty, debit_value, credit_value, debit_account_id, + credit_account_id): + res = super(StockMove, self)._generate_valuation_lines_data( + partner_id, qty, debit_value, credit_value, debit_account_id, + credit_account_id) if res: - debit_line_vals = res[0][2] - credit_line_vals = res[1][2] + debit_line_vals = res.get('debit_line_vals') + credit_line_vals = res.get('credit_line_vals') + price_diff_line_vals = res.get('price_diff_line_vals', {}) if ( self.operating_unit_id and self.operating_unit_dest_id and @@ -29,11 +30,61 @@ def _prepare_account_move_line(self, qty, cost, credit_account_id, 'operating units.') ) - debit_line_vals['operating_unit_id'] = ( + if (not self.operating_unit_dest_id + and not self.operating_unit_id): + ou_id = self.picking_id.picking_type_id.warehouse_id. \ + operating_unit_id.id + else: + ou_id = False + + debit_line_vals['operating_unit_id'] = ou_id or \ self.operating_unit_dest_id.id or self.operating_unit_id.id - ) - credit_line_vals['operating_unit_id'] = ( + credit_line_vals['operating_unit_id'] = ou_id or \ self.operating_unit_id.id or self.operating_unit_dest_id.id - ) - return [(0, 0, debit_line_vals), (0, 0, credit_line_vals)] + rslt = {'credit_line_vals': credit_line_vals, + 'debit_line_vals': debit_line_vals} + if price_diff_line_vals: + price_diff_line_vals['operating_unit_id'] = ou_id or \ + self.operating_unit_id.id or self.operating_unit_dest_id.id + rslt['price_diff_line_vals'] = price_diff_line_vals + return rslt return res + + @api.multi + def _action_done(self): + """ + Generate accounting moves if the product being moved is subject + to real_time valuation tracking, + and the source or destination location are + a transit location or is outside of the company or the source or + destination locations belong to different operating units. + """ + res = super(StockMove, self)._action_done() + for move in self: + + if move.product_id.valuation == 'real_time': + # Inter-operating unit moves do not accept to + # from/to non-internal location + if (move.location_id.company_id and + move.location_id.company_id == + move.location_dest_id.company_id and + move.operating_unit_id != move.operating_unit_dest_id): + (journal_id, acc_src, acc_dest, acc_valuation) = \ + move._get_accounting_data_for_valuation() + + move_lines = move._prepare_account_move_line( + move.product_qty, + move.product_id.standard_price, + acc_valuation, + acc_valuation) + am = self.env["account.move"].with_context( + force_company=move.location_id.company_id.id, + company_id=move.company_id.id).create({ + 'journal_id': journal_id, + 'line_ids': move_lines, + 'company_id': move.company_id.id, + 'ref': move.picking_id and move.picking_id.name, + 'stock_move_id': self.id, + }) + am.post() + return res diff --git a/stock_account_operating_unit/model/stock_quant.py b/stock_account_operating_unit/model/stock_quant.py deleted file mode 100644 index 5026113852..0000000000 --- a/stock_account_operating_unit/model/stock_quant.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# © 2015-17 Eficent Business and IT Consulting Services S.L. -# - Jordi Ballester Alomar -# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya -# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). -from openerp import api, models - - -class StockQuant(models.Model): - _inherit = 'stock.quant' - - @api.multi - def _account_entry_move(self, move): - """ - Generate accounting moves if the product being moved is subject - to real_time valuation tracking, - and the source or destination location are - a transit location or is outside of the company or the source or - destination locations belong to different operating units. - """ - res = super(StockQuant, self)._account_entry_move(move) - if move.product_id.valuation == 'real_time': - # Inter-operating unit moves do not accept to - # from/to non-internal location - if ( - move.location_id.company_id == - move.location_dest_id.company_id and - move.operating_unit_id != move.operating_unit_dest_id - ): - src_company_ctx = dict( - force_company=move.location_id.company_id.id - ) - company_ctx = dict(company_id=move.company_id.id) - self = self.with_context(src_company_ctx) - (journal_id, acc_src, acc_dest, acc_valuation) = \ - move._get_accounting_data_for_valuation() - quant_cost_qty = {} - for quant in self: - if quant_cost_qty.get(quant.cost): - quant_cost_qty[quant.cost] += quant.qty - else: - quant_cost_qty[quant.cost] = quant.qty - move_obj = self.env['account.move'] - for cost, qty in quant_cost_qty.items(): - move_lines = move._prepare_account_move_line(qty, cost, - acc_valuation, - acc_valuation) - move_obj.with_context(company_ctx).create({ - 'journal_id': journal_id, - 'line_ids': move_lines, - 'company_id': move.company_id.id, - 'ref': move.picking_id and move.picking_id.name, - }) - return res diff --git a/stock_account_operating_unit/readme/CONFIGURATION.rst b/stock_account_operating_unit/readme/CONFIGURATION.rst new file mode 100644 index 0000000000..a4a154d498 --- /dev/null +++ b/stock_account_operating_unit/readme/CONFIGURATION.rst @@ -0,0 +1,11 @@ +If your company is required to generate a balanced balance sheet by +operating unit you can specify at company level that operating units should +be self-balanced, and then indicate a self-balancing clearing account. + +#. Create an account for "Inter-OU Clearing" of type Regular. +#. Go to *Settings / Companies / Configuration* and: + + * Set the "Operating Units are self-balanced" checkbox. + * Set the "Inter-OU Clearing" account in "Inter-operating unit clearing + account" field. +#. Assign Operating Unit in Accounts. diff --git a/stock_account_operating_unit/readme/CONTRIBUTORS.rst b/stock_account_operating_unit/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..6f26312176 --- /dev/null +++ b/stock_account_operating_unit/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Eficent Business and IT Consulting Services S.L. +* Serpent Consulting Services Pvt. Ltd. diff --git a/stock_account_operating_unit/readme/DESCRIPTION.rst b/stock_account_operating_unit/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..2e9ddaf403 --- /dev/null +++ b/stock_account_operating_unit/readme/DESCRIPTION.rst @@ -0,0 +1,4 @@ +This module introduces the following features: + +- Creates account move lines when stock moves are posted between internal + locations within the same company, but different OU’s. diff --git a/stock_account_operating_unit/readme/USAGE.rst b/stock_account_operating_unit/readme/USAGE.rst new file mode 100644 index 0000000000..419dbd3b53 --- /dev/null +++ b/stock_account_operating_unit/readme/USAGE.rst @@ -0,0 +1,3 @@ +Create stock moves between internal locations within the same company, but +different OU’s. The journal entries are created and they are self-balanced +within the OU when the journal entries are posted diff --git a/stock_account_operating_unit/static/description/index.html b/stock_account_operating_unit/static/description/index.html new file mode 100644 index 0000000000..13cd2e6f7f --- /dev/null +++ b/stock_account_operating_unit/static/description/index.html @@ -0,0 +1,432 @@ + + + + + + +Stock account moves with Operating Unit + + + +
+

Stock account moves with Operating Unit

+ + +

Beta License: LGPL-3 OCA/operating-unit Translate me on Weblate Try me on Runbot

+

This module introduces the following features:

+
    +
  • Creates account move lines when stock moves are posted between internal +locations within the same company, but different OU’s.
  • +
+

Table of contents

+ +
+

Usage

+

Create stock moves between internal locations within the same company, but +different OU’s. The journal entries are created and they are self-balanced +within the OU when the journal entries are posted

+
+
+

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.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Eficent Business and IT Consulting Services S.L.
  • +
  • Serpent Consulting Services Pvt. Ltd.
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/operating-unit project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/stock_account_operating_unit/tests/__init__.py b/stock_account_operating_unit/tests/__init__.py index 9f4fa86db7..604c5f9c2e 100644 --- a/stock_account_operating_unit/tests/__init__.py +++ b/stock_account_operating_unit/tests/__init__.py @@ -1,6 +1,2 @@ -# -*- coding: utf-8 -*- -# © 2015-17 Eficent Business and IT Consulting Services S.L. - -# Jordi Ballester Alomar -# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from . import test_stock_account_operating_unit diff --git a/stock_account_operating_unit/tests/test_stock_account_operating_unit.py b/stock_account_operating_unit/tests/test_stock_account_operating_unit.py index 5f7ef6a39f..22fbffe462 100644 --- a/stock_account_operating_unit/tests/test_stock_account_operating_unit.py +++ b/stock_account_operating_unit/tests/test_stock_account_operating_unit.py @@ -1,12 +1,11 @@ -# -*- coding: utf-8 -*- -# © 2015-17 Eficent Business and IT Consulting Services S.L. - -# Jordi Ballester Alomar -# © 2015-17 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from openerp.addons.stock.tests import common +# © 2019 Eficent Business and IT Consulting Services S.L. +# - Jordi Ballester Alomar +# © 2019 Serpent Consulting Services Pvt. Ltd. - Sudhir Arya +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from odoo.addons.stock.tests.common import TestStockCommon -class TestStockAccountOperatingUnit(common.TestStockCommon): +class TestStockAccountOperatingUnit(TestStockCommon): def setUp(self): super(TestStockAccountOperatingUnit, self).setUp() @@ -22,95 +21,86 @@ def setUp(self): self.company_model = self.env['res.company'] self.move_model = self.env['stock.move'] self.picking_model = self.env['stock.picking'] - self.ModelDataObj = self.env['ir.model.data'] - # company - self.company = self.ModelDataObj.xmlid_to_res_id('base.main_company') - # groups - self.group_stock_manager =\ - self.ModelDataObj.xmlid_to_res_id('stock.group_stock_manager') - self.grp_acc_user =\ - self.ModelDataObj.xmlid_to_res_id('account.group_account_invoice') - self.grp_stock_user =\ - self.ModelDataObj.xmlid_to_res_id('stock.group_stock_user') + + # Company + self.company = self.env.ref('base.main_company') + self.group_stock_manager = self.env.ref('stock.group_stock_manager') + self.grp_acc_user = self.env.ref('account.group_account_invoice') + self.grp_stock_user = self.env.ref('stock.group_stock_user') # Main Operating Unit - self.ou1 = self.ModelDataObj.get_object('operating_unit', - 'main_operating_unit') + self.ou1 = self.env.ref('operating_unit.main_operating_unit') # B2B Operating Unit - self.b2b = self.ModelDataObj.get_object('operating_unit', - 'b2b_operating_unit') + self.b2b = self.env.ref('operating_unit.b2b_operating_unit') # B2C Operating Unit - self.b2c = self.ModelDataObj.get_object('operating_unit', - 'b2c_operating_unit') + self.b2c = self.env.ref('operating_unit.b2c_operating_unit') # Partner - self.partner1 = self.ModelDataObj.xmlid_to_res_id('base.res_partner_1') - self.stock_location_stock =\ - self.ModelDataObj.xmlid_to_res_id('stock.stock_location_stock') - self.supplier_location =\ - self.ModelDataObj.xmlid_to_res_id('stock.stock_location_suppliers') + self.partner1 = self.env.ref('base.res_partner_1') + self.stock_location_stock = self.env.ref('stock.stock_location_stock') + self.supplier_location = self.env.ref('stock.stock_location_suppliers') + # Create user1 self.user1 =\ self._create_user('stock_account_user_1', [self.grp_stock_user, self.grp_acc_user, self.group_stock_manager], - self.company, [self.ou1.id, self.b2c.id]) + self.company, [self.ou1, self.b2c]) # Create user2 self.user2 =\ self._create_user('stock_account_user_2', [self.grp_stock_user, self.grp_acc_user, self.group_stock_manager], - self.company, [self.b2c.id]) + self.company, [self.b2c]) # Create account for Goods Received Not Invoiced name = 'Goods Received Not Invoiced' code = 'grni' acc_type = self.env.ref('account.data_account_type_equity') - self.account_grni = self._create_account(acc_type, name, code, - self.company) -# # Create account for Cost of Goods Sold + self.account_grni = self._create_account( + acc_type, name, code, self.company) + # Create account for Cost of Goods Sold name = 'Cost of Goods Sold' code = 'cogs' acc_type = self.env.ref('account.data_account_type_expenses') - self.account_cogs_id = self._create_account(acc_type, name, code, - self.company) -# # Create account for Inventory + self.account_cogs_id = self._create_account( + acc_type, name, code, self.company) + # Create account for Inventory name = 'Inventory' code = 'inventory' acc_type = self.env.ref('account.data_account_type_fixed_assets') - self.account_inventory = self._create_account(acc_type, name, code, - self.company) -# # Create account for Inter-OU Clearing + self.account_inventory = self._create_account( + acc_type, name, code, self.company) + # Create account for Inter-OU Clearing name = 'Inter-OU Clearing' code = 'inter_ou' acc_type = self.env.ref('account.data_account_type_equity') - self.account_inter_ou_clearing = self._create_account(acc_type, - name, code, - self.company) + self.account_inter_ou_clearing = self._create_account( + acc_type, name, code, self.company) # Update company data - company = self.env.ref('base.main_company') - company.write({'inter_ou_clearing_account_id': - self.account_inter_ou_clearing.id, - 'ou_is_self_balanced': True}) -# Create Product + self.company.write({ + 'inter_ou_clearing_account_id': self.account_inter_ou_clearing.id, + 'ou_is_self_balanced': True}) + + # Create Product self.product = self._create_product() -# Create incoming stock picking type - self.incoming_id = self.env.ref('stock.warehouse0').in_type_id.id -# Create incoming and internal stock picking types + # Create incoming stock picking type + self.incoming_id = self.env.ref('stock.warehouse0').in_type_id + # Create incoming and internal stock picking types b2c_wh = self.env.ref('stock_operating_unit.stock_warehouse_b2c') b2c_wh.lot_stock_id.write({'operating_unit_id': self.b2c.id}) - self.location_b2c_id = b2c_wh.lot_stock_id.id - self.b2c_type_in_id = b2c_wh.in_type_id.id - self.b2c_type_int_id = b2c_wh.int_type_id.id + self.location_b2c_id = b2c_wh.lot_stock_id + self.b2c_type_in_id = b2c_wh.in_type_id + self.b2c_type_int_id = b2c_wh.int_type_id def _create_user(self, login, groups, company, operating_units): """Create a user.""" - group_ids = [group for group in groups] + group_ids = [group.id for group in groups] user = self.res_users_model.create({ 'name': 'Test Stock Account User', 'login': login, 'password': 'demo', 'email': 'example@yourcompany.com', - 'company_id': company, - 'company_ids': [(4, company)], - 'operating_unit_ids': [(4, ou) for ou in operating_units], + 'company_id': company.id, + 'company_ids': [(4, company.id)], + 'operating_unit_ids': [(4, ou.id) for ou in operating_units], 'groups_id': [(6, 0, group_ids)] }) return user @@ -120,8 +110,8 @@ def _create_account(self, acc_type, name, code, company): account = self.account_model.create({ 'name': name, 'code': code, - 'user_type_id': acc_type.ids and acc_type.ids[0], - 'company_id': company + 'user_type_id': acc_type.id, + 'company_id': company.id }) return account @@ -143,33 +133,33 @@ def _create_product(self): }) return product - def _create_picking(self, user_id, ou_id, picking_type, + def _create_picking(self, user, ou_id, picking_type, src_loc_id, dest_loc_id): """Create a Picking.""" - picking = self.picking_model.sudo(user_id).create({ - 'picking_type_id': picking_type, - 'location_id': src_loc_id, - 'location_dest_id': dest_loc_id, - 'operating_unit_id': ou_id, + picking = self.picking_model.sudo(user.id).create({ + 'picking_type_id': picking_type.id, + 'location_id': src_loc_id.id, + 'location_dest_id': dest_loc_id.id, + 'operating_unit_id': ou_id.id, }) - self.move_model.sudo(user_id).create({ + self.move_model.sudo(user.id).create({ 'name': 'a move', 'product_id': self.product.id, 'product_uom_qty': 1.0, 'product_uom': 1, 'picking_id': picking.id, - 'location_id': src_loc_id, - 'location_dest_id': dest_loc_id, + 'location_id': src_loc_id.id, + 'location_dest_id': dest_loc_id.id, }) return picking - def _confirm_receive(self, user_id, picking, picking_type=None): + def _confirm_receive(self, user_id, picking): """ Checks the stock availability, validates and process the stock picking. """ picking.action_confirm() - picking.force_assign() - res = picking.sudo(user_id).do_new_transfer() + picking.action_assign() + res = picking.sudo(user_id).button_validate() validate_id = res['res_id'] validate = self.env['stock.immediate.transfer'].browse(validate_id) validate.process() @@ -200,11 +190,10 @@ def _get_balance(self, domain): """ Call read_group method and return the balance of particular account. """ - aml_rec = self.aml_model.read_group(domain, - ['debit', 'credit', 'account_id'], - ['account_id']) + aml_rec = self.aml_model.read_group( + domain, ['debit', 'credit', 'account_id'], ['account_id']) if aml_rec: - return aml_rec[0].get('debit', 0) - aml_rec[0].get('credit', 0) + return aml_rec[0].get('debit', 0.0) - aml_rec[0].get('credit', 0.0) else: return 0.0 @@ -214,7 +203,7 @@ def test_pickings(self): from main ou to b2c.""" # Create Incoming Shipment 1 self.picking = self._create_picking( - self.user1.id, self.ou1.id, self.incoming_id, + self.user1, self.ou1, self.incoming_id, self.supplier_location, self.stock_location_stock) # Receive it self._confirm_receive(self.user1.id, self.picking) @@ -228,7 +217,7 @@ def test_pickings(self): self._check_account_balance(self.account_inventory.id, operating_unit=self.ou1, expected_balance=expected_balance) - # GL account ‘Inventory’ has balance 0 on OU main_operating_unit + # GL account ‘Inventory’ has balance 0 on OU B2C expected_balance = 0.0 self._check_account_balance(self.account_inventory.id, operating_unit=self.b2c, @@ -252,12 +241,12 @@ def test_pickings(self): # Create Incoming Shipment 2 self.picking =\ - self._create_picking(self.user2.id, self.b2c.id, + self._create_picking(self.user2, self.b2c, self.b2c_type_in_id, self.supplier_location, self.location_b2c_id) -# # Receive it + # Receive it self._confirm_receive(self.user2.id, self.picking) # GL account ‘Inventory’ has balance 2 irrespective of the OU @@ -294,14 +283,12 @@ def test_pickings(self): # Create Internal Transfer self.picking =\ - self._create_picking(self.user1.id, self.b2c.id, + self._create_picking(self.user1, self.b2c, self.b2c_type_int_id, self.stock_location_stock, self.location_b2c_id) # Receive it - picking_type = 'internal' - self._confirm_receive(self.user1.id, self.picking, - picking_type=picking_type) + self._confirm_receive(self.user1.id, self.picking) # GL account ‘Inventory’ has balance 2 irrespective of the OU expected_balance = 2.0 self._check_account_balance(self.account_inventory.id, From 4537e1813a7b3a5a4f6355ae6bc6394407a60e9b Mon Sep 17 00:00:00 2001 From: mreficent Date: Wed, 27 Nov 2019 19:09:33 +0100 Subject: [PATCH 4/4] [FIX] Alignments --- .../models/account_invoice.py | 16 ++++++++-------- .../models/account_journal.py | 12 ++++++------ account_operating_unit/models/account_move.py | 17 ++++++++++------- .../models/account_payment.py | 2 +- stock_operating_unit/model/stock_warehouse.py | 8 ++++---- 5 files changed, 29 insertions(+), 26 deletions(-) diff --git a/account_operating_unit/models/account_invoice.py b/account_operating_unit/models/account_invoice.py index 399b594ec9..7b5bcde49b 100644 --- a/account_operating_unit/models/account_invoice.py +++ b/account_operating_unit/models/account_invoice.py @@ -10,14 +10,14 @@ class AccountInvoice(models.Model): _inherit = "account.invoice" - operating_unit_id = fields.Many2one('operating.unit', 'Operating Unit', - default=lambda self: - self.env['res.users']. - operating_unit_default_get(self._uid), - domain="[('user_ids', '=', uid)]", - readonly=True, - states={'draft': [('readonly', - False)]}) + operating_unit_id = fields.Many2one( + comodel_name='operating.unit', + string='Operating Unit', + default=lambda self: self.env['res.users'].operating_unit_default_get( + self._uid), + domain="[('user_ids', '=', uid)]", + readonly=True, + states={'draft': [('readonly', False)]}) @api.onchange('operating_unit_id') def _onchange_operating_unit(self): diff --git a/account_operating_unit/models/account_journal.py b/account_operating_unit/models/account_journal.py index f8bad2f4bf..cb7fdf738a 100644 --- a/account_operating_unit/models/account_journal.py +++ b/account_operating_unit/models/account_journal.py @@ -9,12 +9,12 @@ class AccountJournal(models.Model): _inherit = "account.journal" - operating_unit_id = fields.Many2one(comodel_name='operating.unit', - string='Operating Unit', - domain="[('user_ids', '=', uid)]", - help="Operating Unit that will be " - "used in payments, when this " - "journal is used.") + operating_unit_id = fields.Many2one( + comodel_name='operating.unit', + string='Operating Unit', + domain="[('user_ids', '=', uid)]", + help="Operating Unit that will be used in payments, " + "when this journal is used.") @api.multi @api.constrains('type') diff --git a/account_operating_unit/models/account_move.py b/account_operating_unit/models/account_move.py index dbe701aa56..d68eab0c7a 100644 --- a/account_operating_unit/models/account_move.py +++ b/account_operating_unit/models/account_move.py @@ -9,8 +9,10 @@ class AccountMoveLine(models.Model): _inherit = "account.move.line" - operating_unit_id = fields.Many2one('operating.unit', 'Operating Unit', - domain="[('user_ids', '=', uid)]",) + operating_unit_id = fields.Many2one( + comodel_name='operating.unit', + string='Operating Unit', + domain="[('user_ids', '=', uid)]",) @api.model def create(self, vals): @@ -54,11 +56,12 @@ def _check_move_operating_unit(self): class AccountMove(models.Model): _inherit = "account.move" - operating_unit_id = fields.Many2one('operating.unit', - 'Default operating unit', - domain="[('user_ids', '=', uid)]", - help="This operating unit will " - "be defaulted in the move lines.") + operating_unit_id = fields.Many2one( + comodel_name='operating.unit', + string='Default operating unit', + domain="[('user_ids', '=', uid)]", + help="This operating unit will be " + "defaulted in the move lines.") @api.multi def _prepare_inter_ou_balancing_move_line(self, move, ou_id, diff --git a/account_operating_unit/models/account_payment.py b/account_operating_unit/models/account_payment.py index b17e9ba693..d19bb7167b 100644 --- a/account_operating_unit/models/account_payment.py +++ b/account_operating_unit/models/account_payment.py @@ -23,7 +23,7 @@ def _compute_operating_unit_id(self): def _get_counterpart_move_line_vals(self, invoice=False): res = super(AccountPayment, self)._get_counterpart_move_line_vals(invoice=invoice) - if len(invoice) == 1: + if invoice and len(invoice) == 1: res['operating_unit_id'] = invoice.operating_unit_id.id or False else: res['operating_unit_id'] = self.operating_unit_id.id or False diff --git a/stock_operating_unit/model/stock_warehouse.py b/stock_operating_unit/model/stock_warehouse.py index f1832f739f..d3daa77f3a 100644 --- a/stock_operating_unit/model/stock_warehouse.py +++ b/stock_operating_unit/model/stock_warehouse.py @@ -46,7 +46,7 @@ def _check_location(self): if (rec.warehouse_id and rec.location_id and rec.warehouse_id.operating_unit_id != rec.location_id.operating_unit_id): - raise UserError( - _('Configuration Error. The Operating Unit of the ' - 'Warehouse and the Location must be the same. ') - ) + raise UserError( + _('Configuration Error. The Operating Unit of the ' + 'Warehouse and the Location must be the same. ') + )