diff --git a/addons/base_setup/res_config.py b/addons/base_setup/res_config.py
index 4a7614b0908..7ba914f0774 100644
--- a/addons/base_setup/res_config.py
+++ b/addons/base_setup/res_config.py
@@ -43,6 +43,8 @@ class base_config_settings(osv.osv_memory):
help="""This installs the module google_calendar."""),
'font': fields.many2one('res.font', string="Report Font", domain=[('mode', 'in', ('Normal', 'Regular', 'all', 'Book'))],
help="Set the font into the report header, it will be used as default font in the RML reports of the user company"),
+ 'module_inter_company_rules': fields.boolean('Manage Inter Company',
+ help="""This installs the module inter_company_rules.\n Configure company rules to automatically create SO/PO when one of your company sells/buys to another of your company."""),
'company_share_partner': fields.boolean('Share partners to all companies',
help="Share your partners to all companies defined in your instance.\n"
" * Checked : Partners are visible for every companies, even if a company is defined on the partner.\n"
diff --git a/addons/base_setup/res_config_view.xml b/addons/base_setup/res_config_view.xml
index 687558b2782..d5e0b50d479 100644
--- a/addons/base_setup/res_config_view.xml
+++ b/addons/base_setup/res_config_view.xml
@@ -104,6 +104,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/addons/inter_company_rules/__init__.py b/addons/inter_company_rules/__init__.py
new file mode 100644
index 00000000000..89d26e2f597
--- /dev/null
+++ b/addons/inter_company_rules/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+import models
diff --git a/addons/inter_company_rules/__openerp__.py b/addons/inter_company_rules/__openerp__.py
new file mode 100644
index 00000000000..0f3da7a26e8
--- /dev/null
+++ b/addons/inter_company_rules/__openerp__.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-Today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+{
+ 'name': 'Inter Company Module for Sale/Purchase Orders and Invoices',
+ 'version': '1.1',
+ 'summary': 'Intercompany SO/PO/INV rules',
+ 'description': ''' Module for synchronization of Documents between several companies. For example, this allow you to have a Sale Order created automatically when a Purchase Order is validated with another company of the system as supplier, and inversely.
+
+ Supported documents are SO, PO and invoices/refunds.
+''',
+ 'author': 'Odoo SA',
+ 'website': 'http://www.odoo.com',
+ 'depends': [
+ 'sale',
+ 'purchase',
+ 'sale_stock',
+ 'sale_order_dates'
+ ],
+ 'data': [
+ 'views/inter_company_so_po_view.xml',
+ 'views/res_config_view.xml',
+ ],
+ 'test': [
+ 'test/test_intercompany_data.yml',
+ 'test/inter_company_so_to_po.yml',
+ 'test/inter_company_po_to_so.yml',
+ 'test/inter_company_invoice.yml'
+ ],
+ 'demo': [],
+ 'installable': True,
+ 'auto_install': False,
+}
diff --git a/addons/inter_company_rules/models/__init__.py b/addons/inter_company_rules/models/__init__.py
new file mode 100644
index 00000000000..33c928ea8b2
--- /dev/null
+++ b/addons/inter_company_rules/models/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+import res_config
+import res_company
+import purchase_order
+import sale_order
+import account_invoice
\ No newline at end of file
diff --git a/addons/inter_company_rules/models/account_invoice.py b/addons/inter_company_rules/models/account_invoice.py
new file mode 100644
index 00000000000..4740cc4e1be
--- /dev/null
+++ b/addons/inter_company_rules/models/account_invoice.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+from openerp import api, fields, models, _
+from openerp.exceptions import Warning
+
+
+class account_invoice(models.Model):
+
+ _inherit = 'account.invoice'
+
+ auto_generated = fields.Boolean(string='Auto Generated Document', copy=False, default=False)
+ auto_invoice_id = fields.Many2one('account.invoice', string='Source Invoice', readonly=True, copy=False)
+
+ @api.multi
+ def invoice_validate(self):
+ """ Validated invoice generate cross invoice base on company rules """
+ for invoice in self:
+ # do not consider invoices that have already been auto-generated, nor the invoices that were already validated in the past
+ company = self.env['res.company']._find_company_from_partner(invoice.partner_id.id)
+ if company and company.auto_generate_invoices and not invoice.auto_generated:
+ if invoice.type == 'out_invoice':
+ invoice.inter_company_create_invoice(company, 'in_invoice', 'purchase')
+ elif invoice.type == 'in_invoice':
+ invoice.inter_company_create_invoice(company, 'out_invoice', 'sale')
+ elif invoice.type == 'out_refund':
+ invoice.inter_company_create_invoice(company, 'in_refund', 'purchase_refund')
+ elif invoice.type == 'in_refund':
+ invoice.inter_company_create_invoice(company, 'out_refund', 'sale_refund')
+ return super(account_invoice, self).invoice_validate()
+
+ @api.one
+ def inter_company_create_invoice(self, company, inv_type, journal_type):
+ """ create an invoice for the given company : it wil copy the invoice lines in the new
+ invoice. The intercompany user is the author of the new invoice.
+ :param company : the company of the created invoice
+ :rtype company : res.company record
+ :param inv_type : the type of the invoice ('in_refund', 'out_refund', 'in_invoice', ...)
+ :rtype inv_type : string
+ :param journal_type : the type of the journal to register the invoice
+ :rtype journal_type : string
+ """
+ # find user for creating the invoice from company
+ intercompany_uid = company.intercompany_user_id and company.intercompany_user_id.id or False
+ if not intercompany_uid:
+ raise Warning(_('Provide one user for intercompany relation for % ') % company.name)
+
+ context = self._context.copy()
+ context['force_company'] = company.id
+ origin_partner_id = self.company_id.partner_id
+ invoice_line_ids = []
+ for line in self.invoice_line:
+ # get invoice line data from product onchange
+ product_uom_id = line.product_id.uom_id and line.product_id.uom_id.id or False
+ line_data = line.with_context(context).sudo().product_id_change(line.product_id.id,
+ product_uom_id,
+ qty=line.quantity,
+ name='',
+ type=inv_type,
+ partner_id=origin_partner_id.id,
+ fposition_id=origin_partner_id.property_account_position.id,
+ company_id=company.id)
+ # create invoice line, as the intercompany user
+ inv_line_data = self.sudo()._prepare_invoice_line_data(line_data, line)
+ inv_line_id = line.with_context(context).sudo(intercompany_uid).create(inv_line_data)
+ invoice_line_ids.append(inv_line_id.id)
+ # create invoice, as the intercompany user
+ invoice_vals = self.with_context(context).sudo()._prepare_invoice_data(invoice_line_ids, inv_type, journal_type, company)[0]
+ return self.with_context(context).sudo(intercompany_uid).create(invoice_vals)
+
+ @api.one
+ def _prepare_invoice_data(self, invoice_line_ids, inv_type, journal_type, company):
+ """ Generate invoice values
+ :param invoice_line_ids : the ids of the invoice lines
+ :rtype invoice_line_ids : array of integer
+ :param inv_type : the type of the invoice to prepare the values
+ :param journal_type : type of the journal to register the invoice_line_ids
+ :rtype journal_type : string
+ :rtype company : res.company record
+ """
+ # find the correct journal
+ journal = self.env['account.journal'].search([('type', '=', journal_type), ('company_id', '=', company.id)], limit=1)
+ if not journal:
+ raise Warning(_('Please define %s journal for this company: "%s" (id:%d).') % (journal_type, company.name, company.id))
+
+ # find periods of supplier company
+ context = self._context.copy()
+ context['company_id'] = company.id
+ period_ids = self.env['account.period'].with_context(context).find(self.date_invoice)
+
+ # find account, payment term, fiscal position, bank.
+ partner_data = self.onchange_partner_id(inv_type, self.company_id.partner_id.id, company_id=company.id)
+ return {
+ 'name': self.name,
+ #TODO : not sure !!
+ 'origin': self.company_id.name + _(' Invoice: ') + str(self.number),
+ 'type': inv_type,
+ 'date_invoice': self.date_invoice,
+ 'reference': self.reference,
+ 'account_id': partner_data['value'].get('account_id', False),
+ 'partner_id': self.company_id.partner_id.id,
+ 'journal_id': journal.id,
+ 'invoice_line': [(6, 0, invoice_line_ids)],
+ 'currency_id': self.currency_id and self.currency_id.id,
+ 'fiscal_position': partner_data['value'].get('fiscal_position', False),
+ 'payment_term': partner_data['value'].get('payment_term', False),
+ 'company_id': company.id,
+ 'period_id': period_ids and period_ids[0].id or False,
+ 'partner_bank_id': partner_data['value'].get('partner_bank_id', False),
+ 'auto_generated': True,
+ 'auto_invoice_id': self.id,
+ }
+
+ @api.model
+ def _prepare_invoice_line_data(self, line_data, line):
+ """ Generate invoice line values
+ :param line_data : dict of invoice line data
+ :rtype line_data : dict
+ :param line : the invoice line object
+ :rtype line : account.invoice.line record
+ """
+ vals = {
+ 'name': line.name,
+ 'price_unit': line.price_unit,
+ 'quantity': line.quantity,
+ 'discount': line.discount,
+ 'product_id': line.product_id.id or False,
+ 'uos_id': line.uos_id.id or False,
+ 'sequence': line.sequence,
+ 'invoice_line_tax_id': [(6, 0, line_data['value'].get('invoice_line_tax_id', []))],
+ 'account_analytic_id': line.account_analytic_id.id or False,
+ }
+ if line_data['value'].get('account_id'):
+ vals['account_id'] = line_data['value']['account_id']
+ return vals
diff --git a/addons/inter_company_rules/models/purchase_order.py b/addons/inter_company_rules/models/purchase_order.py
new file mode 100644
index 00000000000..b59592a2a1d
--- /dev/null
+++ b/addons/inter_company_rules/models/purchase_order.py
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+from openerp import api, fields, models, _
+from openerp.exceptions import Warning
+
+
+class purchase_order(models.Model):
+
+ _inherit = "purchase.order"
+
+ auto_generated = fields.Boolean(string='Auto Generated Purchase Order', copy=False)
+ auto_sale_order_id = fields.Many2one('sale.order', string='Source Sale Order', readonly=True, copy=False)
+
+ @api.multi
+ def wkf_confirm_order(self):
+ """ Generate inter company sale order base on conditions."""
+ res = super(purchase_order, self).wkf_confirm_order()
+ for order in self:
+ # get the company from partner then trigger action of intercompany relation
+ company_rec = self.env['res.company']._find_company_from_partner(order.partner_id.id)
+ if company_rec and company_rec.so_from_po and (not order.auto_generated):
+ order.inter_company_create_sale_order(company_rec)
+ return res
+
+
+ @api.one
+ 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
+ """
+ SaleOrder = self.env['sale.order']
+ company_partner = self.company_id.partner_id
+
+ # 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 Warning(_('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 Warning(_("Inter company user of company %s doesn't have enough access rights") % company.name)
+
+ # check pricelist currency should be same with SO/PO document
+ if self.pricelist_id.currency_id.id != company_partner.property_product_pricelist.currency_id.id:
+ raise Warning(_('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.auto_validation:
+ sale_order.sudo(intercompany_uid).signal_workflow('order_confirm')
+
+ @api.one
+ 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
+ """
+ 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
+ if line.product_id:
+ taxes = line.product_id.taxes_id
+ company_taxes = [tax_rec.id for tax_rec in taxes if tax_rec.company_id.id == company.id]
+ return {
+ 'name': line.product_id and line.product_id.name or line.name,
+ 'order_id': sale_id,
+ 'product_uom_qty': line.product_qty,
+ 'product_id': line.product_id and line.product_id.id or False,
+ 'product_uom': line.product_id and line.product_id.uom_id.id or line.product_uom.id,
+ 'price_unit': price,
+ 'delay': line.product_id and line.product_id.sale_delay or 0.0,
+ 'company_id': company.id,
+ 'tax_id': [(6, 0, company_taxes)],
+ }
diff --git a/addons/inter_company_rules/models/res_company.py b/addons/inter_company_rules/models/res_company.py
new file mode 100644
index 00000000000..2d7c4081a7a
--- /dev/null
+++ b/addons/inter_company_rules/models/res_company.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+from openerp import api, fields, models, _, SUPERUSER_ID
+from openerp.exceptions import Warning
+
+
+class res_company(models.Model):
+
+ _inherit = 'res.company'
+
+ so_from_po = fields.Boolean(string="Create Sale Orders when buying to this company",
+ help="Generate a Sale Order when a Purchase Order with this company as supplier is created.\n The intercompany user must at least be Sale User.")
+ po_from_so = fields.Boolean(string="Create Purchase Orders when selling to this company",
+ help="Generate a Purchase Order when a Sale Order with this company as customer is created.\n The intercompany user must at least be Purchase User.")
+ auto_generate_invoices = fields.Boolean(string="Create Invoices/Refunds when encoding invoices/refunds made to this company",
+ help="Generate Customer/Supplier Invoices (and refunds) when encoding invoices (or refunds) made to this company.\n e.g: Generate a Customer Invoice when a Supplier Invoice with this company as supplier is created.")
+ auto_validation = fields.Boolean(string="Sale/Purchase Orders Auto Validation",
+ help="When a Sale Order or a Purchase Order is created by a multi company rule for this company, it will automatically validate it")
+ intercompany_user_id = fields.Many2one("res.users", string="Inter Company User", default=SUPERUSER_ID,
+ help="Responsible user for creation of documents triggered by intercompany rules.")
+ warehouse_id = fields.Many2one("stock.warehouse", string="Warehouse For Purchase Orders",
+ help="Default value to set on Purchase Orders that will be created based on Sale Orders made to this company")
+
+ @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.one
+ @api.constrains('po_from_so', 'so_from_po', 'auto_generate_invoices')
+ def _check_intercompany_missmatch_selection(self):
+ if (self.po_from_so or self.so_from_po) and self.auto_generate_invoices:
+ raise Warning(_('''You cannot select to create invoices based on other invoices
+ simultaneously with another option ('Create Sale Orders when buying to this
+ company' or 'Create Purchase Orders when selling to this company')!'''))
+
diff --git a/addons/inter_company_rules/models/res_config.py b/addons/inter_company_rules/models/res_config.py
new file mode 100644
index 00000000000..dfc6aa8c681
--- /dev/null
+++ b/addons/inter_company_rules/models/res_config.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+from openerp import models, fields, api
+
+class inter_company_rules_configuration(models.TransientModel):
+
+ _inherit = 'base.config.settings'
+
+ company_id = fields.Many2one('res.company', string='Select Company',
+ help='Select company to setup Inter company rules.')
+ rule_type = fields.Selection([('so_and_po', 'SO and PO setting for inter company'),
+ ('invoice_and_refunds', 'Create Invoice/Refunds when encoding invoice/refunds')],
+ help='Select the type to setup inter company rules in selected company.')
+ so_from_po = fields.Boolean(string='Create Sale Orders when buying to this company',
+ help='Generate a Sale Order when a Purchase Order with this company as supplier is created.')
+ po_from_so = fields.Boolean(string='Create Purchase Orders when selling to this company',
+ help='Generate a Purchase Order when a Sale Order with this company as customer is created.')
+ auto_validation = fields.Boolean(string='Sale/Purchase Orders Auto Validation',
+ help='''When a Sale Order or a Purchase 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 Purchase Orders',
+ help='Default value to set on Purchase Orders that will be created based on Sale Orders made to this company.')
+
+ @api.onchange('rule_type')
+ def onchange_rule_type(self):
+ if self.rule_type == 'invoice_and_refunds':
+ self.so_from_po = False
+ self.po_from_so = False
+ self.auto_validation = False
+
+ elif self.rule_type == 'so_and_po':
+ self.invoice_and_refunds = False
+
+ @api.onchange('company_id')
+ def onchange_company_id(self):
+ if self.company_id:
+ rule_type = False
+ if self.company_id.so_from_po or self.company_id.po_from_so or self.company_id.auto_validation:
+ rule_type = 'so_and_po'
+ elif self.company_id.auto_generate_invoices:
+ rule_type = 'invoice_and_refunds'
+
+ self.rule_type = rule_type
+ self.so_from_po = self.company_id.so_from_po
+ self.po_from_so = self.company_id.po_from_so
+ self.auto_validation = self.company_id.auto_validation
+ self.warehouse_id = self.company_id.warehouse_id.id
+
+ @api.multi
+ def set_inter_company_configuration(self):
+ if self.company_id:
+ vals = {
+ 'so_from_po': self.so_from_po,
+ 'po_from_so': self.po_from_so,
+ 'auto_validation': self.auto_validation,
+ 'auto_generate_invoices': True if self.rule_type == 'invoice_and_refunds' else False,
+ 'warehouse_id': self.warehouse_id.id
+ }
+ self.company_id.write(vals)
diff --git a/addons/inter_company_rules/models/sale_order.py b/addons/inter_company_rules/models/sale_order.py
new file mode 100644
index 00000000000..a8070e270ad
--- /dev/null
+++ b/addons/inter_company_rules/models/sale_order.py
@@ -0,0 +1,127 @@
+# -*- coding: utf-8 -*-
+from openerp import api, fields, models, _
+from openerp.exceptions import Warning
+
+
+class sale_order(models.Model):
+
+ _inherit = "sale.order"
+
+ auto_generated = fields.Boolean(string='Auto Generated Sale Order', copy=False)
+ auto_purchase_order_id = fields.Many2one('purchase.order', string='Source Purchase Order', readonly=True, copy=False)
+
+ @api.multi
+ def action_button_confirm(self):
+ """ Generate inter company purchase order based on conditions """
+ res = super(sale_order, self).action_button_confirm()
+ for order in self:
+ if not order.company_id: # if company_id not found, return to normal behavior
+ continue
+ # if company allow to create a Purchase Order from Sale Order, then do it !
+ company = self.env['res.company']._find_company_from_partner(order.partner_id.id)
+ if company and company.po_from_so and (not order.auto_generated):
+ order.inter_company_create_purchase_order(company)
+ return res
+
+ @api.one
+ def inter_company_create_purchase_order(self, company):
+ """ Create a Purchase Order from the current SO (self)
+ Note : In this method, reading the current SO is done as sudo, and the creation of the derived
+ PO 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
+ """
+ PurchaseOrder = self.env['purchase.order']
+ company_partner = self.company_id and self.company_id.partner_id or False
+ if not company or not company_partner.id:
+ return
+
+ # find user for creating and validating SO/PO from company
+ intercompany_uid = company.intercompany_user_id and company.intercompany_user_id.id or False
+ if not intercompany_uid:
+ raise Warning(_('Provide one user for intercompany relation for % ') % company.name)
+ # check intercompany user access rights
+ if not PurchaseOrder.sudo(intercompany_uid).check_access_rights('create', raise_exception=False):
+ raise Warning(_("Inter company user of company %s doesn't have enough access rights") % company.name)
+
+ # check pricelist currency should be same with SO/PO document
+ if self.pricelist_id.currency_id.id != company_partner.property_product_pricelist_purchase.currency_id.id:
+ raise Warning(_('You cannot create PO from SO because purchase pricelist currency is different than sale pricelist currency.'))
+
+ # create the PO and generate its lines from the SO
+ PurchaseOrderLine = self.env['purchase.order.line']
+ # read it as sudo, because inter-compagny user can not have the access right on PO
+ po_vals = self.sudo()._prepare_purchase_order_data(company, company_partner)
+ purchase_order = PurchaseOrder.sudo(intercompany_uid).create(po_vals[0])
+ for line in self.order_line:
+ po_line_vals = self.sudo()._prepare_purchase_order_line_data(line, self.date_order, purchase_order.id, company)
+ PurchaseOrderLine.sudo(intercompany_uid).create(po_line_vals)
+
+ # write customer reference field on SO
+ if not self.client_order_ref:
+ self.client_order_ref = purchase_order.name
+
+ # auto-validate the purchase order if needed
+ if company.auto_validation:
+ purchase_order.sudo(intercompany_uid).signal_workflow('purchase_confirm')
+
+ @api.one
+ def _prepare_purchase_order_data(self, company, company_partner):
+ """ Generate purchase order values, from the SO (self)
+ :param company_partner : the partner representing the company of the SO
+ :rtype company_partner : res.partner record
+ :param company : the company in which the PO line will be created
+ :rtype company : res.company record
+ """
+ # 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 Warning(_('Configure correct warehouse for company(%s) from Menu: Settings/companies/companies' % (company.name)))
+
+ return {
+ 'name': self.env['ir.sequence'].sudo().next_by_code('purchase.order'),
+ 'origin': self.name,
+ 'partner_id': company_partner.id,
+ 'location_id': warehouse.lot_stock_id.id,
+ 'pricelist_id': company_partner.property_product_pricelist_purchase.id,
+ 'date_order': self.date_order,
+ 'company_id': company.id,
+ 'fiscal_position': company_partner.property_account_position or False,
+ 'payment_term_id': company_partner.property_supplier_payment_term.id or False,
+ 'auto_generated': True,
+ 'auto_sale_order_id': self.id,
+ 'partner_ref': self.name,
+ 'dest_address_id': self.partner_shipping_id and self.partner_shipping_id.id or False,
+ }
+
+ @api.model
+ def _prepare_purchase_order_line_data(self, so_line, date_order, purchase_id, company):
+ """ Generate purchase order line values, from the SO line
+ :param so_line : origin SO line
+ :rtype so_line : sale.order.line record
+ :param date_order : the date of the orgin SO
+ :param purchase_id : the id of the purchase order
+ :param company : the company in which the PO line will be created
+ :rtype company : res.company record
+ """
+ # price on PO so_line should be so_line - discount
+ price = so_line.price_unit - (so_line.price_unit * (so_line.discount / 100))
+
+ # computing Default taxes of so_line. It may not affect because of parallel company relation
+ taxes = so_line.tax_id
+ if so_line.product_id:
+ taxes = so_line.product_id.supplier_taxes_id
+
+ # fetch taxes by company not by inter-company user
+ company_taxes = [tax_rec.id for tax_rec in taxes if tax_rec.company_id.id == company.id]
+ return {
+ 'name': so_line.name,
+ 'order_id': purchase_id,
+ 'product_qty': so_line.product_uom_qty,
+ 'product_id': so_line.product_id and so_line.product_id.id or False,
+ 'product_uom': so_line.product_id and so_line.product_id.uom_po_id.id or so_line.product_uom.id,
+ 'price_unit': price or 0.0,
+ 'company_id': so_line.order_id.company_id.id,
+ 'date_planned': so_line.order_id.commitment_date or date_order,
+ 'taxes_id': [(6, 0, company_taxes)],
+ }
diff --git a/addons/inter_company_rules/test/inter_company_invoice.yml b/addons/inter_company_rules/test/inter_company_invoice.yml
new file mode 100644
index 00000000000..65287bc04d0
--- /dev/null
+++ b/addons/inter_company_rules/test/inter_company_invoice.yml
@@ -0,0 +1,79 @@
+-
+ In order to check Inter-Company Rule of creating Supplier invoice from Customer invoice
+ make 'Create Invoices/Refunds when encoding invoices/refunds made' true.
+-
+ !record {model: res.company, id: company_a}:
+ so_from_po: 0
+ po_from_so: 0
+ auto_generate_invoices: 1
+-
+ !record {model: res.company, id: company_b}:
+ so_from_po: 0
+ po_from_so: 0
+ auto_generate_invoices: 1
+-
+ I apply account user group to Company A user.
+-
+ !record {model: res.users, id: res_users_company_a}:
+ groups_id:
+ - account.group_account_user
+-
+ I will create customer invoice with account user.
+-
+ !context
+ uid: 'res_users_company_a'
+-
+ !record {model: account.account, id: account_expense_company_a}:
+ name: Expenses
+ code: X1000
+ type: other
+ user_type: account.data_account_type_expense
+ company_id: company_a
+-
+ Create an invoice for the partner of company B with amount 450.0
+-
+ !record {model: account.invoice, id: account_invoice_0}:
+ company_id: company_a
+ partner_id: partner_company_b
+ currency_id: base.EUR
+ invoice_line:
+ - product_id: product_consultant
+ price_unit: 450.0
+ account_id: account_expense_company_a
+ quantity: 1.0
+-
+ I check that the customer invoice is in draft state.
+-
+ !assert {model: account.invoice, id: account_invoice_0}:
+ - state == 'draft'
+-
+ I validate the invoice.
+-
+ !workflow {model: account.invoice, action: invoice_open, ref: account_invoice_0}
+-
+ Now apply account user group to User B and test with that user.
+-
+ !record {model: res.users, id: res_users_company_b}:
+ groups_id:
+ - account.group_account_user
+-
+ !context
+ uid: 'res_users_company_b'
+-
+ I check that the supplier invoice is created with proper data.
+-
+ !python {model: account.invoice}: |
+ supplier_invoices = self.search(cr, uid, [('type','=','in_invoice')], context=context)
+ supplier_invoice = self.browse(cr, uid, supplier_invoices[0], context=context)
+
+ #check invoice line is created with proper data
+ assert supplier_invoice.invoice_line[0].quantity == 1, "Quantity in invoice line is incorrect."
+ assert supplier_invoice.invoice_line[0].product_id.id == ref("product_consultant"), "Product in line is incorrect."
+ assert supplier_invoice.invoice_line[0].price_unit == 450, "Unit Price in invoice line is incorrect."
+ assert supplier_invoice.invoice_line[0].account_id.company_id.id == ref("company_b"), "Applied account in created invoice line is not relevant to company."
+
+ assert supplier_invoice.state == "draft", "invoice should be in draft state."
+ assert supplier_invoice.amount_total == 450.0, "Total amount is incorrect."
+ assert supplier_invoice.company_id.id == ref("company_b"), "Applied company in created invoice is incorrect."
+ assert supplier_invoice.account_id.company_id.id == ref("company_b"), "Applied account in created invoice is not relevant to company."
+
diff --git a/addons/inter_company_rules/test/inter_company_po_to_so.yml b/addons/inter_company_rules/test/inter_company_po_to_so.yml
new file mode 100644
index 00000000000..417d903d945
--- /dev/null
+++ b/addons/inter_company_rules/test/inter_company_po_to_so.yml
@@ -0,0 +1,42 @@
+-
+ In order to check Inter-Company Rule 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."
diff --git a/addons/inter_company_rules/test/inter_company_so_to_po.yml b/addons/inter_company_rules/test/inter_company_so_to_po.yml
new file mode 100644
index 00000000000..95e7711397d
--- /dev/null
+++ b/addons/inter_company_rules/test/inter_company_so_to_po.yml
@@ -0,0 +1,46 @@
+-
+ In order to check Inter-Company Rule of creating PO from SO, I login with user company A
+-
+ !context
+ uid: 'res_users_company_a'
+-
+ I create Quotation of sale order and confirm it.
+-
+ !record {model: sale.order, id: sale_order_cmpa, view: False}:
+ company_id: company_a
+ warehouse_id: warehouse_company_a
+ user_id: res_users_company_a
+ pricelist_id: 1
+ partner_id: partner_company_b
+ partner_invoice_id: partner_company_b
+ partner_shipping_id: partner_company_b
+ order_line:
+ - product_id: product_consultant
+ name: 'Service'
+ price_unit: 450.0
+-
+ I confirm the order.
+-
+ !python {model: sale.order}: |
+ self.action_button_confirm(cr, uid, [ref('sale_order_cmpa')], context=context)
+-
+ I login with user company B.
+-
+ !context
+ uid: 'res_users_company_b'
+-
+ I check that Quotation of purchase order and order line is same as sale order
+-
+ !python {model: purchase.order}: |
+ order_ids = self.search(cr, uid, [('company_id','=',ref("company_b"))], context=context)
+ purchase_order = self.browse(cr, uid, order_ids[0], context=context)
+ assert purchase_order.state == "draft", "Invoice should be in draft state."
+ assert purchase_order.partner_id.id == ref("partner_company_a"),"Supplier is not correspond to Company A."
+ assert purchase_order.company_id.id == ref("company_b"),"Company is not correspond to purchase order."
+ assert purchase_order.amount_total == 450.0, "Total amount is incorrect."
+
+ assert purchase_order.order_line[0].product_id.id == ref("product_consultant"), "Product in line is incorrect."
+ assert purchase_order.order_line[0].name == 'Service',"Product name is incorrect."
+ assert purchase_order.order_line[0].price_unit == 450 , "Price unit is incorrect."
+ assert purchase_order.order_line[0].product_qty == 1 , "Product qty is incorrect."
+ assert purchase_order.order_line[0].price_subtotal == 450, "line total is incorrect."
diff --git a/addons/inter_company_rules/test/test_intercompany_data.yml b/addons/inter_company_rules/test/test_intercompany_data.yml
new file mode 100644
index 00000000000..bc0259ef6ab
--- /dev/null
+++ b/addons/inter_company_rules/test/test_intercompany_data.yml
@@ -0,0 +1,147 @@
+-
+ !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
+-
+ 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)
diff --git a/addons/inter_company_rules/views/inter_company_so_po_view.xml b/addons/inter_company_rules/views/inter_company_so_po_view.xml
new file mode 100644
index 00000000000..92ab0bb1ee1
--- /dev/null
+++ b/addons/inter_company_rules/views/inter_company_so_po_view.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ res.company.form.inherit
+
+ res.company
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/inter_company_rules/views/res_config_view.xml b/addons/inter_company_rules/views/res_config_view.xml
new file mode 100644
index 00000000000..88516f0cb99
--- /dev/null
+++ b/addons/inter_company_rules/views/res_config_view.xml
@@ -0,0 +1,34 @@
+
+
+
+
+ base.config.settings.inter.company.rules
+ base.config.settings
+
+
+
+