[IMP] stock, mrp, repair: locations per company
Each company should have their own inventory loss, production, scrap and transit locations in order to apply specific accounting configurations. TASK 1882269 closes odoo/odoo#28508
This commit is contained in:
@@ -59,9 +59,7 @@ class StockWarehouse(models.Model):
|
||||
|
||||
@api.model
|
||||
def _get_production_location(self):
|
||||
location = self.env.ref('stock.location_production', raise_if_not_found=False)
|
||||
if not location:
|
||||
location = self.env['stock.location'].search([('usage', '=', 'production')], limit=1)
|
||||
location = self.env['stock.location'].with_context(force_company=self.company_id.id).search([('usage', '=', 'production'), ('company_id', '=', self.company_id.id)], limit=1)
|
||||
if not location:
|
||||
raise UserError(_('Can\'t find any production location.'))
|
||||
return location
|
||||
|
||||
@@ -13,5 +13,5 @@ class ReportStockRule(models.AbstractModel):
|
||||
"""
|
||||
res = super(ReportStockRule, self)._get_rule_loc(rule, product_id)
|
||||
if rule.action == 'manufacture':
|
||||
res['source'] = product_id.property_stock_production or self.env.ref('stock.location_production')
|
||||
res['source'] = product_id.property_stock_production
|
||||
return res
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<field name="partner_invoice_id" ref="base.res_partner_address_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_stock"/>
|
||||
<field name="operations" model="repair.line" eval="[(5, 0, 0), (0, 0, {
|
||||
'location_dest_id': obj().env.ref('stock.location_production').id,
|
||||
'location_dest_id': obj().env.ref('product.product_product_11').property_stock_production.id,
|
||||
'location_id': obj().env.ref('stock.stock_location_stock').id,
|
||||
'name': '[M-Las] Mouse, Laser',
|
||||
'product_id': obj().env.ref('product.product_product_11').id,
|
||||
@@ -38,7 +38,7 @@
|
||||
<field name="partner_invoice_id" ref="base.res_partner_address_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_stock"/>
|
||||
<field name="operations" model="repair.line" eval="[(5, 0, 0), (0, 0, {
|
||||
'location_dest_id': obj().env.ref('stock.location_production').id,
|
||||
'location_dest_id': obj().env.ref('product.product_product_12').property_stock_production.id,
|
||||
'location_id': obj().env.ref('stock.stock_location_stock').id,
|
||||
'name': '[M-Wir] Mouse, Wireless',
|
||||
'price_unit': 50.0,
|
||||
@@ -67,7 +67,7 @@
|
||||
<field name="partner_invoice_id" ref="base.res_partner_address_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_14"/>
|
||||
<field name="operations" model="repair.line" eval="[(5, 0, 0), (0, 0, {
|
||||
'location_dest_id': obj().env.ref('stock.location_production').id,
|
||||
'location_dest_id': obj().env.ref('product.product_product_13').property_stock_production.id,
|
||||
'location_id': obj().env.ref('stock.stock_location_stock').id,
|
||||
'name': '[RAM-SR5] RAM DDR SR5',
|
||||
'price_unit': 50.0,
|
||||
|
||||
@@ -59,7 +59,7 @@ class TestRepair(AccountingTestCase):
|
||||
'price_unit': price_unit,
|
||||
'repair_id': repair_id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.location_production').id,
|
||||
'location_dest_id': product_to_add.property_stock_production.id,
|
||||
})
|
||||
|
||||
def _create_simple_fee(self, repair_id=False, qty=0.0, price_unit=0.0):
|
||||
@@ -145,7 +145,7 @@ class TestRepair(AccountingTestCase):
|
||||
'Repaired product went to the wrong location')
|
||||
self.assertEqual(repair.operations.move_id.location_id.id, self.env.ref('stock.stock_location_stock').id,
|
||||
'Consumed product was taken in the wrong location')
|
||||
self.assertEqual(repair.operations.move_id.location_dest_id.id, self.env.ref('stock.location_production').id,
|
||||
self.assertEqual(repair.operations.move_id.location_dest_id.id, self.env.ref('product.product_product_5').property_stock_production.id,
|
||||
'Consumed product went to the wrong location')
|
||||
|
||||
# I define Invoice Method 'No Invoice' option in this repair order.
|
||||
|
||||
@@ -17,20 +17,3 @@ def pre_init_hook(cr):
|
||||
('model', 'like', '%stock%'),
|
||||
('module', '=', 'stock')
|
||||
]).unlink()
|
||||
|
||||
def _create_warehouse(cr, registry):
|
||||
""" This hook is used to add a warehouse on existing companies
|
||||
when module stock is installed.
|
||||
"""
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
company_ids = env['res.company'].search([])
|
||||
company_with_warehouse = env['stock.warehouse'].search([]).mapped('company_id')
|
||||
company_without_warehouse = company_ids - company_with_warehouse
|
||||
for company in company_without_warehouse:
|
||||
company.create_transit_location()
|
||||
env['stock.warehouse'].create({
|
||||
'name': company.name,
|
||||
'code': company.name[:5],
|
||||
'company_id': company.id,
|
||||
'partner_id': company.partner_id.id
|
||||
})
|
||||
|
||||
@@ -78,5 +78,4 @@
|
||||
'application': True,
|
||||
'auto_install': False,
|
||||
'pre_init_hook': 'pre_init_hook',
|
||||
'post_init_hook': '_create_warehouse',
|
||||
}
|
||||
|
||||
@@ -31,32 +31,13 @@
|
||||
<field name="posz">1</field>
|
||||
<field name="company_id"></field>
|
||||
</record>
|
||||
<record id="stock_location_scrapped" model="stock.location">
|
||||
<field name="name">Scrapped</field>
|
||||
<field name="location_id" ref="stock_location_locations_virtual"/>
|
||||
<field name="scrap_location">True</field>
|
||||
<field name="usage">inventory</field>
|
||||
<field name="company_id"></field>
|
||||
</record>
|
||||
|
||||
<record id="location_inventory" model="stock.location">
|
||||
<field name="name">Inventory adjustment</field>
|
||||
<field name="location_id" ref="stock_location_locations_virtual"/>
|
||||
<field name="usage">inventory</field>
|
||||
<field name="company_id"></field>
|
||||
</record>
|
||||
<record id="location_procurement" model="stock.location">
|
||||
<field name="name">Procurements</field>
|
||||
<field name="location_id" ref="stock_location_locations_virtual"/>
|
||||
<field name="usage">procurement</field>
|
||||
<field name="company_id"></field>
|
||||
</record>
|
||||
<record id="location_production" model="stock.location">
|
||||
<field name="name">Production</field>
|
||||
<field name="location_id" ref="stock_location_locations_virtual"/>
|
||||
<field name="usage">production</field>
|
||||
<field name="company_id"></field>
|
||||
</record>
|
||||
<record id="stock_location_suppliers" model="stock.location">
|
||||
<field name="name">Vendors</field>
|
||||
<field name="location_id" ref="stock_location_locations_partner"/>
|
||||
@@ -103,16 +84,6 @@
|
||||
<field name="fields_id" search="[('model','=','res.partner'),('name','=','property_stock_customer')]"/>
|
||||
<field eval="'stock.location,'+str(stock_location_customers)" name="value"/>
|
||||
</record>
|
||||
<record forcecreate="True" id="property_stock_inventory" model="ir.property">
|
||||
<field name="name">property_stock_inventory</field>
|
||||
<field name="fields_id" search="[('model','=','product.template'),('name','=','property_stock_inventory')]"/>
|
||||
<field eval="'stock.location,'+str(location_inventory)" name="value"/>
|
||||
</record>
|
||||
<record forcecreate="True" id="property_stock_production" model="ir.property">
|
||||
<field name="name">property_stock_production</field>
|
||||
<field name="fields_id" search="[('model','=','product.template'),('name','=','property_stock_production')]"/>
|
||||
<field eval="'stock.location,'+str(location_production)" name="value"/>
|
||||
</record>
|
||||
|
||||
|
||||
<!--
|
||||
@@ -177,8 +148,20 @@
|
||||
</function>
|
||||
|
||||
<!-- create the transit location for each company existing -->
|
||||
<function model="res.company" name="create_transit_location">
|
||||
<value model="res.company" eval="obj().search([('internal_transit_location_id', '=', False)]).ids"/>
|
||||
<function model="res.company" name="create_missing_transit_location">
|
||||
<value model="res.company" eval="obj()"/>
|
||||
</function>
|
||||
<function model="res.company" name="create_missing_warehouse">
|
||||
<value model="res.company" eval="obj()"/>
|
||||
</function>
|
||||
<function model="res.company" name="create_missing_inventory_loss_location">
|
||||
<value model="res.company" eval="obj()"/>
|
||||
</function>
|
||||
<function model="res.company" name="create_missing_production_location">
|
||||
<value model="res.company" eval="obj()"/>
|
||||
</function>
|
||||
<function model="res.company" name="create_missing_scrap_location">
|
||||
<value model="res.company" eval="obj()"/>
|
||||
</function>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
<field name="tracking">lot</field>
|
||||
<field name="uom_id" ref="uom.product_uom_kgm"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_kgm"/>
|
||||
<field name="property_stock_inventory" ref="stock.location_inventory"/>
|
||||
<field name="image" type="base64" file="stock/static/img/cable_management.png"/>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -12,34 +12,127 @@ class Company(models.Model):
|
||||
'stock.location', 'Internal Transit Location', on_delete="restrict",
|
||||
help="Technical field used for resupply routes between warehouses that belong to this company")
|
||||
|
||||
def create_transit_location(self):
|
||||
def _create_transit_location(self):
|
||||
'''Create a transit location with company_id being the given company_id. This is needed
|
||||
in case of resuply routes between warehouses belonging to the same company, because
|
||||
we don't want to create accounting entries at that time.
|
||||
'''
|
||||
# TDE FIXME: called in data - should be done in init ??
|
||||
parent_location = self.env.ref('stock.stock_location_locations', raise_if_not_found=False)
|
||||
for company in self:
|
||||
location = self.env['stock.location'].create({
|
||||
'name': _('%s: Transit Location') % company.name,
|
||||
'usage': 'transit',
|
||||
'location_id': parent_location and parent_location.id or False,
|
||||
'company_id': company.id,
|
||||
})
|
||||
location.sudo().write({'company_id': company.id})
|
||||
|
||||
company.write({'internal_transit_location_id': location.id})
|
||||
|
||||
warehouses = self.env['stock.warehouse'].search([('partner_id', '=', company.partner_id.id)])
|
||||
warehouses.mapped('partner_id').with_context(force_company=company.id).write({
|
||||
company.partner_id.with_context(force_company=company.id).write({
|
||||
'property_stock_customer': location.id,
|
||||
'property_stock_supplier': location.id,
|
||||
})
|
||||
|
||||
def _create_inventory_loss_location(self):
|
||||
parent_location = self.env.ref('stock.stock_location_locations_virtual', raise_if_not_found=False)
|
||||
inventory_loss_product_template_field = self.env['ir.model.fields'].search([('model','=','product.template'),('name','=','property_stock_inventory')])
|
||||
for company in self:
|
||||
inventory_loss_location = self.env['stock.location'].create({
|
||||
'name': '%s: Inventory adjustment' % company.name,
|
||||
'usage': 'inventory',
|
||||
'location_id': parent_location.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
self.env['ir.property'].create({
|
||||
'name': 'property_stock_inventory_%s' % company.name,
|
||||
'fields_id': inventory_loss_product_template_field.id,
|
||||
'company_id': company.id,
|
||||
'value': 'stock.location,%d' % inventory_loss_location.id,
|
||||
})
|
||||
|
||||
def _create_production_location(self):
|
||||
parent_location = self.env.ref('stock.stock_location_locations_virtual', raise_if_not_found=False)
|
||||
production_product_template_field = self.env['ir.model.fields'].search([('model','=','product.template'),('name','=','property_stock_production')])
|
||||
for company in self:
|
||||
production_location = self.env['stock.location'].create({
|
||||
'name': '%s: Production' % company.name,
|
||||
'usage': 'production',
|
||||
'location_id': parent_location.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
self.env['ir.property'].create({
|
||||
'name': 'property_stock_inventory_%s' % company.name,
|
||||
'fields_id': production_product_template_field.id,
|
||||
'company_id': company.id,
|
||||
'value': 'stock.location,%d' % production_location.id,
|
||||
})
|
||||
|
||||
def _create_scrap_location(self):
|
||||
for company in self:
|
||||
parent_location = self.env.ref('stock.stock_location_locations_virtual', raise_if_not_found=False)
|
||||
scrap_location = self.env['stock.location'].create({
|
||||
'name': '%s: Scrap' % company.name,
|
||||
'usage': 'inventory',
|
||||
'location_id': parent_location.id,
|
||||
'company_id': company.id,
|
||||
'scrap_location': True,
|
||||
})
|
||||
|
||||
@api.model
|
||||
def create_missing_warehouse(self):
|
||||
""" This hook is used to add a warehouse on existing companies
|
||||
when module stock is installed.
|
||||
"""
|
||||
company_ids = self.env['res.company'].search([])
|
||||
company_with_warehouse = self.env['stock.warehouse'].search([]).mapped('company_id')
|
||||
company_without_warehouse = company_ids - company_with_warehouse
|
||||
for company in company_without_warehouse:
|
||||
self.env['stock.warehouse'].create({
|
||||
'name': company.name,
|
||||
'code': company.name[:5],
|
||||
'company_id': company.id,
|
||||
'partner_id': company.partner_id.id,
|
||||
})
|
||||
|
||||
@api.model
|
||||
def create_missing_transit_location(self):
|
||||
company_without_transit = self.env['res.company'].search([('internal_transit_location_id', '=', False)])
|
||||
for company in company_without_transit:
|
||||
company._create_transit_location()
|
||||
|
||||
@api.model
|
||||
def create_missing_inventory_loss_location(self):
|
||||
company_ids = self.env['res.company'].search([])
|
||||
inventory_loss_product_template_field = self.env['ir.model.fields'].search([('model','=','product.template'),('name','=','property_stock_inventory')])
|
||||
companies_having_property = self.env['ir.property'].search([('fields_id', '=', inventory_loss_product_template_field.id)]).mapped('company_id')
|
||||
company_without_property = company_ids - companies_having_property
|
||||
for company in company_without_property:
|
||||
company._create_inventory_loss_location()
|
||||
|
||||
@api.model
|
||||
def create_missing_production_location(self):
|
||||
company_ids = self.env['res.company'].search([])
|
||||
production_product_template_field = self.env['ir.model.fields'].search([('model','=','product.template'),('name','=','property_stock_production')])
|
||||
companies_having_property = self.env['ir.property'].search([('fields_id', '=', production_product_template_field.id)]).mapped('company_id')
|
||||
company_without_property = company_ids - companies_having_property
|
||||
for company in company_without_property:
|
||||
company._create_production_location()
|
||||
|
||||
@api.model
|
||||
def create_missing_scrap_location(self):
|
||||
company_ids = self.env['res.company'].search([])
|
||||
companies_having_scrap_loc = self.env['stock.location'].search([('scrap_location', '=', True)]).mapped('company_id')
|
||||
company_without_property = company_ids - companies_having_scrap_loc
|
||||
for company in company_without_property:
|
||||
company._create_scrap_location()
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
company = super(Company, self).create(vals)
|
||||
|
||||
company.create_transit_location()
|
||||
# mutli-company rules prevents creating warehouse and sub-locations
|
||||
self.env['stock.warehouse'].check_access_rights('create')
|
||||
company.sudo()._create_transit_location()
|
||||
company.sudo()._create_inventory_loss_location()
|
||||
company.sudo()._create_production_location()
|
||||
company.sudo()._create_scrap_location()
|
||||
self.env['stock.warehouse'].sudo().create({'name': company.name, 'code': company.name[:5], 'company_id': company.id, 'partner_id': company.partner_id.id})
|
||||
return company
|
||||
|
||||
@@ -34,9 +34,9 @@ class TestWarehouse(TestStockCommon):
|
||||
self.assertIn(inventory.name, inventory.move_ids.name)
|
||||
self.assertEqual(inventory.move_ids.product_qty, 15.0)
|
||||
self.assertEqual(inventory.move_ids.location_id, self.warehouse_1.lot_stock_id)
|
||||
self.assertEqual(inventory.move_ids.location_dest_id, self.env.ref('stock.location_inventory')) # Inventory loss
|
||||
self.assertEqual(inventory.move_ids.location_dest_id, self.product_1.property_stock_inventory) # Inventory loss
|
||||
self.assertEqual(inventory.move_ids.state, 'done')
|
||||
quants = self.env['stock.quant']._gather(self.product_1, self.env.ref('stock.location_inventory'))
|
||||
quants = self.env['stock.quant']._gather(self.product_1, self.product_1.property_stock_inventory)
|
||||
self.assertEqual(len(quants), 1) # One quant created for inventory loss
|
||||
|
||||
# Check quantity of product in various locations: current, its parent, brother and other
|
||||
@@ -176,7 +176,7 @@ class TestWarehouse(TestStockCommon):
|
||||
productA = self.env['product.product'].create({'name': 'Product A', 'type': 'product'})
|
||||
stock_location = self.env.ref('stock.stock_location_stock')
|
||||
customer_location = self.env.ref('stock.stock_location_customers')
|
||||
location_loss = self.env.ref('stock.location_inventory')
|
||||
location_loss = productA.property_stock_inventory
|
||||
|
||||
# Create a picking out and force availability
|
||||
picking_out = self.env['stock.picking'].create({
|
||||
|
||||
Reference in New Issue
Block a user