From 6294d8149e675b953fe9306de61aeaa48c2b0086 Mon Sep 17 00:00:00 2001 From: chafique-delli Date: Mon, 17 Oct 2016 19:11:31 +0200 Subject: [PATCH 01/87] [ADD] purchase_sale_inter_company module --- purchase_sale_inter_company/README.rst | 66 ++++++++ purchase_sale_inter_company/__init__.py | 2 + purchase_sale_inter_company/__openerp__.py | 29 ++++ purchase_sale_inter_company/config.yml | 12 ++ .../models/__init__.py | 5 + .../models/purchase_order.py | 154 ++++++++++++++++++ .../models/res_company.py | 50 ++++++ .../models/res_config.py | 28 ++++ .../models/sale_order.py | 13 ++ .../static/description/icon.png | Bin 0 -> 3735 bytes .../test/inter_company_po_to_so.yml | 42 +++++ .../test/test_intercompany_data.yml | 153 +++++++++++++++++ .../views/inter_company_po_to_so_view.xml | 20 +++ .../views/res_config_view.xml | 21 +++ 14 files changed, 595 insertions(+) create mode 100644 purchase_sale_inter_company/README.rst create mode 100644 purchase_sale_inter_company/__init__.py create mode 100644 purchase_sale_inter_company/__openerp__.py create mode 100644 purchase_sale_inter_company/config.yml create mode 100644 purchase_sale_inter_company/models/__init__.py create mode 100644 purchase_sale_inter_company/models/purchase_order.py create mode 100644 purchase_sale_inter_company/models/res_company.py create mode 100644 purchase_sale_inter_company/models/res_config.py create mode 100644 purchase_sale_inter_company/models/sale_order.py create mode 100644 purchase_sale_inter_company/static/description/icon.png create mode 100644 purchase_sale_inter_company/test/inter_company_po_to_so.yml create mode 100644 purchase_sale_inter_company/test/test_intercompany_data.yml create mode 100644 purchase_sale_inter_company/views/inter_company_po_to_so_view.xml create mode 100644 purchase_sale_inter_company/views/res_config_view.xml diff --git a/purchase_sale_inter_company/README.rst b/purchase_sale_inter_company/README.rst new file mode 100644 index 00000000000..5db23f49e82 --- /dev/null +++ b/purchase_sale_inter_company/README.rst @@ -0,0 +1,66 @@ + +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +=========================== +Purchase Sale Inter Company +=========================== + +This module is usefull if there are multiple companies in the same Odoo database and those companies sell goods or services among themselves. + +Imagine you have company A and company B in the same Odoo database and company A purchase goods from company B: company A will create a purchase.order with company B as supplier, and company B will have to create a sale order with company A as customer. This module automate the creation of the sale order in company B. + + +Configuration +============= + +To configure this module, you need to go to the menu *Settings > Companies > Companies*, select one of the companies and go to the tab *Inter-Company*. You have to choose which scenario you want to have for interactions between companies: either automate the creation of sale.order between companies. + +Another important configuration is the *Inter Company User* : it is the user that will be used to automatically generate the corresponding object in the other company. One important thing to understand is that the fields *Customer Taxes* (technical field field: *taxes_id*) and *Supplier Taxes* (technical field field: *supplier_taxes_id*) on product.template are **NOT** property fields (in new API, we would say: *company_dependant=False*). So you cannot select the administrator as *Inter Company User* because this user by-passes the record rules ; you have to select a regular user that is attached to the company and only to this company (don't select a user that is allowed to switch between companies). + +Usage +===== + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/133/8.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 +======= + +Contributors +------------ + +* Odoo S.A. +* Chafique Delli +* Alexis de Lattre +* Lorenzo Battistini + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://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 https://odoo-community.org. diff --git a/purchase_sale_inter_company/__init__.py b/purchase_sale_inter_company/__init__.py new file mode 100644 index 00000000000..a0fdc10fe11 --- /dev/null +++ b/purchase_sale_inter_company/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/purchase_sale_inter_company/__openerp__.py b/purchase_sale_inter_company/__openerp__.py new file mode 100644 index 00000000000..d3d75d11033 --- /dev/null +++ b/purchase_sale_inter_company/__openerp__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# © 2013-Today Odoo SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + 'name': 'Inter Company Module for Purchase to Sale Orders', + 'summary': 'Intercompany PO/SO rules', + 'version': '8.0.1.0.0', + 'category': 'Purchase Management', + 'website': 'http://www.odoo.com', + 'author': 'Odoo SA, Akretion, Odoo Community Association (OCA)', + 'license': 'AGPL-3', + 'installable': True, + 'depends': [ + 'sale', + 'purchase', + 'sale_stock', + 'sale_order_dates' + ], + 'data': [ + 'views/inter_company_po_to_so_view.xml', + 'views/res_config_view.xml', + 'config.yml' + ], + 'test': [ + 'test/test_intercompany_data.yml', + 'test/inter_company_po_to_so.yml', + ], +} diff --git a/purchase_sale_inter_company/config.yml b/purchase_sale_inter_company/config.yml new file mode 100644 index 00000000000..df5c8725724 --- /dev/null +++ b/purchase_sale_inter_company/config.yml @@ -0,0 +1,12 @@ +- + Base config +- + !python {model: base.config.settings}: | + config_obj = self.pool.get('base.config.settings') + if config_obj: + vals = { + 'module_multi_company': 'True', + } + config_id = config_obj.create(cr, uid, vals, context) + config = config_obj.browse(cr, uid, config_id) + config.execute() diff --git a/purchase_sale_inter_company/models/__init__.py b/purchase_sale_inter_company/models/__init__.py new file mode 100644 index 00000000000..d6fc0fcfed9 --- /dev/null +++ b/purchase_sale_inter_company/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +from . import res_config +from . import res_company +from . import purchase_order +from . import sale_order diff --git a/purchase_sale_inter_company/models/purchase_order.py b/purchase_sale_inter_company/models/purchase_order.py new file mode 100644 index 00000000000..b07f49f5b86 --- /dev/null +++ b/purchase_sale_inter_company/models/purchase_order.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +from openerp import api, models, _ +from openerp.exceptions import Warning as UserError + + +class PurchaseOrder(models.Model): + + _inherit = 'purchase.order' + + @api.multi + def wkf_confirm_order(self): + """ Generate inter company sale order base on conditions.""" + res = super(PurchaseOrder, self).wkf_confirm_order() + for purchase_order in self: + # get the company from partner then trigger action of + # intercompany relation + company_rec = self.env['res.company']._find_company_from_partner( + purchase_order.partner_id.id) + purchase_order.inter_company_create_sale_order(company_rec) + return res + + @api.multi + def inter_company_create_sale_order(self, company): + """ Create a Sale Order from the current PO (self) + Note : In this method, reading the current PO is done as sudo, + and the creation of the derived + SO as intercompany_user, minimizing the access right required + for the trigger user. + :param company : the company of the created PO + :rtype company : res.company record + """ + self.ensure_one() + SaleOrder = self.env['sale.order'] + + # find user for creating and validation SO/PO from partner company + intercompany_uid = (company.intercompany_user_id and + company.intercompany_user_id.id or False) + if not intercompany_uid: + raise UserError(_( + 'Provide at least one user for inter company relation for % ') + % company.name) + # check intercompany user access rights + if not SaleOrder.sudo(intercompany_uid).check_access_rights( + 'create', raise_exception=False): + raise UserError(_( + "Inter company user of company %s doesn't have enough " + "access rights") % company.name) + + # Accessing to selling partner with selling user, so data like + # property_account_position can be retrieved + company_partner = self.env['res.partner'].sudo( + intercompany_uid).browse(self.company_id.partner_id.id) + + # check pricelist currency should be same with PO/SO document + if self.pricelist_id.currency_id.id != ( + company_partner.property_product_pricelist.currency_id.id): + raise UserError(_( + 'You cannot create SO from PO because ' + 'sale price list currency is different than ' + 'purchase price list currency.')) + + # create the SO and generate its lines from the PO lines + SaleOrderLine = self.env['sale.order.line'] + # read it as sudo, because inter-compagny user + # can not have the access right on PO + sale_order_data = self.sudo()._prepare_sale_order_data( + self.name, company_partner, company, + self.dest_address_id and self.dest_address_id.id or False) + sale_order = SaleOrder.sudo(intercompany_uid).create( + sale_order_data[0]) + for line in self.order_line: + so_line_vals = self.sudo()._prepare_sale_order_line_data( + line, company, sale_order.id) + SaleOrderLine.sudo(intercompany_uid).create(so_line_vals) + + # write supplier reference field on PO + if not self.partner_ref: + self.partner_ref = sale_order.name + + # Validation of sale order + if company.sale_auto_validation: + sale_order.sudo(intercompany_uid).signal_workflow('order_confirm') + + @api.multi + def _prepare_sale_order_data(self, name, partner, company, + direct_delivery_address): + """ Generate the Sale Order values from the PO + :param name : the origin client reference + :rtype name : string + :param partner : the partner reprenseting the company + :rtype partner : res.partner record + :param company : the company of the created SO + :rtype company : res.company record + :param direct_delivery_address : the address of the SO + :rtype direct_delivery_address : res.partner record + """ + self.ensure_one() + partner_addr = partner.sudo().address_get(['default', + 'invoice', + 'delivery', + 'contact']) + return { + 'name': ( + self.env['ir.sequence'].sudo().next_by_code('sale.order') or + '/' + ), + 'company_id': company.id, + 'client_order_ref': name, + 'partner_id': partner.id, + 'pricelist_id': partner.property_product_pricelist.id, + 'partner_invoice_id': partner_addr['invoice'], + 'date_order': self.date_order, + 'fiscal_position': (partner.property_account_position and + partner.property_account_position.id or False), + 'user_id': False, + 'auto_generated': True, + 'auto_purchase_order_id': self.id, + 'partner_shipping_id': (direct_delivery_address or + partner_addr['delivery']) + } + + @api.model + def _prepare_sale_order_line_data(self, line, company, sale_id): + """ Generate the Sale Order Line values from the PO line + :param line : the origin Purchase Order Line + :rtype line : purchase.order.line record + :param company : the company of the created SO + :rtype company : res.company record + :param sale_id : the id of the SO + """ + # it may not affected because of parallel company relation + price = line.price_unit or 0.0 + taxes = line.taxes_id + product = None + if line.product_id: + # make a new browse otherwise line._uid keeps the purchasing + # company's user and can't see the selling company's taxes + product = self.env['product.product'].browse(line.product_id.id) + taxes = product.taxes_id + company_taxes = [tax_rec.id + for tax_rec in taxes + if tax_rec.company_id.id == company.id] + return { + 'name': line.name, + 'order_id': sale_id, + 'product_uom_qty': line.product_qty, + 'product_id': product and product.id or False, + 'product_uom': (product and product.uom_id.id or + line.product_uom.id), + 'price_unit': price, + 'delay': product and product.sale_delay or 0.0, + 'company_id': company.id, + 'tax_id': [(6, 0, company_taxes)], + } diff --git a/purchase_sale_inter_company/models/res_company.py b/purchase_sale_inter_company/models/res_company.py new file mode 100644 index 00000000000..a658bd474d2 --- /dev/null +++ b/purchase_sale_inter_company/models/res_company.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +from openerp import api, fields, models, _, SUPERUSER_ID +from openerp.exceptions import ValidationError + + +class ResCompany(models.Model): + + _inherit = 'res.company' + + sale_auto_validation = fields.Boolean( + string='Sale Auto Validation', + help="When a Sale Order is created by a multi company rule " + "for this company, it will automatically validate it", + default=True) + intercompany_user_id = fields.Many2one( + 'res.users', string='Inter Company User', + help="Responsible user for creation of documents triggered by " + "intercompany rules. You cannot select the administrator, because " + "the administrator by-passes the record rules, which is a problem " + "when Odoo reads taxes on products.") + + @api.model + def _find_company_from_partner(self, partner_id): + company = self.sudo().search([('partner_id', '=', partner_id)], + limit=1) + return company or False + + @api.multi + @api.constrains('intercompany_user_id') + def _check_intercompany_user_id(self): + self.ensure_one() + if self.intercompany_user_id: + if self.intercompany_user_id.id == SUPERUSER_ID: + raise ValidationError(_( + 'You cannot use the administrator as the Inter Company ' + 'User, because the administrator by-passes record rules.' + )) + if self.intercompany_user_id.company_id != self: + raise ValidationError(_( + "The Inter Company User '%s' is attached to company " + "'%s', so you cannot select it for company '%s'.") % ( + self.intercompany_user_id.name, + self.intercompany_user_id.company_id.name, + self.name)) + if len(self.intercompany_user_id.company_ids) > 1: + raise ValidationError(_( + "You should not select '%s' as Inter Company User " + "because he is allowed to switch between several " + "companies, so there is no warranty that he will " + "stay in this company.") % self.intercompany_user_id.name) diff --git a/purchase_sale_inter_company/models/res_config.py b/purchase_sale_inter_company/models/res_config.py new file mode 100644 index 00000000000..342b5c0c595 --- /dev/null +++ b/purchase_sale_inter_company/models/res_config.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from openerp import models, fields, api + + +class InterCompanyRulesConfig(models.TransientModel): + + _inherit = 'base.config.settings' + + company_id = fields.Many2one( + 'res.company', string='Select Company', + help='Select company to setup Inter company rules.') + sale_auto_validation = fields.Boolean( + string='Sale Orders Auto Validation', + help='When a Sale Order is created by a multi company rule for ' + 'this company, it will automatically validate it.') + + @api.onchange('company_id') + def onchange_company_id(self): + if self.company_id: + self.sale_auto_validation = self.company_id.sale_auto_validation + + @api.multi + def set_inter_company_configuration(self): + if self.company_id: + vals = { + 'sale_auto_validation': self.auto_validation, + } + self.company_id.write(vals) diff --git a/purchase_sale_inter_company/models/sale_order.py b/purchase_sale_inter_company/models/sale_order.py new file mode 100644 index 00000000000..c8092c5460e --- /dev/null +++ b/purchase_sale_inter_company/models/sale_order.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +from openerp import fields, models + + +class SaleOrder(models.Model): + + _inherit = "sale.order" + + auto_generated = fields.Boolean(string='Auto Generated Sale Order', + readonly=True, copy=False) + auto_purchase_order_id = fields.Many2one('purchase.order', + string='Source Purchase Order', + readonly=True, copy=False) diff --git a/purchase_sale_inter_company/static/description/icon.png b/purchase_sale_inter_company/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b3de330666df26b26f9648d86c9f12b1dc6fc699 GIT binary patch literal 3735 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 BKy?5B literal 0 HcmV?d00001 diff --git a/purchase_sale_inter_company/test/inter_company_po_to_so.yml b/purchase_sale_inter_company/test/inter_company_po_to_so.yml new file mode 100644 index 00000000000..6882f5aef2c --- /dev/null +++ b/purchase_sale_inter_company/test/inter_company_po_to_so.yml @@ -0,0 +1,42 @@ +- + In order to check Purchase/Sale Inter-Company of creating SO from PO, I login with user company A +- + !context + uid: 'res_users_company_a' +- + Quotation of purchase order. +- + !record {model: purchase.order, id: purchase_order_cmpa}: + partner_id: partner_company_b + company_id: company_a + order_line: + - product_id: product_consultant + name: 'Service' + price_unit: 450.0 +- + I confirm the order. +- + !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_cmpa} +- + Login with user of company B and check sale order. +- + !context + uid: 'res_users_company_b' +- + I check that the Quotation of sale order is created with proper data. +- + !python {model: sale.order}: | + #NOTE: Browse PO with superuser id because of different company of user. + po_name = self.pool.get('purchase.order').browse(cr, 1, ref("purchase_order_cmpa"), context=context).name + sale_order_ids = self.search(cr, uid, [('client_order_ref', '=', po_name)], context=context) + sale_order= self.browse(cr, uid, sale_order_ids[0], context=context) + assert sale_order.state == "draft", "sale order should be in draft state." + assert sale_order.partner_id.id == ref("partner_company_a"), "Supplier is not correspond to Company A." + assert sale_order.company_id.id == ref("company_b"), "Applied company in created sale order is incorrect." + assert sale_order.amount_total == 450.0, "Total amount is incorrect." + + #check for order line + assert sale_order.order_line[0].product_id.id == ref("product_consultant"), "Product in line is incorrect." + assert sale_order.order_line[0].name == 'Service', "Product name is incorrect." + assert sale_order.order_line[0].product_uom_qty == 1 , "Product qty is incorrect." + assert sale_order.order_line[0].price_unit == 450, "Unit Price in line is incorrect." \ No newline at end of file diff --git a/purchase_sale_inter_company/test/test_intercompany_data.yml b/purchase_sale_inter_company/test/test_intercompany_data.yml new file mode 100644 index 00000000000..3753bcfc942 --- /dev/null +++ b/purchase_sale_inter_company/test/test_intercompany_data.yml @@ -0,0 +1,153 @@ +- + !record {model: res.partner, id: partner_company_a, view: False}: + name: Company A + supplier: 1 +- + !record {model: res.company, id: company_a, view: False}: + name: Company A + parent_id: base.main_company + currency_id: base.EUR + partner_id: partner_company_a + so_from_po: 1 + po_from_so: 1 +- + !record {model: stock.location, id: location_stock_company_a, view: False}: + name: Stock - A + usage: internal + company_id: company_a +- + !record {model: stock.location, id: location_output_company_a, view: False}: + name: Output - A + usage: internal + company_id: company_a +- + !record {model: stock.warehouse, id: warehouse_company_a, view: False}: + name: purchase warehouse - A + code: CMPA + wh_input_stock_loc_id: location_stock_company_a + lot_stock_id: location_stock_company_a + wh_output_stock_loc_id: location_output_company_a + company_id: company_a +- + !record {model: res.partner, id: partner_company_b, view: False}: + name: Company B + supplier: 1 +- + !record {model: res.company, id: company_b, view: False}: + name: Company B + parent_id: base.main_company + currency_id: base.EUR + partner_id: partner_company_b + so_from_po: 1 + po_from_so: 1 +- + !record {model: stock.location, id: location_stock_company_b, view: False}: + name: Stock - B + usage: internal + company_id: company_b +- + !record {model: stock.location, id: location_output_company_b, view: False}: + name: Output - B + usage: internal + company_id: company_b +- + !record {model: stock.warehouse, id: warehouse_company_b, view: False}: + name: purchase warehouse - B + code: CMPB + wh_input_stock_loc_id: location_stock_company_b + lot_stock_id: location_stock_company_b + wh_output_stock_loc_id: location_output_company_b + company_id: company_b +- + Apply warehouse in company. +- + !record {model: res.company, id: company_a}: + warehouse_id: warehouse_company_a +- + !record {model: res.company, id: company_b}: + warehouse_id: warehouse_company_b +- + !record {model: product.product, id: product_consultant, view: False}: + name: Service + uom_id: product.product_uom_hour + uom_po_id: product.product_uom_hour + categ_id: product.product_category_all + type: service + company_id: False +- + Create a user for each company +- + !record {model: res.users, id: res_users_company_a, view: False}: + name: User A + login: usera + password: usera + email: usera@yourcompany.com + company_id: company_a + company_ids: + - company_a + groups_id: + - base.group_sale_salesman + - purchase.group_purchase_user +- + !record {model: res.users, id: res_users_company_b, view: False}: + name: User B + login: userb + password: userb + email: userb@yourcompany.com + company_id: company_b + company_ids: + - company_b + groups_id: + - base.group_sale_salesman + - purchase.group_purchase_user +- + !record {model: res.company, id: company_a, view: False}: + intercompany_user_id: res_users_company_a +- + !record {model: res.company, id: company_b, view: False}: + intercompany_user_id: res_users_company_b +- + Install COA for new companies. +- + !python {model: account.installer}: | + wiz_comp_a = self.create(cr, uid, {'charts': 'configurable', 'company_id': ref("company_a")}) + self.execute(cr, uid, [wiz_comp_a]) + wiz_comp_b = self.create(cr, uid, {'charts': 'configurable', 'company_id': ref("company_b")}) + self.execute(cr, uid, [wiz_comp_b]) +- + !python {model: wizard.multi.charts.accounts}: | + wiz_comp_a = self.create(cr, uid, { + 'chart_template_id': '1', + 'company_id': ref("company_a"), + 'currency_id': ref("base.EUR"), + 'code_digits': 6, + 'purchase_tax': False, + 'sale_tax': False}) + self.execute(cr, uid, [wiz_comp_a]) + wiz_comp_b = self.create(cr, uid, { + 'chart_template_id': '1', + 'company_id': ref("company_b"), + 'currency_id': ref("base.EUR"), + 'code_digits': 6, + 'purchase_tax': False, + 'sale_tax': False}) + self.execute(cr, uid, [wiz_comp_b]) +- + Now remove company from related partners of company and apply default accounts. +- + !python {model: res.partner}: | + account_obj = self.pool.get("account.account") + account_receivable_a = account_obj.search(cr, uid, [('company_id', '=', ref("company_a")), ('type', '=', 'receivable')], context=context) + account_payable_a = account_obj.search(cr, uid, [('company_id', '=', ref("company_a")), ('type', '=', 'payable')], context=context) + self.write(cr, uid, [ref("partner_company_a")], { + 'company_id': False, + 'property_account_receivable': account_receivable_a[0], + 'property_account_payable': account_payable_a[0] + }, context=context) + account_receivable_b = account_obj.search(cr, uid, [('company_id', '=', ref("company_b")), ('type', '=', 'receivable')], context=context) + account_payable_b = account_obj.search(cr, uid, [('company_id', '=', ref("company_b")), ('type', '=', 'payable')], context=context) + self.write(cr, uid, [ref("partner_company_b")], { + 'company_id': False, + 'property_account_receivable': account_receivable_b[0], + 'property_account_payable': account_payable_b[0] + }, context=context) \ No newline at end of file diff --git a/purchase_sale_inter_company/views/inter_company_po_to_so_view.xml b/purchase_sale_inter_company/views/inter_company_po_to_so_view.xml new file mode 100644 index 00000000000..4b3f84ff738 --- /dev/null +++ b/purchase_sale_inter_company/views/inter_company_po_to_so_view.xml @@ -0,0 +1,20 @@ + + + + + res.company.form.inherit + + res.company + + + + + + + + + + + + + diff --git a/purchase_sale_inter_company/views/res_config_view.xml b/purchase_sale_inter_company/views/res_config_view.xml new file mode 100644 index 00000000000..ebc6a307404 --- /dev/null +++ b/purchase_sale_inter_company/views/res_config_view.xml @@ -0,0 +1,21 @@ + + + + + base.config.settings.purchase.sale.inter.company + base.config.settings + + + +
+ +
+
+ +
+
+
+
+
+
From c8c91f0a1d7076d5b21dd2397f52c0202508ec6f Mon Sep 17 00:00:00 2001 From: chafique-delli Date: Tue, 18 Oct 2016 15:21:07 +0200 Subject: [PATCH 02/87] add warehouse_id in the values for create sale order --- .../models/purchase_order.py | 15 ++++++++++++--- purchase_sale_inter_company/models/res_company.py | 4 ++++ purchase_sale_inter_company/models/res_config.py | 8 +++++++- .../views/inter_company_po_to_so_view.xml | 1 + .../views/res_config_view.xml | 4 ++++ 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/purchase_sale_inter_company/models/purchase_order.py b/purchase_sale_inter_company/models/purchase_order.py index b07f49f5b86..b47b04952d5 100644 --- a/purchase_sale_inter_company/models/purchase_order.py +++ b/purchase_sale_inter_company/models/purchase_order.py @@ -67,10 +67,10 @@ def inter_company_create_sale_order(self, company): self.name, company_partner, company, self.dest_address_id and self.dest_address_id.id or False) sale_order = SaleOrder.sudo(intercompany_uid).create( - sale_order_data[0]) - for line in self.order_line: + sale_order_data) + for po_line in self.order_line: so_line_vals = self.sudo()._prepare_sale_order_line_data( - line, company, sale_order.id) + po_line, company, sale_order.id) SaleOrderLine.sudo(intercompany_uid).create(so_line_vals) # write supplier reference field on PO @@ -99,6 +99,14 @@ def _prepare_sale_order_data(self, name, partner, company, 'invoice', 'delivery', 'contact']) + # find location and warehouse, pick warehouse from company object + warehouse = (company.warehouse_id and + company.warehouse_id.company_id.id == company.id and + company.warehouse_id or False) + if not warehouse: + raise UserError(_( + 'Configure correct warehouse for company(%s) from ' + 'Menu: Settings/companies/companies' % (company.name))) return { 'name': ( self.env['ir.sequence'].sudo().next_by_code('sale.order') or @@ -107,6 +115,7 @@ def _prepare_sale_order_data(self, name, partner, company, 'company_id': company.id, 'client_order_ref': name, 'partner_id': partner.id, + 'warehouse_id': warehouse.id, 'pricelist_id': partner.property_product_pricelist.id, 'partner_invoice_id': partner_addr['invoice'], 'date_order': self.date_order, diff --git a/purchase_sale_inter_company/models/res_company.py b/purchase_sale_inter_company/models/res_company.py index a658bd474d2..e811c298001 100644 --- a/purchase_sale_inter_company/models/res_company.py +++ b/purchase_sale_inter_company/models/res_company.py @@ -18,6 +18,10 @@ class ResCompany(models.Model): "intercompany rules. You cannot select the administrator, because " "the administrator by-passes the record rules, which is a problem " "when Odoo reads taxes on products.") + warehouse_id = fields.Many2one( + 'stock.warehouse', string='Warehouse For Sale Orders', + help="Default value to set on Sale Orders that " + "will be created based on Purchase Orders made to this company") @api.model def _find_company_from_partner(self, partner_id): diff --git a/purchase_sale_inter_company/models/res_config.py b/purchase_sale_inter_company/models/res_config.py index 342b5c0c595..e0a219ae27c 100644 --- a/purchase_sale_inter_company/models/res_config.py +++ b/purchase_sale_inter_company/models/res_config.py @@ -13,16 +13,22 @@ class InterCompanyRulesConfig(models.TransientModel): string='Sale Orders Auto Validation', help='When a Sale Order is created by a multi company rule for ' 'this company, it will automatically validate it.') + warehouse_id = fields.Many2one( + 'stock.warehouse', string='Warehouse For Sale Orders', + help='Default value to set on Sale Orders that will be created ' + 'based on Purchase Orders made to this company.') @api.onchange('company_id') def onchange_company_id(self): if self.company_id: self.sale_auto_validation = self.company_id.sale_auto_validation + self.warehouse_id = self.company_id.warehouse_id.id @api.multi def set_inter_company_configuration(self): if self.company_id: vals = { - 'sale_auto_validation': self.auto_validation, + 'sale_auto_validation': self.sale_auto_validation, + 'warehouse_id': self.warehouse_id.id } self.company_id.write(vals) diff --git a/purchase_sale_inter_company/views/inter_company_po_to_so_view.xml b/purchase_sale_inter_company/views/inter_company_po_to_so_view.xml index 4b3f84ff738..72eba36a0b7 100644 --- a/purchase_sale_inter_company/views/inter_company_po_to_so_view.xml +++ b/purchase_sale_inter_company/views/inter_company_po_to_so_view.xml @@ -9,6 +9,7 @@ + diff --git a/purchase_sale_inter_company/views/res_config_view.xml b/purchase_sale_inter_company/views/res_config_view.xml index ebc6a307404..1f734cbf57d 100644 --- a/purchase_sale_inter_company/views/res_config_view.xml +++ b/purchase_sale_inter_company/views/res_config_view.xml @@ -10,6 +10,10 @@
+
+