[ADD] inter_company_rules : feature to create inter company sale, purchase and invoice (Example : automatically create PO or SO for supplier company of the same database).
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -104,6 +104,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</group>
|
||||
<group>
|
||||
<label for="module_inter_company_rules" string="Inter company"/>
|
||||
<div name="inter_company">
|
||||
<div name="module_inter_company_rules">
|
||||
<field name="module_inter_company_rules" class="oe_inline"/>
|
||||
<label for="module_inter_company_rules"/>
|
||||
</div>
|
||||
</div>
|
||||
</group>
|
||||
<group string="Shared ressources" name="company_share_ressources">
|
||||
<label for="company_share_partner" />
|
||||
<div>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import models
|
||||
@@ -0,0 +1,50 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
{
|
||||
'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,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import res_config
|
||||
import res_company
|
||||
import purchase_order
|
||||
import sale_order
|
||||
import account_invoice
|
||||
@@ -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
|
||||
@@ -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)],
|
||||
}
|
||||
@@ -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')!'''))
|
||||
|
||||
@@ -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)
|
||||
@@ -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)],
|
||||
}
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
@@ -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."
|
||||
@@ -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)
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="view_company_inter_change_inherit_form">
|
||||
<field name="name">res.company.form.inherit</field>
|
||||
<field name="inherit_id" ref="base.view_company_form"/>
|
||||
<field name="model">res.company</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//notebook" position="inside">
|
||||
<page string="Inter-Company Rules">
|
||||
<group string="Inter-Company Rules">
|
||||
<field name="so_from_po" attrs="{'invisible': [('auto_generate_invoices', '=', True)]}"/>
|
||||
<field name="po_from_so" attrs="{'invisible': [('auto_generate_invoices', '=', True)]}"/>
|
||||
<field name="warehouse_id" attrs="{'required': [('po_from_so', '=', True)], 'invisible': [('po_from_so', '=', False)]}" domain="[('company_id', '=', active_id)]"/>
|
||||
<field name="auto_validation" attrs="{'invisible': [('auto_generate_invoices', '=', True)]}"/>
|
||||
<field name="auto_generate_invoices" attrs="{'invisible': ['|', ('so_from_po', '=', True), ('po_from_so', '=', True)]}"/>
|
||||
<field name="intercompany_user_id" colspan="2" attrs="{'required': ['|','|',('so_from_po', '=', True), ('po_from_so', '=', True), ('auto_generate_invoices', '=', True)]}"/>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_general_configuration_inter_company_rules" model="ir.ui.view">
|
||||
<field name="name">base.config.settings.inter.company.rules</field>
|
||||
<field name="model">base.config.settings</field>
|
||||
<field name="inherit_id" ref="base_setup.view_general_configuration"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='module_inter_company_rules']" position='after'>
|
||||
<div name="module_inter_company_rules_company_id" attrs="{'invisible': [('module_inter_company_rules', '=', False)]}">
|
||||
<field name="company_id" widget="selection" class="oe_inline"/>
|
||||
<field name="rule_type" widget="radio"/>
|
||||
</div>
|
||||
<div name="module_inter_company_rules_set_so_po" attrs="{'invisible': ['|', ('rule_type', '!=', 'so_and_po'), ('module_inter_company_rules', '=', False)]}">
|
||||
<field name="so_from_po"/>
|
||||
<label for="so_from_po"/>
|
||||
</div>
|
||||
<div name="module_inter_company_rules_set_po_so" attrs="{'invisible': ['|', ('rule_type', '!=', 'so_and_po'), ('module_inter_company_rules', '=', False)]}">
|
||||
<field name="po_from_so"/>
|
||||
<label for="po_from_so"/>
|
||||
</div>
|
||||
<div name="module_inter_company_rules_warehouse_id" attrs="{'invisible': ['|', ('po_from_so', '=', False), ('module_inter_company_rules', '=', False)]}">
|
||||
<label for="warehouse_id"/>
|
||||
<field name="warehouse_id" class="oe_inline" attrs="{'required': [('po_from_so', '=', True), ('module_inter_company_rules', '=', True)]}" domain="[('company_id', '=', company_id)]"/>
|
||||
</div>
|
||||
<div name="module_inter_company_rules_set_auto_validation" attrs="{'invisible': ['|', ('rule_type', '!=', 'so_and_po'), ('module_inter_company_rules', '=', False)]}">
|
||||
<field name="auto_validation"/>
|
||||
<label for="auto_validation"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user