[MOV] mrp: move and split

Split files and move them according to the new API guidelines. Containing

 - mrp splitted into its main models and files
 - stock code moved into its own models and files
 - various renaming
This commit is contained in:
Dhaval Panchal
2016-07-04 16:22:35 +02:00
committed by Thibault Delavallée
parent ba7274dc01
commit c8a39e734f
37 changed files with 1744 additions and 1664 deletions
+1 -6
View File
@@ -1,11 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import mrp
import stock
import product
import models
import wizard
import report
import company
import procurement
import res_config
+24 -16
View File
@@ -36,26 +36,34 @@ Dashboard / Reports for MRP will include:
'data': [
'security/mrp_security.xml',
'security/ir.model.access.csv',
'mrp_workflow.xml',
'mrp_data.xml',
'wizard/mrp_product_produce_view.xml',
'wizard/change_production_qty_view.xml',
'wizard/stock_move_view.xml',
'mrp_view.xml',
'mrp_report.xml',
'company_view.xml',
'report/mrp_report_view.xml',
'res_config_view.xml',
'data/mrp_data_workflow.xml',
'data/mrp_data.xml',
'wizard/mrp_product_produce_views.xml',
'wizard/change_production_qty_views.xml',
'wizard/stock_move_consume_views.xml',
'views/mrp_views_menus.xml',
'views/mrp_property_views.xml',
'views/mrp_workcenter_views.xml',
'views/mrp_routing_views.xml',
'views/mrp_production_views.xml',
'views/mrp_bom_views.xml',
'views/procurement_views.xml',
'views/product_views.xml',
'views/stock_warehouse_views.xml',
'views/res_company_views.xml',
'views/mrp_config_settings_views.xml',
'report/mrp_report_views_main.xml',
'report/mrp_report_views.xml',
'views/report_mrporder.xml',
'views/report_mrpbomstructure.xml',
],
'demo': ['mrp_demo.xml'],
'demo': ['data/mrp_demo.xml'],
'test': [
'test/bom_with_service_type_product.yml',
'test/mrp_users.yml',
'test/order_demo.yml',
'test/order_process.yml',
'test/cancel_order.yml',
'test/bom_with_service_type_product.yml',
'test/mrp_users.yml',
'test/order_demo.yml',
'test/order_process.yml',
'test/cancel_order.yml',
],
'installable': True,
'application': True,
+14
View File
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import mrp_config_settings
import mrp_bom
import mrp_production
import mrp_property
import mrp_routing
import mrp_workcenter
import procurement
import product
import res_company
import stock_move
import stock_warehouse
+336
View File
@@ -0,0 +1,336 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
import openerp.addons.decimal_precision as dp
from openerp import tools
from openerp.exceptions import UserError
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
from openerp.tools.translate import _
class mrp_bom(osv.osv):
"""
Defines bills of material for a product.
"""
_name = 'mrp.bom'
_description = 'Bill of Material'
_inherit = ['mail.thread']
_columns = {
'code': fields.char('Reference', size=16),
'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the bills of material without removing it."),
'type': fields.selection([('normal','Manufacture this product'),('phantom','Ship this product as a set of components (kit)')], 'BoM Type', required=True,
help= "Set: When processing a sales order for this product, the delivery order will contain the raw materials, instead of the finished product."),
'position': fields.char('Internal Reference', help="Reference to a position in an external plan."),
'product_tmpl_id': fields.many2one('product.template', 'Product', domain="[('type', 'in', ['product', 'consu'])]", required=True),
'product_id': fields.many2one('product.product', 'Product Variant',
domain="['&', ('product_tmpl_id','=',product_tmpl_id), ('type', 'in', ['product', 'consu'])]",
help="If a product variant is defined the BOM is available only for this product."),
'bom_line_ids': fields.one2many('mrp.bom.line', 'bom_id', 'BoM Lines', copy=True),
'product_qty': fields.float('Product Quantity', required=True, digits_compute=dp.get_precision('Product Unit of Measure')),
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, help="Unit of Measure (Unit of Measure) is the unit of measurement for the inventory control"),
'date_start': fields.date('Valid From', help="Validity of this BoM. Keep empty if it's always valid."),
'date_stop': fields.date('Valid Until', help="Validity of this BoM. Keep empty if it's always valid."),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of bills of material."),
'routing_id': fields.many2one('mrp.routing', 'Routing', help="The list of operations (list of work centers) to produce the finished product. "\
"The routing is mainly used to compute work center costs during operations and to plan future loads on work centers based on production planning."),
'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity."),
'product_efficiency': fields.float('Manufacturing Efficiency', required=True, help="A factor of 0.9 means a loss of 10% during the production process."),
'property_ids': fields.many2many('mrp.property', string='Properties'),
'company_id': fields.many2one('res.company', 'Company', required=True),
}
def _get_uom_id(self, cr, uid, *args):
return self.pool["product.uom"].search(cr, uid, [], limit=1, order='id')[0]
_defaults = {
'active': lambda *a: 1,
'product_qty': lambda *a: 1.0,
'product_efficiency': lambda *a: 1.0,
'product_rounding': lambda *a: 0.0,
'type': lambda *a: 'normal',
'product_uom': _get_uom_id,
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.bom', context=c),
}
_order = "sequence"
def _bom_find(self, cr, uid, product_tmpl_id=None, product_id=None, properties=None, context=None):
""" Finds BoM for particular product and product uom.
@param product_tmpl_id: Selected product.
@param product_uom: Unit of measure of a product.
@param properties: List of related properties.
@return: False or BoM id.
"""
if not context:
context = {}
if properties is None:
properties = []
if product_id:
if not product_tmpl_id:
product_tmpl_id = self.pool['product.product'].browse(cr, uid, product_id, context=context).product_tmpl_id.id
domain = [
'|',
('product_id', '=', product_id),
'&',
('product_id', '=', False),
('product_tmpl_id', '=', product_tmpl_id)
]
elif product_tmpl_id:
domain = [('product_id', '=', False), ('product_tmpl_id', '=', product_tmpl_id)]
else:
# neither product nor template, makes no sense to search
return False
if context.get('company_id'):
domain = domain + [('company_id', '=', context['company_id'])]
domain = domain + [ '|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATE_FORMAT)),
'|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATE_FORMAT))]
# order to prioritize bom with product_id over the one without
ids = self.search(cr, uid, domain, order='sequence, product_id', context=context)
# Search a BoM which has all properties specified, or if you can not find one, you could
# pass a BoM without any properties with the smallest sequence
bom_empty_prop = False
for bom in self.pool.get('mrp.bom').browse(cr, uid, ids, context=context):
if not set(map(int, bom.property_ids or [])) - set(properties or []):
if not properties or bom.property_ids:
return bom.id
elif not bom_empty_prop:
bom_empty_prop = bom.id
return bom_empty_prop
def _skip_bom_line(self, cr, uid, line, product, context=None):
""" Control if a BoM line should be produce, can be inherited for add
custom control.
@param line: BoM line.
@param product: Selected product produced.
@return: True or False
"""
if line.date_start and line.date_start > time.strftime(DEFAULT_SERVER_DATE_FORMAT) or \
line.date_stop and line.date_stop < time.strftime(DEFAULT_SERVER_DATE_FORMAT):
return True
# all bom_line_id variant values must be in the product
if line.attribute_value_ids:
if not product or (set(map(int,line.attribute_value_ids or [])) - set(map(int,product.attribute_value_ids))):
return True
return False
def _prepare_wc_line(self, cr, uid, bom, wc_use, level=0, factor=1, context=None):
wc = wc_use.workcenter_id
d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle)
mult = (d + (m and 1.0 or 0.0))
cycle = mult * wc_use.cycle_nbr
return {
'name': tools.ustr(wc_use.name) + ' - ' + tools.ustr(bom.product_tmpl_id.name_get()[0][1]),
'workcenter_id': wc.id,
'sequence': level + (wc_use.sequence or 0),
'cycle': cycle,
'hour': float(wc_use.hour_nbr * mult + ((wc.time_start or 0.0) + (wc.time_stop or 0.0) + cycle * (wc.time_cycle or 0.0)) * (wc.time_efficiency or 100)),
}
def _prepare_consume_line(self, cr, uid, bom_line_id, quantity, context=None):
return {
'name': bom_line_id.product_id.name,
'product_id': bom_line_id.product_id.id,
'product_qty': quantity,
'product_uom': bom_line_id.product_uom.id
}
def _bom_explode(self, cr, uid, bom, product, factor, properties=None, level=0, routing_id=False, previous_products=None, master_bom=None, context=None):
""" Finds Products and Work Centers for related BoM for manufacturing order.
@param bom: BoM of particular product template.
@param product: Select a particular variant of the BoM. If False use BoM without variants.
@param factor: Factor represents the quantity, but in UoM of the BoM, taking into account the numbers produced by the BoM
@param properties: A List of properties Ids.
@param level: Depth level to find BoM lines starts from 10.
@param previous_products: List of product previously use by bom explore to avoid recursion
@param master_bom: When recursion, used to display the name of the master bom
@return: result: List of dictionaries containing product details.
result2: List of dictionaries containing Work Center details.
"""
uom_obj = self.pool.get("product.uom")
routing_obj = self.pool.get('mrp.routing')
master_bom = master_bom or bom
def _factor(factor, product_efficiency, product_rounding):
factor = factor / (product_efficiency or 1.0)
if product_rounding:
factor = tools.float_round(factor,
precision_rounding=product_rounding,
rounding_method='UP')
if factor < product_rounding:
factor = product_rounding
return factor
factor = _factor(factor, bom.product_efficiency, bom.product_rounding)
result = []
result2 = []
routing = (routing_id and routing_obj.browse(cr, uid, routing_id)) or bom.routing_id or False
if routing:
for wc_use in routing.workcenter_lines:
result2.append(self._prepare_wc_line(
cr, uid, bom, wc_use, level=level, factor=factor,
context=context))
for bom_line_id in bom.bom_line_ids:
if self._skip_bom_line(cr, uid, bom_line_id, product, context=context):
continue
if set(map(int, bom_line_id.property_ids or [])) - set(properties or []):
continue
if previous_products and bom_line_id.product_id.product_tmpl_id.id in previous_products:
raise UserError(_('BoM "%s" contains a BoM line with a product recursion: "%s".') % (master_bom.code or "", bom_line_id.product_id.name_get()[0][1]))
quantity = _factor(bom_line_id.product_qty * factor, bom_line_id.product_efficiency, bom_line_id.product_rounding)
bom_id = self._bom_find(cr, uid, product_id=bom_line_id.product_id.id, properties=properties, context=context)
#If BoM should not behave like kit, just add the product, otherwise explode further
if (not bom_id) or (self.browse(cr, uid, bom_id, context=context).type != "phantom"):
result.append(self._prepare_consume_line(
cr, uid, bom_line_id, quantity, context=context))
else:
all_prod = [bom.product_tmpl_id.id] + (previous_products or [])
bom2 = self.browse(cr, uid, bom_id, context=context)
# We need to convert to units/UoM of chosen BoM
factor2 = uom_obj._compute_qty(cr, uid, bom_line_id.product_uom.id, quantity, bom2.product_uom.id)
quantity2 = factor2 / bom2.product_qty
res = self._bom_explode(cr, uid, bom2, bom_line_id.product_id, quantity2,
properties=properties, level=level + 10, previous_products=all_prod, master_bom=master_bom, context=context)
result = result + res[0]
result2 = result2 + res[1]
return result, result2
def copy_data(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
bom_data = self.read(cr, uid, id, [], context=context)
default.update(name=_("%s (copy)") % (bom_data['display_name']))
return super(mrp_bom, self).copy_data(cr, uid, id, default, context=context)
def onchange_uom(self, cr, uid, ids, product_tmpl_id, product_uom, context=None):
res = {'value': {}}
if not product_uom or not product_tmpl_id:
return res
product = self.pool.get('product.template').browse(cr, uid, product_tmpl_id, context=context)
uom = self.pool.get('product.uom').browse(cr, uid, product_uom, context=context)
if uom.category_id.id != product.uom_id.category_id.id:
res['warning'] = {'title': _('Warning'), 'message': _('The Product Unit of Measure you chose has a different category than in the product form.')}
res['value'].update({'product_uom': product.uom_id.id})
return res
def unlink(self, cr, uid, ids, context=None):
if self.pool['mrp.production'].search(cr, uid, [('bom_id', 'in', ids), ('state', 'not in', ['done', 'cancel'])], context=context):
raise UserError(_('You can not delete a Bill of Material with running manufacturing orders.\nPlease close or cancel it first.'))
return super(mrp_bom, self).unlink(cr, uid, ids, context=context)
def onchange_product_tmpl_id(self, cr, uid, ids, product_tmpl_id, product_qty=0, context=None):
""" Changes UoM and name if product_id changes.
@param product_id: Changed product_id
@return: Dictionary of changed values
"""
res = {}
if product_tmpl_id:
prod = self.pool.get('product.template').browse(cr, uid, product_tmpl_id, context=context)
res['value'] = {
'product_uom': prod.uom_id.id,
}
return res
def name_get(self, cr, uid, ids, context=None):
res = []
for record in self.browse(cr, uid, ids, context=context):
name = record.product_tmpl_id.name
if record.code:
name = '[%s] %s' % (record.code, name)
res.append((record.id, name))
return res
class mrp_bom_line(osv.osv):
_name = 'mrp.bom.line'
_order = "sequence"
_rec_name = "product_id"
def _get_child_bom_lines(self, cr, uid, ids, field_name, arg, context=None):
"""If the BOM line refers to a BOM, return the ids of the child BOM lines"""
bom_obj = self.pool['mrp.bom']
res = {}
for bom_line in self.browse(cr, uid, ids, context=context):
bom_id = bom_obj._bom_find(cr, uid,
product_tmpl_id=bom_line.product_id.product_tmpl_id.id,
product_id=bom_line.product_id.id, context=context)
if bom_id:
child_bom = bom_obj.browse(cr, uid, bom_id, context=context)
res[bom_line.id] = [x.id for x in child_bom.bom_line_ids]
else:
res[bom_line.id] = False
return res
_columns = {
'product_id': fields.many2one('product.product', 'Product', required=True),
'product_qty': fields.float('Product Quantity', required=True, digits_compute=dp.get_precision('Product Unit of Measure')),
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True,
help="Unit of Measure (Unit of Measure) is the unit of measurement for the inventory control"),
'date_start': fields.date('Valid From', help="Validity of component. Keep empty if it's always valid."),
'date_stop': fields.date('Valid Until', help="Validity of component. Keep empty if it's always valid."),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying."),
'routing_id': fields.many2one('mrp.routing', 'Routing', help="The list of operations (list of work centers) to produce the finished product. The routing is mainly used to compute work center costs during operations and to plan future loads on work centers based on production planning."),
'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity."),
'product_efficiency': fields.float('Manufacturing Efficiency', required=True, help="A factor of 0.9 means a loss of 10% within the production process."),
'property_ids': fields.many2many('mrp.property', string='Properties'), #Not used
'bom_id': fields.many2one('mrp.bom', 'Parent BoM', ondelete='cascade', select=True, required=True),
'attribute_value_ids': fields.many2many('product.attribute.value', string='Variants', help="BOM Product Variants needed form apply this line."),
'child_line_ids': fields.function(_get_child_bom_lines, relation="mrp.bom.line", string="BOM lines of the referred bom", type="one2many")
}
def _get_uom_id(self, cr, uid, *args):
return self.pool["product.uom"].search(cr, uid, [], limit=1, order='id')[0]
_defaults = {
'product_qty': lambda *a: 1.0,
'product_efficiency': lambda *a: 1.0,
'product_rounding': lambda *a: 0.0,
'product_uom': _get_uom_id,
'sequence': 1,
}
_sql_constraints = [
('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
'You should install the mrp_byproduct module if you want to manage extra products on BoMs !'),
]
def create(self, cr, uid, values, context=None):
if context is None:
context = {}
product_obj = self.pool.get('product.product')
if 'product_id' in values and not 'product_uom' in values:
values['product_uom'] = product_obj.browse(cr, uid, values.get('product_id'), context=context).uom_id.id
return super(mrp_bom_line, self).create(cr, uid, values, context=context)
def onchange_uom(self, cr, uid, ids, product_id, product_uom, context=None):
res = {'value': {}}
if not product_uom or not product_id:
return res
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
uom = self.pool.get('product.uom').browse(cr, uid, product_uom, context=context)
if uom.category_id.id != product.uom_id.category_id.id:
res['warning'] = {'title': _('Warning'), 'message': _('The Product Unit of Measure you chose has a different category than in the product form.')}
res['value'].update({'product_uom': product.uom_id.id})
return res
def onchange_product_id(self, cr, uid, ids, product_id, product_qty=0, context=None):
""" Changes UoM if product_id changes.
@param product_id: Changed product_id
@return: Dictionary of changed values
"""
res = {}
if product_id:
prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
res['value'] = {
'product_uom': prod.uom_id.id,
}
return res
@@ -6,466 +6,12 @@ from collections import OrderedDict
import openerp.addons.decimal_precision as dp
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
from openerp.tools import float_compare, float_is_zero
from openerp.tools.translate import _
from openerp import tools, SUPERUSER_ID
from openerp import SUPERUSER_ID
from openerp.exceptions import UserError, AccessError
class mrp_property_group(osv.osv):
"""
Group of mrp properties.
"""
_name = 'mrp.property.group'
_description = 'Property Group'
_columns = {
'name': fields.char('Property Group', required=True),
'description': fields.text('Description'),
}
class mrp_property(osv.osv):
"""
Properties of mrp.
"""
_name = 'mrp.property'
_description = 'Property'
_columns = {
'name': fields.char('Name', required=True),
'composition': fields.selection([('min','min'),('max','max'),('plus','plus')], 'Properties composition', required=True, help="Not used in computations, for information purpose only."),
'group_id': fields.many2one('mrp.property.group', 'Property Group', required=True),
'description': fields.text('Description'),
}
_defaults = {
'composition': lambda *a: 'min',
}
#----------------------------------------------------------
# Work Centers
#----------------------------------------------------------
# capacity_hour : capacity per hour. default: 1.0.
# Eg: If 5 concurrent operations at one time: capacity = 5 (because 5 employees)
# unit_per_cycle : how many units are produced for one cycle
class mrp_workcenter(osv.osv):
_name = 'mrp.workcenter'
_description = 'Work Center'
_inherits = {'resource.resource':"resource_id"}
_columns = {
'active': fields.boolean('Active'),
'note': fields.text('Description', help="Description of the Work Center. Explain here what's a cycle according to this Work Center."),
'capacity_per_cycle': fields.float('Capacity per Cycle', help="Number of operations this Work Center can do in parallel. If this Work Center represents a team of 5 workers, the capacity per cycle is 5."),
'time_cycle': fields.float('Time for 1 cycle (hour)', help="Time in hours for doing one cycle."),
'time_start': fields.float('Time before prod.', help="Time in hours for the setup."),
'time_stop': fields.float('Time after prod.', help="Time in hours for the cleaning."),
'costs_hour': fields.float('Cost per hour', help="Specify Cost of Work Center per hour."),
'costs_hour_account_id': fields.many2one('account.analytic.account', 'Hour Account',
help="Fill this only if you want automatic analytic accounting entries on production orders."),
'costs_cycle': fields.float('Cost per cycle', help="Specify Cost of Work Center per cycle."),
'costs_cycle_account_id': fields.many2one('account.analytic.account', 'Cycle Account',
help="Fill this only if you want automatic analytic accounting entries on production orders."),
'costs_general_account_id': fields.many2one('account.account', 'General Account', domain=[('deprecated', '=', False)]),
'resource_id': fields.many2one('resource.resource','Resource', ondelete='cascade', required=True),
'product_id': fields.many2one('product.product','Work Center Product', help="Fill this product to easily track your production costs in the analytic accounting."),
}
_defaults = {
'active': True,
'capacity_per_cycle': 1.0,
'resource_type': 'material',
}
def on_change_product_cost(self, cr, uid, ids, product_id, context=None):
value = {}
if product_id:
cost = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
value = {'costs_hour': cost.standard_price}
return {'value': value}
def _check_capacity_per_cycle(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
if obj.capacity_per_cycle <= 0.0:
return False
return True
_constraints = [
(_check_capacity_per_cycle, 'The capacity per cycle must be strictly positive.', ['capacity_per_cycle']),
]
class mrp_routing(osv.osv):
"""
For specifying the routings of Work Centers.
"""
_name = 'mrp.routing'
_description = 'Routings'
_columns = {
'name': fields.char('Name', required=True),
'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the routing without removing it."),
'code': fields.char('Code', size=8),
'note': fields.text('Description'),
'workcenter_lines': fields.one2many('mrp.routing.workcenter', 'routing_id', 'Work Centers', copy=True),
'location_id': fields.many2one('stock.location', 'Production Location',
help="Keep empty if you produce at the location where the finished products are needed." \
"Set a location if you produce at a fixed location. This can be a partner location " \
"if you subcontract the manufacturing operations."
),
'company_id': fields.many2one('res.company', 'Company'),
}
_defaults = {
'active': lambda *a: 1,
'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.routing', context=context)
}
class mrp_routing_workcenter(osv.osv):
"""
Defines working cycles and hours of a Work Center using routings.
"""
_name = 'mrp.routing.workcenter'
_description = 'Work Center Usage'
_order = 'sequence, id'
_columns = {
'workcenter_id': fields.many2one('mrp.workcenter', 'Work Center', required=True),
'name': fields.char('Name', required=True),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of routing Work Centers."),
'cycle_nbr': fields.float('Number of Cycles', required=True,
help="Number of iterations this work center has to do in the specified operation of the routing."),
'hour_nbr': fields.float('Number of Hours', required=True, help="Time in hours for this Work Center to achieve the operation of the specified routing."),
'routing_id': fields.many2one('mrp.routing', 'Parent Routing', select=True, ondelete='cascade',
help="Routings indicates all the Work Centers used, for how long and/or cycles." \
"If Routings is set then,the third tab of a production order (Work Centers) will be automatically pre-completed."),
'note': fields.text('Description'),
'company_id': fields.related('routing_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
}
_defaults = {
'cycle_nbr': lambda *a: 1.0,
'hour_nbr': lambda *a: 0.0,
'sequence': 100,
}
class mrp_bom(osv.osv):
"""
Defines bills of material for a product.
"""
_name = 'mrp.bom'
_description = 'Bill of Material'
_inherit = ['mail.thread']
_columns = {
'code': fields.char('Reference', size=16),
'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the bills of material without removing it."),
'type': fields.selection([('normal','Manufacture this product'),('phantom','Ship this product as a set of components (kit)')], 'BoM Type', required=True,
help= "Set: When processing a sales order for this product, the delivery order will contain the raw materials, instead of the finished product."),
'position': fields.char('Internal Reference', help="Reference to a position in an external plan."),
'product_tmpl_id': fields.many2one('product.template', 'Product', domain="[('type', 'in', ['product', 'consu'])]", required=True),
'product_id': fields.many2one('product.product', 'Product Variant',
domain="['&', ('product_tmpl_id','=',product_tmpl_id), ('type', 'in', ['product', 'consu'])]",
help="If a product variant is defined the BOM is available only for this product."),
'bom_line_ids': fields.one2many('mrp.bom.line', 'bom_id', 'BoM Lines', copy=True),
'product_qty': fields.float('Product Quantity', required=True, digits_compute=dp.get_precision('Product Unit of Measure')),
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, help="Unit of Measure (Unit of Measure) is the unit of measurement for the inventory control"),
'date_start': fields.date('Valid From', help="Validity of this BoM. Keep empty if it's always valid."),
'date_stop': fields.date('Valid Until', help="Validity of this BoM. Keep empty if it's always valid."),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of bills of material."),
'routing_id': fields.many2one('mrp.routing', 'Routing', help="The list of operations (list of work centers) to produce the finished product. "\
"The routing is mainly used to compute work center costs during operations and to plan future loads on work centers based on production planning."),
'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity."),
'product_efficiency': fields.float('Manufacturing Efficiency', required=True, help="A factor of 0.9 means a loss of 10% during the production process."),
'property_ids': fields.many2many('mrp.property', string='Properties'),
'company_id': fields.many2one('res.company', 'Company', required=True),
}
def _get_uom_id(self, cr, uid, *args):
return self.pool["product.uom"].search(cr, uid, [], limit=1, order='id')[0]
_defaults = {
'active': lambda *a: 1,
'product_qty': lambda *a: 1.0,
'product_efficiency': lambda *a: 1.0,
'product_rounding': lambda *a: 0.0,
'type': lambda *a: 'normal',
'product_uom': _get_uom_id,
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.bom', context=c),
}
_order = "sequence"
def _bom_find(self, cr, uid, product_tmpl_id=None, product_id=None, properties=None, context=None):
""" Finds BoM for particular product and product uom.
@param product_tmpl_id: Selected product.
@param product_uom: Unit of measure of a product.
@param properties: List of related properties.
@return: False or BoM id.
"""
if not context:
context = {}
if properties is None:
properties = []
if product_id:
if not product_tmpl_id:
product_tmpl_id = self.pool['product.product'].browse(cr, uid, product_id, context=context).product_tmpl_id.id
domain = [
'|',
('product_id', '=', product_id),
'&',
('product_id', '=', False),
('product_tmpl_id', '=', product_tmpl_id)
]
elif product_tmpl_id:
domain = [('product_id', '=', False), ('product_tmpl_id', '=', product_tmpl_id)]
else:
# neither product nor template, makes no sense to search
return False
if context.get('company_id'):
domain = domain + [('company_id', '=', context['company_id'])]
domain = domain + [ '|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATE_FORMAT)),
'|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATE_FORMAT))]
# order to prioritize bom with product_id over the one without
ids = self.search(cr, uid, domain, order='sequence, product_id', context=context)
# Search a BoM which has all properties specified, or if you can not find one, you could
# pass a BoM without any properties with the smallest sequence
bom_empty_prop = False
for bom in self.pool.get('mrp.bom').browse(cr, uid, ids, context=context):
if not set(map(int, bom.property_ids or [])) - set(properties or []):
if not properties or bom.property_ids:
return bom.id
elif not bom_empty_prop:
bom_empty_prop = bom.id
return bom_empty_prop
def _skip_bom_line(self, cr, uid, line, product, context=None):
""" Control if a BoM line should be produce, can be inherited for add
custom control.
@param line: BoM line.
@param product: Selected product produced.
@return: True or False
"""
if line.date_start and line.date_start > time.strftime(DEFAULT_SERVER_DATE_FORMAT) or \
line.date_stop and line.date_stop < time.strftime(DEFAULT_SERVER_DATE_FORMAT):
return True
# all bom_line_id variant values must be in the product
if line.attribute_value_ids:
if not product or (set(map(int,line.attribute_value_ids or [])) - set(map(int,product.attribute_value_ids))):
return True
return False
def _prepare_wc_line(self, cr, uid, bom, wc_use, level=0, factor=1, context=None):
wc = wc_use.workcenter_id
d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle)
mult = (d + (m and 1.0 or 0.0))
cycle = mult * wc_use.cycle_nbr
return {
'name': tools.ustr(wc_use.name) + ' - ' + tools.ustr(bom.product_tmpl_id.name_get()[0][1]),
'workcenter_id': wc.id,
'sequence': level + (wc_use.sequence or 0),
'cycle': cycle,
'hour': float(wc_use.hour_nbr * mult + ((wc.time_start or 0.0) + (wc.time_stop or 0.0) + cycle * (wc.time_cycle or 0.0)) * (wc.time_efficiency or 100)),
}
def _prepare_consume_line(self, cr, uid, bom_line_id, quantity, context=None):
return {
'name': bom_line_id.product_id.name,
'product_id': bom_line_id.product_id.id,
'product_qty': quantity,
'product_uom': bom_line_id.product_uom.id
}
def _bom_explode(self, cr, uid, bom, product, factor, properties=None, level=0, routing_id=False, previous_products=None, master_bom=None, context=None):
""" Finds Products and Work Centers for related BoM for manufacturing order.
@param bom: BoM of particular product template.
@param product: Select a particular variant of the BoM. If False use BoM without variants.
@param factor: Factor represents the quantity, but in UoM of the BoM, taking into account the numbers produced by the BoM
@param properties: A List of properties Ids.
@param level: Depth level to find BoM lines starts from 10.
@param previous_products: List of product previously use by bom explore to avoid recursion
@param master_bom: When recursion, used to display the name of the master bom
@return: result: List of dictionaries containing product details.
result2: List of dictionaries containing Work Center details.
"""
uom_obj = self.pool.get("product.uom")
routing_obj = self.pool.get('mrp.routing')
master_bom = master_bom or bom
def _factor(factor, product_efficiency, product_rounding):
factor = factor / (product_efficiency or 1.0)
if product_rounding:
factor = tools.float_round(factor,
precision_rounding=product_rounding,
rounding_method='UP')
if factor < product_rounding:
factor = product_rounding
return factor
factor = _factor(factor, bom.product_efficiency, bom.product_rounding)
result = []
result2 = []
routing = (routing_id and routing_obj.browse(cr, uid, routing_id)) or bom.routing_id or False
if routing:
for wc_use in routing.workcenter_lines:
result2.append(self._prepare_wc_line(
cr, uid, bom, wc_use, level=level, factor=factor,
context=context))
for bom_line_id in bom.bom_line_ids:
if self._skip_bom_line(cr, uid, bom_line_id, product, context=context):
continue
if set(map(int, bom_line_id.property_ids or [])) - set(properties or []):
continue
if previous_products and bom_line_id.product_id.product_tmpl_id.id in previous_products:
raise UserError(_('BoM "%s" contains a BoM line with a product recursion: "%s".') % (master_bom.code or "", bom_line_id.product_id.name_get()[0][1]))
quantity = _factor(bom_line_id.product_qty * factor, bom_line_id.product_efficiency, bom_line_id.product_rounding)
bom_id = self._bom_find(cr, uid, product_id=bom_line_id.product_id.id, properties=properties, context=context)
#If BoM should not behave like kit, just add the product, otherwise explode further
if (not bom_id) or (self.browse(cr, uid, bom_id, context=context).type != "phantom"):
result.append(self._prepare_consume_line(
cr, uid, bom_line_id, quantity, context=context))
else:
all_prod = [bom.product_tmpl_id.id] + (previous_products or [])
bom2 = self.browse(cr, uid, bom_id, context=context)
# We need to convert to units/UoM of chosen BoM
factor2 = uom_obj._compute_qty(cr, uid, bom_line_id.product_uom.id, quantity, bom2.product_uom.id)
quantity2 = factor2 / bom2.product_qty
res = self._bom_explode(cr, uid, bom2, bom_line_id.product_id, quantity2,
properties=properties, level=level + 10, previous_products=all_prod, master_bom=master_bom, context=context)
result = result + res[0]
result2 = result2 + res[1]
return result, result2
def copy_data(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
bom_data = self.read(cr, uid, id, [], context=context)
default.update(name=_("%s (copy)") % (bom_data['display_name']))
return super(mrp_bom, self).copy_data(cr, uid, id, default, context=context)
def onchange_uom(self, cr, uid, ids, product_tmpl_id, product_uom, context=None):
res = {'value': {}}
if not product_uom or not product_tmpl_id:
return res
product = self.pool.get('product.template').browse(cr, uid, product_tmpl_id, context=context)
uom = self.pool.get('product.uom').browse(cr, uid, product_uom, context=context)
if uom.category_id.id != product.uom_id.category_id.id:
res['warning'] = {'title': _('Warning'), 'message': _('The Product Unit of Measure you chose has a different category than in the product form.')}
res['value'].update({'product_uom': product.uom_id.id})
return res
def unlink(self, cr, uid, ids, context=None):
if self.pool['mrp.production'].search(cr, uid, [('bom_id', 'in', ids), ('state', 'not in', ['done', 'cancel'])], context=context):
raise UserError(_('You can not delete a Bill of Material with running manufacturing orders.\nPlease close or cancel it first.'))
return super(mrp_bom, self).unlink(cr, uid, ids, context=context)
def onchange_product_tmpl_id(self, cr, uid, ids, product_tmpl_id, product_qty=0, context=None):
""" Changes UoM and name if product_id changes.
@param product_id: Changed product_id
@return: Dictionary of changed values
"""
res = {}
if product_tmpl_id:
prod = self.pool.get('product.template').browse(cr, uid, product_tmpl_id, context=context)
res['value'] = {
'product_uom': prod.uom_id.id,
}
return res
def name_get(self, cr, uid, ids, context=None):
res = []
for record in self.browse(cr, uid, ids, context=context):
name = record.product_tmpl_id.name
if record.code:
name = '[%s] %s' % (record.code, name)
res.append((record.id, name))
return res
class mrp_bom_line(osv.osv):
_name = 'mrp.bom.line'
_order = "sequence"
_rec_name = "product_id"
def _get_child_bom_lines(self, cr, uid, ids, field_name, arg, context=None):
"""If the BOM line refers to a BOM, return the ids of the child BOM lines"""
bom_obj = self.pool['mrp.bom']
res = {}
for bom_line in self.browse(cr, uid, ids, context=context):
bom_id = bom_obj._bom_find(cr, uid,
product_tmpl_id=bom_line.product_id.product_tmpl_id.id,
product_id=bom_line.product_id.id, context=context)
if bom_id:
child_bom = bom_obj.browse(cr, uid, bom_id, context=context)
res[bom_line.id] = [x.id for x in child_bom.bom_line_ids]
else:
res[bom_line.id] = False
return res
_columns = {
'product_id': fields.many2one('product.product', 'Product', required=True),
'product_qty': fields.float('Product Quantity', required=True, digits_compute=dp.get_precision('Product Unit of Measure')),
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True,
help="Unit of Measure (Unit of Measure) is the unit of measurement for the inventory control"),
'date_start': fields.date('Valid From', help="Validity of component. Keep empty if it's always valid."),
'date_stop': fields.date('Valid Until', help="Validity of component. Keep empty if it's always valid."),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying."),
'routing_id': fields.many2one('mrp.routing', 'Routing', help="The list of operations (list of work centers) to produce the finished product. The routing is mainly used to compute work center costs during operations and to plan future loads on work centers based on production planning."),
'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity."),
'product_efficiency': fields.float('Manufacturing Efficiency', required=True, help="A factor of 0.9 means a loss of 10% within the production process."),
'property_ids': fields.many2many('mrp.property', string='Properties'), #Not used
'bom_id': fields.many2one('mrp.bom', 'Parent BoM', ondelete='cascade', select=True, required=True),
'attribute_value_ids': fields.many2many('product.attribute.value', string='Variants', help="BOM Product Variants needed form apply this line."),
'child_line_ids': fields.function(_get_child_bom_lines, relation="mrp.bom.line", string="BOM lines of the referred bom", type="one2many")
}
def _get_uom_id(self, cr, uid, *args):
return self.pool["product.uom"].search(cr, uid, [], limit=1, order='id')[0]
_defaults = {
'product_qty': lambda *a: 1.0,
'product_efficiency': lambda *a: 1.0,
'product_rounding': lambda *a: 0.0,
'product_uom': _get_uom_id,
'sequence': 1,
}
_sql_constraints = [
('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
'You should install the mrp_byproduct module if you want to manage extra products on BoMs !'),
]
def create(self, cr, uid, values, context=None):
if context is None:
context = {}
product_obj = self.pool.get('product.product')
if 'product_id' in values and not 'product_uom' in values:
values['product_uom'] = product_obj.browse(cr, uid, values.get('product_id'), context=context).uom_id.id
return super(mrp_bom_line, self).create(cr, uid, values, context=context)
def onchange_uom(self, cr, uid, ids, product_id, product_uom, context=None):
res = {'value': {}}
if not product_uom or not product_id:
return res
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
uom = self.pool.get('product.uom').browse(cr, uid, product_uom, context=context)
if uom.category_id.id != product.uom_id.category_id.id:
res['warning'] = {'title': _('Warning'), 'message': _('The Product Unit of Measure you chose has a different category than in the product form.')}
res['value'].update({'product_uom': product.uom_id.id})
return res
def onchange_product_id(self, cr, uid, ids, product_id, product_qty=0, context=None):
""" Changes UoM if product_id changes.
@param product_id: Changed product_id
@return: Dictionary of changed values
"""
res = {}
if product_id:
prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
res['value'] = {
'product_uom': prod.uom_id.id,
}
return res
class mrp_production(osv.osv):
"""
Production Orders / Manufacturing Orders
+33
View File
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
class mrp_property_group(osv.osv):
"""
Group of mrp properties.
"""
_name = 'mrp.property.group'
_description = 'Property Group'
_columns = {
'name': fields.char('Property Group', required=True),
'description': fields.text('Description'),
}
class mrp_property(osv.osv):
"""
Properties of mrp.
"""
_name = 'mrp.property'
_description = 'Property'
_columns = {
'name': fields.char('Name', required=True),
'composition': fields.selection([('min','min'),('max','max'),('plus','plus')], 'Properties composition', required=True, help="Not used in computations, for information purpose only."),
'group_id': fields.many2one('mrp.property.group', 'Property Group', required=True),
'description': fields.text('Description'),
}
_defaults = {
'composition': lambda *a: 'min',
}
+57
View File
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
class mrp_routing(osv.osv):
"""
For specifying the routings of Work Centers.
"""
_name = 'mrp.routing'
_description = 'Routings'
_columns = {
'name': fields.char('Name', required=True),
'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the routing without removing it."),
'code': fields.char('Code', size=8),
'note': fields.text('Description'),
'workcenter_lines': fields.one2many('mrp.routing.workcenter', 'routing_id', 'Work Centers', copy=True),
'location_id': fields.many2one('stock.location', 'Production Location',
help="Keep empty if you produce at the location where the finished products are needed." \
"Set a location if you produce at a fixed location. This can be a partner location " \
"if you subcontract the manufacturing operations."
),
'company_id': fields.many2one('res.company', 'Company'),
}
_defaults = {
'active': lambda *a: 1,
'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.routing', context=context)
}
class mrp_routing_workcenter(osv.osv):
"""
Defines working cycles and hours of a Work Center using routings.
"""
_name = 'mrp.routing.workcenter'
_description = 'Work Center Usage'
_order = 'sequence, id'
_columns = {
'workcenter_id': fields.many2one('mrp.workcenter', 'Work Center', required=True),
'name': fields.char('Name', required=True),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of routing Work Centers."),
'cycle_nbr': fields.float('Number of Cycles', required=True,
help="Number of iterations this work center has to do in the specified operation of the routing."),
'hour_nbr': fields.float('Number of Hours', required=True, help="Time in hours for this Work Center to achieve the operation of the specified routing."),
'routing_id': fields.many2one('mrp.routing', 'Parent Routing', select=True, ondelete='cascade',
help="Routings indicates all the Work Centers used, for how long and/or cycles." \
"If Routings is set then,the third tab of a production order (Work Centers) will be automatically pre-completed."),
'note': fields.text('Description'),
'company_id': fields.related('routing_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
}
_defaults = {
'cycle_nbr': lambda *a: 1.0,
'hour_nbr': lambda *a: 0.0,
'sequence': 100,
}
+57
View File
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
#----------------------------------------------------------
# Work Centers
#----------------------------------------------------------
# capacity_hour : capacity per hour. default: 1.0.
# Eg: If 5 concurrent operations at one time: capacity = 5 (because 5 employees)
# unit_per_cycle : how many units are produced for one cycle
class mrp_workcenter(osv.osv):
_name = 'mrp.workcenter'
_description = 'Work Center'
_inherits = {'resource.resource':"resource_id"}
_columns = {
'active': fields.boolean('Active'),
'note': fields.text('Description', help="Description of the Work Center. Explain here what's a cycle according to this Work Center."),
'capacity_per_cycle': fields.float('Capacity per Cycle', help="Number of operations this Work Center can do in parallel. If this Work Center represents a team of 5 workers, the capacity per cycle is 5."),
'time_cycle': fields.float('Time for 1 cycle (hour)', help="Time in hours for doing one cycle."),
'time_start': fields.float('Time before prod.', help="Time in hours for the setup."),
'time_stop': fields.float('Time after prod.', help="Time in hours for the cleaning."),
'costs_hour': fields.float('Cost per hour', help="Specify Cost of Work Center per hour."),
'costs_hour_account_id': fields.many2one('account.analytic.account', 'Hour Account',
help="Fill this only if you want automatic analytic accounting entries on production orders."),
'costs_cycle': fields.float('Cost per cycle', help="Specify Cost of Work Center per cycle."),
'costs_cycle_account_id': fields.many2one('account.analytic.account', 'Cycle Account',
help="Fill this only if you want automatic analytic accounting entries on production orders."),
'costs_general_account_id': fields.many2one('account.account', 'General Account', domain=[('deprecated', '=', False)]),
'resource_id': fields.many2one('resource.resource','Resource', ondelete='cascade', required=True),
'product_id': fields.many2one('product.product','Work Center Product', help="Fill this product to easily track your production costs in the analytic accounting."),
}
_defaults = {
'active': True,
'capacity_per_cycle': 1.0,
'resource_type': 'material',
}
def on_change_product_cost(self, cr, uid, ids, product_id, context=None):
value = {}
if product_id:
cost = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
value = {'costs_hour': cost.standard_price}
return {'value': value}
def _check_capacity_per_cycle(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
if obj.capacity_per_cycle <= 0.0:
return False
return True
_constraints = [
(_check_capacity_per_cycle, 'The capacity per cycle must be strictly positive.', ['capacity_per_cycle']),
]
@@ -222,79 +222,3 @@ class StockMove(osv.osv):
if self.pool.get('mrp.production').test_ready(cr, uid, [order_id]):
workflow.trg_validate(uid, 'mrp.production', order_id, 'moves_ready', cr)
return res
class stock_warehouse(osv.osv):
_inherit = 'stock.warehouse'
_columns = {
'manufacture_to_resupply': fields.boolean('Manufacture in this Warehouse',
help="When products are manufactured, they can be manufactured in this warehouse."),
'manufacture_pull_id': fields.many2one('procurement.rule', 'Manufacture Rule'),
}
_defaults = {
'manufacture_to_resupply': True,
}
def _get_manufacture_pull_rule(self, cr, uid, warehouse, context=None):
route_obj = self.pool.get('stock.location.route')
data_obj = self.pool.get('ir.model.data')
try:
manufacture_route_id = data_obj.get_object_reference(cr, uid, 'mrp', 'route_warehouse0_manufacture')[1]
except:
manufacture_route_id = route_obj.search(cr, uid, [('name', 'like', _('Manufacture'))], context=context)
manufacture_route_id = manufacture_route_id and manufacture_route_id[0] or False
if not manufacture_route_id:
raise UserError(_('Can\'t find any generic Manufacture route.'))
return {
'name': warehouse._format_routename(_(' Manufacture')),
'location_id': warehouse.lot_stock_id.id,
'route_id': manufacture_route_id,
'action': 'manufacture',
'picking_type_id': warehouse.int_type_id.id,
'propagate': False,
'warehouse_id': warehouse.id,
}
def create_routes(self, cr, uid, ids, context=None):
pull_obj = self.pool.get('procurement.rule')
res = super(stock_warehouse, self).create_routes(cr, uid, ids, context=context)
warehouse = self.browse(cr, uid, ids, context=context)[0]
if warehouse.manufacture_to_resupply:
manufacture_pull_vals = self._get_manufacture_pull_rule(cr, uid, warehouse, context=context)
manufacture_pull_id = pull_obj.create(cr, uid, manufacture_pull_vals, context=context)
res['manufacture_pull_id'] = manufacture_pull_id
return res
def write(self, cr, uid, ids, vals, context=None):
pull_obj = self.pool.get('procurement.rule')
if isinstance(ids, (int, long)):
ids = [ids]
if 'manufacture_to_resupply' in vals:
if vals.get("manufacture_to_resupply"):
for warehouse in self.browse(cr, uid, ids, context=context):
if not warehouse.manufacture_pull_id:
manufacture_pull_vals = self._get_manufacture_pull_rule(cr, uid, warehouse, context=context)
manufacture_pull_id = pull_obj.create(cr, uid, manufacture_pull_vals, context=context)
vals['manufacture_pull_id'] = manufacture_pull_id
else:
for warehouse in self.browse(cr, uid, ids, context=context):
if warehouse.manufacture_pull_id:
pull_obj.unlink(cr, uid, warehouse.manufacture_pull_id.id, context=context)
return super(stock_warehouse, self).write(cr, uid, ids, vals, context=context)
@api.multi
def _get_all_routes(self):
routes = super(stock_warehouse, self).get_all_routes_for_wh()
routes |= self.filtered(lambda self: self.manufacture_to_resupply and self.manufacture_pull_id and self.manufacture_pull_id.route_id).mapped('manufacture_pull_id').mapped('route_id')
return routes
def _handle_renaming(self, cr, uid, ids, name, code, context=None):
res = super(stock_warehouse, self)._handle_renaming(cr, uid, ids, name, code, context=context)
warehouse = self.browse(cr, uid, ids[0], context=context)
pull_obj = self.pool.get('procurement.rule')
#change the manufacture procurement rule name
if warehouse.manufacture_pull_id:
pull_obj.write(cr, uid, warehouse.manufacture_pull_id.id, {'name': warehouse.manufacture_pull_id.name.replace(warehouse.name, name, 1)}, context=context)
return res
+86
View File
@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import api
from openerp.osv import fields
from openerp.osv import osv
from openerp.tools.translate import _
from openerp.exceptions import UserError
class stock_warehouse(osv.osv):
_inherit = 'stock.warehouse'
_columns = {
'manufacture_to_resupply': fields.boolean('Manufacture in this Warehouse',
help="When products are manufactured, they can be manufactured in this warehouse."),
'manufacture_pull_id': fields.many2one('procurement.rule', 'Manufacture Rule'),
}
_defaults = {
'manufacture_to_resupply': True,
}
def _get_manufacture_pull_rule(self, cr, uid, warehouse, context=None):
route_obj = self.pool.get('stock.location.route')
data_obj = self.pool.get('ir.model.data')
try:
manufacture_route_id = data_obj.get_object_reference(cr, uid, 'mrp', 'route_warehouse0_manufacture')[1]
except:
manufacture_route_id = route_obj.search(cr, uid, [('name', 'like', _('Manufacture'))], context=context)
manufacture_route_id = manufacture_route_id and manufacture_route_id[0] or False
if not manufacture_route_id:
raise UserError(_('Can\'t find any generic Manufacture route.'))
return {
'name': warehouse._format_routename(_(' Manufacture')),
'location_id': warehouse.lot_stock_id.id,
'route_id': manufacture_route_id,
'action': 'manufacture',
'picking_type_id': warehouse.int_type_id.id,
'propagate': False,
'warehouse_id': warehouse.id,
}
def create_routes(self, cr, uid, ids, context=None):
pull_obj = self.pool.get('procurement.rule')
res = super(stock_warehouse, self).create_routes(cr, uid, ids, context=context)
warehouse = self.browse(cr, uid, ids, context=context)[0]
if warehouse.manufacture_to_resupply:
manufacture_pull_vals = self._get_manufacture_pull_rule(cr, uid, warehouse, context=context)
manufacture_pull_id = pull_obj.create(cr, uid, manufacture_pull_vals, context=context)
res['manufacture_pull_id'] = manufacture_pull_id
return res
def write(self, cr, uid, ids, vals, context=None):
pull_obj = self.pool.get('procurement.rule')
if isinstance(ids, (int, long)):
ids = [ids]
if 'manufacture_to_resupply' in vals:
if vals.get("manufacture_to_resupply"):
for warehouse in self.browse(cr, uid, ids, context=context):
if not warehouse.manufacture_pull_id:
manufacture_pull_vals = self._get_manufacture_pull_rule(cr, uid, warehouse, context=context)
manufacture_pull_id = pull_obj.create(cr, uid, manufacture_pull_vals, context=context)
vals['manufacture_pull_id'] = manufacture_pull_id
else:
for warehouse in self.browse(cr, uid, ids, context=context):
if warehouse.manufacture_pull_id:
pull_obj.unlink(cr, uid, warehouse.manufacture_pull_id.id, context=context)
return super(stock_warehouse, self).write(cr, uid, ids, vals, context=context)
@api.multi
def _get_all_routes(self):
routes = super(stock_warehouse, self).get_all_routes_for_wh()
routes |= self.filtered(lambda self: self.manufacture_to_resupply and self.manufacture_pull_id and self.manufacture_pull_id.route_id).mapped('manufacture_pull_id').mapped('route_id')
return routes
def _handle_renaming(self, cr, uid, ids, name, code, context=None):
res = super(stock_warehouse, self)._handle_renaming(cr, uid, ids, name, code, context=context)
warehouse = self.browse(cr, uid, ids[0], context=context)
pull_obj = self.pool.get('procurement.rule')
#change the manufacture procurement rule name
if warehouse.manufacture_pull_id:
pull_obj.write(cr, uid, warehouse.manufacture_pull_id.id, {'name': warehouse.manufacture_pull_id.name.replace(warehouse.name, name, 1)}, context=context)
return res
-8
View File
@@ -1,8 +0,0 @@
-
!python {model: ir.model.data} : |
account_id = self.pool.get('account.account').search(cr, uid, [('user_type_id', '=', ref('account.data_account_type_expenses')), ('tag_ids', 'in', [ref('account.account_tag_operating')])])
if not account_id:
account_id = self.pool.get('account.account').search(cr, uid, [('user_type_id', '=', ref('account.data_account_type_expenses'))])
if account_id:
self._update(cr, uid, 'mrp.workcenter', 'mrp', {'costs_general_account_id': account_id[0]}, 'mrp_workcenter_0')
-
File diff suppressed because it is too large Load Diff
+225
View File
@@ -0,0 +1,225 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Bill of Materials -->
<record id="mrp_bom_form_view" model="ir.ui.view">
<field name="name">mrp.bom.form</field>
<field name="model">mrp.bom</field>
<field name="arch" type="xml">
<form string="Bill of Material">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
</button>
</div>
<group>
<group>
<field name="product_tmpl_id" on_change="onchange_product_tmpl_id(product_tmpl_id, product_qty, context)"/>
<field name="product_id" groups="product.group_product_variant"/>
<label for="product_qty" string="Quantity"/>
<div>
<field name="product_qty" class="oe_inline" on_change="onchange_product_tmpl_id(product_tmpl_id, product_qty, context)"/>
<field name="product_uom" options="{'no_open':True,'no_create':True}" class="oe_inline" on_change="onchange_uom(product_tmpl_id, product_uom)" groups="product.group_uom"/>
</div>
<label for="routing_id" class="oe_inline" groups="mrp.group_mrp_routings"/>
<div groups="mrp.group_mrp_routings">
<field name="routing_id" class="oe_inline"/>
</div>
</group>
<group>
<field name="code"/>
<field name="type" widget="radio"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
</group>
</group>
<notebook>
<page string="Components">
<field name="bom_line_ids" widget="one2many_list">
<tree string="Components" editable="bottom">
<field name="sequence" widget="handle"/>
<field name="product_id" on_change="onchange_product_id(product_id, product_qty)"/>
<field name="product_qty"/>
<field name="product_uom" options="{'no_open':True,'no_create':True}" on_change="onchange_uom(product_id, product_uom)" groups="product.group_uom"/>
<field name="product_rounding" groups="mrp.group_rounding_efficiency"/>
<field name="product_efficiency" groups="mrp.group_rounding_efficiency"/>
<field name="date_start"/>
<field name="date_stop"/>
<field name="attribute_value_ids" widget="many2many_tags" domain="[('product_ids.product_tmpl_id', '=', parent.product_tmpl_id)]" groups="product.group_product_variant"/>
<field name="property_ids" widget="many2many_tags" groups="product.group_mrp_properties"/>
</tree>
</field>
</page>
<page string="Properties" groups="base.group_no_one">
<group>
<group>
<field name="position"/>
<field name="sequence"/>
</group>
<group>
<field name="date_start"/>
<field name="date_stop"/>
<field name="product_rounding"/>
<field name="product_efficiency"/>
</group>
</group>
<separator string="Properties" groups="product.group_mrp_properties"/>
<group>
<group>
<field name="property_ids" widget="many2many_tags" groups="product.group_mrp_properties"/>
</group>
<group/>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
</div>
</form>
</field>
</record>
<record id="mrp_bom_tree_view" model="ir.ui.view">
<field name="name">mrp.bom.tree</field>
<field name="model">mrp.bom</field>
<field name="arch" type="xml">
<tree string="Bill of Materials" default_order="product_tmpl_id">
<field name="active" invisible="1"/>
<field name="product_tmpl_id"/>
<field name="code"/>
<field name="product_id" groups="product.group_product_variant"/>
<field name="product_qty"/>
<field name="product_uom" groups="product.group_uom"/>
</tree>
</field>
</record>
<record id="view_mrp_bom_filter" model="ir.ui.view">
<field name="name">mrp.bom.select</field>
<field name="model">mrp.bom</field>
<field name="arch" type="xml">
<search string="Search Bill Of Material">
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<field name="code"/>
<field name="bom_line_ids" string="Components"/>
<field name="product_tmpl_id" string="Product"/>
<field name="company_id" groups="base.group_multi_company"/>
<group expand="0" string="Group By...">
<filter string="Product" domain="[]" context="{'group_by':'product_tmpl_id'}"/>
<filter string='Default Unit of Measure' domain="[]" context="{'group_by' : 'product_uom'}"/>
<filter string="Routings" domain="[]" context="{'group_by':'routing_id'}"/>
<filter string="Valid From Month" domain="[]" context="{'group_by':'date_start'}" help="Valid From Date by Month"/>
</group>
</search>
</field>
</record>
<record id="mrp_bom_form_action" model="ir.actions.act_window">
<field name="name">Bill of Materials</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.bom</field>
<field name="domain">[]</field> <!-- force empty -->
<field name="view_type">form</field>
<field name="search_view_id" ref="view_mrp_bom_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a bill of material.
</p><p>
Bills of Materials allow you to define the list of required raw
materials used to make a finished product; through a manufacturing
order or a pack of products.
</p><p>
Odoo uses these BoMs to automatically propose manufacturing
orders according to procurement needs.
</p>
</field>
</record>
<menuitem id="menu_mrp_bom_form_action"
action="mrp_bom_form_action"
parent="menu_mrp_bom"
sequence="99"/>
<!-- BOM Line -->
<record id="mrp_bom_component_tree_view" model="ir.ui.view">
<field name="name">mrp.bom.component.tree</field>
<field name="model">mrp.bom.line</field>
<field name="arch" type="xml">
<tree string="Components" editable="top">
<field name="sequence" widget="handle"/>
<field name="bom_id"/>
<field name="product_id" on_change="onchange_product_id(product_id)"/>
<field name="product_qty"/>
<field name="product_rounding"/>
<field name="product_efficiency"/>
<field name="product_uom" options="{'no_open':True,'no_create':True}" on_change="onchange_uom(product_id, product_uom)" groups="product.group_uom"/>
<field name="date_start"/>
<field name="date_stop"/>
<field name="routing_id"/>
<field name="attribute_value_ids" widget="many2many_tags" groups="product.group_product_variant"/>
<field name="property_ids" widget="many2many_tags" groups="product.group_mrp_properties"/>
</tree>
</field>
</record>
<record id="mrp_bom_line_tree_view" model="ir.ui.view">
<field name="name">mrp.bom.tree</field>
<field name="model">mrp.bom.line</field>
<field name="field_parent">child_line_ids</field>
<field name="arch" type="xml">
<tree string="Bill of Materials">
<field name="sequence" invisible="1"/>
<field name="product_id"/>
<field name="product_uom" options="{'no_open':True,'no_create':True}" groups="product.group_uom"/>
<field name="routing_id" groups="mrp.group_mrp_routings"/>
<field name="date_start"/>
<field name="date_stop"/>
</tree>
</field>
</record>
<record id="view_mrp_bom_line_filter" model="ir.ui.view">
<field name="name">mrp.bom.line.select</field>
<field name="model">mrp.bom.line</field>
<field name="arch" type="xml">
<search string="Search Bill Of Material Components">
<field name="bom_id"/>
<field name="product_id"/>
<group expand="0" string="Group By...">
<filter string="Bill Of Material" domain="[]" context="{'group_by':'bom_id'}"/>
<filter string="Product" domain="[]" context="{'group_by':'product_id'}"/>
<filter string='Default Unit of Measure' domain="[]" context="{'group_by' : 'product_uom'}"/>
<filter string="Routings" domain="[]" context="{'group_by':'routing_id'}"/>
<filter string=" Valid From Month" domain="[]" context="{'group_by':'date_start'}" help="Valid From Date by Month"/>
</group>
</search>
</field>
</record>
<record id="action2" model="ir.actions.act_window">
<field name="name">Bill of Materials Structure</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.bom.line</field>
<field name="domain">[('bom_id', 'in', active_ids)]</field>
<field name="view_type">tree</field>
<field name="view_id" ref="mrp_bom_line_tree_view"/>
<field name="view_type">tree</field>
</record>
<record id="ir_BOM_structure" model="ir.values">
<field eval="'client_action_multi'" name="key2"/>
<field eval="'mrp.bom'" name="model"/>
<field name="name">Bill of Materials Structure</field>
<field eval="'ir.actions.act_window,'+str(action2)" name="value"/>
</record>
<record id="template_open_bom" model="ir.actions.act_window">
<field name="context">{'default_product_tmpl_id': active_id, 'search_default_product_tmpl_id': active_id}</field>
<field name="name">Bill of Materials</field>
<field name="res_model">mrp.bom</field>
<field name="view_type">form</field>
</record>
<record id="product_open_bom" model="ir.actions.act_window">
<field name="context">{'default_product_id': active_id, 'search_default_product_id': active_id}</field>
<field name="name">Bill of Materials</field>
<field name="res_model">mrp.bom</field>
<field name="domain">[]</field> <!-- Force empty -->
<field name="view_type">form</field>
</record>
</data>
</openerp>
+384
View File
@@ -0,0 +1,384 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="mrp_production_form_view" model="ir.ui.view">
<field name="name">mrp.production.form</field>
<field name="model">mrp.production</field>
<field name="arch" type="xml">
<form string="Manufacturing Orders">
<header>
<button name="button_confirm" states="draft" string="Confirm Production" class="oe_highlight"/>
<button name="%(act_mrp_product_produce)d" states="ready,in_production" string="Produce" type="action" class="oe_highlight"/>
<button name="action_assign" states="confirmed,picking_except" string="Reserve" type="object" class="oe_highlight"/>
<button name="force_production" states="confirmed" string="Force Reservation" type="object"/>
<button name="button_produce" states="ready" string="Mark as Started"/>
<button name="button_cancel" states="draft,ready,in_production" string="Cancel Production"/>
<button name="action_cancel" type="object" states="confirmed" string="Cancel Production"/>
<field name="state" widget="statusbar" statusbar_visible="draft,ready,in_production,done" statusbar_colors='{"confirmed":"blue"}'/>
</header>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name" placeholder="Manufacturing Reference"/></h1>
</div>
<group>
<group>
<field name="product_id" on_change="product_id_change(product_id, product_qty)" domain="[('bom_ids','!=',False),('bom_ids.type','!=','phantom')]" context='{"default_type": "product"}'/>
<field name="product_tmpl_id" invisible="1"/>
<label for="product_qty"/>
<div class="o_row">
<field name="product_qty" on_change="product_id_change(product_id, product_qty)"/>
<field name="product_uom" options="{'no_open':True,'no_create':True}" groups="product.group_uom"/>
<button type="action"
name="%(mrp.action_change_production_qty)d"
string="Update" states="confirmed" class="oe_edit_only oe_link"/>
</div>
<field name="date_planned"/>
</group>
<group>
<field name="bom_id"
domain="['|',
('product_id','=',product_id),
'&amp;',
('product_tmpl_id.product_variant_ids','=',product_id),
('product_id','=',False)]"
context="{'default_product_id': product_id, 'default_product_tmpl_id': product_tmpl_id}" on_change="bom_id_change(bom_id)" required="1"/>
<field name="routing_id" groups="mrp.group_mrp_routings"/>
<field name="user_id"/>
<field name="origin"/>
</group>
<group groups="stock.group_stock_multi_locations">
<field name="location_src_id" domain="[('usage','=','internal')]" on_change="location_id_change(location_src_id,location_dest_id)"/>
<field name="location_dest_id" domain="[('usage','=','internal')]"/>
</group>
</group>
<notebook>
<page string="Consumed Products">
<group>
<group string="Products to Consume">
<field name="move_lines" nolabel="1" options="{'reload_on_button': true}">
<tree decoration-info="state == 'draft'" decoration-muted="state in ('cancel','done')" decoration-danger="state in ('confirmed','waiting')" string="Products to Consume">
<field name="product_id"/>
<field name="product_uom_qty" string="Quantity"/>
<field name="product_uom" options="{'no_open':True,'no_create':True}" string="Unit of Measure" groups="product.group_uom"/>
<field name="state" invisible="1"/>
<button name="%(mrp.move_consume)d"
string="Consume Products" type="action"
icon="gtk-go-forward" context="{'consume': True}"
states="assigned"
/>
<button name="%(stock.move_scrap)d"
string="Scrap Products" type="action"
icon="terp-gtk-jump-to-ltr" context="{'scrap': True}"
states="draft,waiting,confirmed,assigned"/>
</tree>
</field>
</group>
<group string="Consumed Products">
<field name="move_lines2" nolabel="1" options="{'reload_on_button': true}">
<tree decoration-danger="scrapped==True" decoration-info="state == 'draft'" decoration-muted="state == 'cancel'" string="Consumed Products" editable="bottom">
<field name="product_id" readonly="1"/>
<field name="restrict_lot_id" context="{'product_id': product_id}" groups="stock.group_production_lot"/>
<field name="product_uom_qty" readonly="1"/>
<field name="product_uom" options="{'no_open':True,'no_create':True}" readonly="1" string="Unit of Measure" groups="product.group_uom"/>
<field name="state" invisible="1"/>
<field name="scrapped" invisible="1"/>
</tree>
</field>
</group>
</group>
</page>
<page string="Finished Products">
<group>
<group string="Products to Produce">
<field name="move_created_ids" nolabel="1" options="{'reload_on_button': true}">
<tree string="Products to Finish">
<field name="product_id"/>
<field name="product_uom_qty"/>
<field name="product_uom" options="{'no_open':True,'no_create':True}" string="Unit of Measure" groups="product.group_uom"/>
<field name="state" invisible="1"/>
</tree>
</field>
</group>
<group string="Produced Products">
<field name="move_created_ids2" nolabel="1" options="{'reload_on_button': true}">
<tree decoration-danger="scrapped==True" decoration-info="state == 'draft'" decoration-muted="state == 'cancel'" string="Finished Products">
<field name="product_id" readonly="1"/>
<field name="product_uom_qty" readonly="1"/>
<field name="restrict_lot_id" groups="stock.group_production_lot"/>
<field name="product_uom" options="{'no_open':True,'no_create':True}" readonly="1" string="Unit of Measure" groups="product.group_uom"/>
<field name="location_dest_id" readonly="1" string="Destination Loc." widget="selection" groups="stock.group_stock_multi_locations"/>
<field name="scrapped" invisible="1"/>
<field name="state" invisible="1"/>
<button name="%(stock.move_scrap)d"
string="Scrap Products" type="action" icon="terp-gtk-jump-to-ltr"
states="done,cancel"/>
</tree>
</field>
</group>
</group>
</page>
<page string="Work Orders" groups="mrp.group_mrp_routings">
<button name="action_compute" states="draft"
string="Compute Data" type="object"
icon="terp-stock_format-scientific"/>
<field name="workcenter_lines" options="{'reload_on_button': true}">
<form string="Production Work Centers">
<group col="4">
<field colspan="4" name="name"/>
<field name="workcenter_id"/>
<field name="sequence"/>
<field name="cycle"/>
<field name="hour" widget="float_time"/>
</group>
</form>
<tree string="Production Work Centers">
<field name="sequence"/>
<field name="name"/>
<field name="workcenter_id"/>
<field name="cycle"/>
<field name="hour" widget="float_time"/>
</tree>
</field>
</page>
<page string="Scheduled Products">
<button name="action_compute" states="draft"
string="Compute Data" type="object"
icon="terp-stock_format-scientific"/>
<field name="product_lines"/>
</page>
<page string="Extra Information">
<group>
<group>
<field name="priority"/>
<field name="date_start" invisible="1"/>
<field name="date_finished" invisible="1"/>
</group>
<group>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
<field name="move_prod_id" groups="base.group_no_one"/>
</group>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record id="mrp_production_tree_view" model="ir.ui.view">
<field name="name">mrp.production.tree</field>
<field name="model">mrp.production</field>
<field name="arch" type="xml">
<tree decoration-bf="message_needaction==True" decoration-info="state in ('draft','confirmed')" decoration-danger="date_planned&lt;current_date and state not in ('done','cancel')" decoration-muted="state in ('done','cancel')" string="Manufacturing Orders">
<field name="message_needaction" invisible="1"/>
<field name="name"/>
<field name="date_planned"/>
<field name="product_id"/>
<field name="product_qty" sum="Total Qty"/>
<field name="product_uom" options="{'no_open':True,'no_create':True}" groups="product.group_uom"/>
<field name="routing_id" groups="mrp.group_mrp_routings"/>
<field name="hour_total" sum="Total Hours"/>
<field name="cycle_total" sum="Total Cycles"/>
<field name="origin"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="mrp_production_kanban_view" model="ir.ui.view">
<field name="name">mrp.production.kanban</field>
<field name="model">mrp.production</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile">
<field name="name"/>
<field name="product_id"/>
<field name="product_qty"/>
<field name="product_uom" options="{'no_open':True,'no_create':True}"/>
<field name="date_planned"/>
<field name="state"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
<div class="row">
<div class="col-xs-6">
<strong><span><t t-esc="record.product_id.value"/></span></strong>
</div>
<div class="col-xs-6">
<strong><span class="pull-right text-right"><t t-esc="record.product_qty.value"/> <t t-esc="record.product_uom.value"/></span></strong>
</div>
</div>
<div class="row">
<div class="col-xs-6 text-muted">
<span><t t-esc="record.name.value"/> <t t-esc="record.date_planned.value and record.date_planned.value.split(' ')[0] or False"/></span>
</div>
<div class="col-xs-6">
<span class="pull-right text-right">
<field name="state" widget="kanban_label_selection" options="{'classes': {'draft': 'default', 'cancel': 'default', 'none': 'danger', 'confirmed': 'warning',
'done': 'success'}}"/>
</span>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="view_production_calendar" model="ir.ui.view">
<field name="name">mrp.production.calendar</field>
<field name="model">mrp.production</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<calendar color="routing_id" date_start="date_planned" string="Manufacturing Orders">
<field name="name"/>
<field name="product_id"/>
<field name="product_qty"/>
</calendar>
</field>
</record>
<record id="view_production_gantt" model="ir.ui.view">
<field name="name">mrp.production.gantt</field>
<field name="model">mrp.production</field>
<field name="arch" type="xml">
<gantt date_stop="date_finished" date_start="date_start" progress="progress" string="Productions" default_group_by="routing_id">
</gantt>
</field>
</record>
<record id="view_production_pivot" model="ir.ui.view">
<field name="name">mrp.production.pivot</field>
<field name="model">mrp.production</field>
<field name="arch" type="xml">
<pivot string="Manufacturing Orders">
<field name="date_planned" type="row"/>
<field name="hour_total" type="measure"/>
<field name="cycle_total" type="measure"/>
</pivot>
</field>
</record>
<record id="view_production_graph" model="ir.ui.view">
<field name="name">mrp.production.graph</field>
<field name="model">mrp.production</field>
<field name="arch" type="xml">
<graph string="Manufacturing Orders">
<field name="date_planned"/>
<field name="hour_total" type="measure"/>
<field name="cycle_total" type="measure"/>
</graph>
</field>
</record>
<record id="view_mrp_production_filter" model="ir.ui.view">
<field name="name">mrp.production.select</field>
<field name="model">mrp.production</field>
<field name="arch" type="xml">
<search string="Search Production">
<field name="name" string="Production" filter_domain="['|',('name','ilike',self),('origin','ilike',self)]"/>
<filter string="Pending" name="pending" domain="[('state','=','confirmed')]"
help="Manufacturing Orders which are waiting for raw materials."/>
<filter string="Ready" name="ready" domain="[('state','=','ready')]"
help="Manufacturing Orders which are ready to start production."/>
<filter string="In Production" name="inprogress" domain="[('state','=','in_production')]"
help="Manufacturing Orders which are currently in production."/>
<separator/>
<filter string="Late" domain="['&amp;', ('date_planned','&lt;', current_date), ('state', 'in', ('draft', 'confirmed', 'ready'))]"
help="Production started late"/>
<field name="product_tmpl_id"/>
<field name="product_id"/>
<field name="routing_id" groups="mrp.group_mrp_routings"/>
<group expand="0" string="Group By...">
<filter string="Product" domain="[]" context="{'group_by':'product_id'}"/>
<filter string="Routing" domain="[]" context="{'group_by':'routing_id'}"/>
<filter string="Status" domain="[]" context="{'group_by':'state'}"/>
<filter string="Scheduled Month" domain="[]" context="{'group_by':'date_planned'}" help="Scheduled Date by Month"/>
</group>
</search>
</field>
</record>
<record id="mrp_production_action" model="ir.actions.act_window">
<field name="name">Manufacturing Orders</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.production</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form,calendar,pivot,graph,gantt</field>
<field name="view_id" eval="False"/>
<field name="search_view_id" ref="view_mrp_production_filter"/>
<field name="context">{}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a manufacturing order.
</p><p>
A manufacturing order, based on a bill of materials, will
consume raw materials and produce finished products.
</p><p>
Manufacturing orders are usually proposed automatically based
on customer requirements or automated rules like the minimum
stock rule.
</p>
</field>
</record>
<menuitem action="mrp_production_action"
id="menu_mrp_production_action"
parent="menu_mrp_manufacturing"
sequence="1"/>
<record id="mrp_production_action_planning" model="ir.actions.act_window">
<field name="name">Manufacturing Orders</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.production</field>
<field name="view_type">form</field>
<field name="view_mode">calendar,tree,form,pivot,graph</field>
<field name="domain">[('state','in',('ready','confirmed','in_production'))]</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to start a new manufacturing order.
</p><p>
A manufacturing order, based on a bill of materials, will
consume raw materials and produce finished products.
</p><p>
Manufacturing orders are usually proposed automatically based
on customer requirements or automated rules like the minimum
stock rule.
</p>
</field>
</record>
<menuitem action="mrp.mrp_production_action_planning"
id="menu_mrp_production_order_action" name="Order Planning"
parent="menu_mrp_planning" sequence="1"/>
<!-- Production Order Line -->
<record id="mrp_production_product_form_view" model="ir.ui.view">
<field name="name">mrp.production.product.line.form</field>
<field name="model">mrp.production.product.line</field>
<field name="arch" type="xml">
<form string="Scheduled Products">
<group col="4">
<field name="name"/>
<field name="product_id"/>
<field name="product_qty"/>
<field name="product_uom" options="{'no_open':True,'no_create':True}" groups="product.group_uom"/>
</group>
</form>
</field>
</record>
<record id="mrp_production_product_tree_view" model="ir.ui.view">
<field name="name">mrp.production.product.line.tree</field>
<field name="model">mrp.production.product.line</field>
<field name="arch" type="xml">
<tree string="Scheduled Products">
<field name="product_id"/>
<field name="product_qty"/>
<field name="product_uom" options="{'no_open':True,'no_create':True}" groups="product.group_uom"/>
</tree>
</field>
</record>
<record id="act_product_mrp_production" model="ir.actions.act_window">
<field name="context">{'search_default_product_id': [active_id]}</field>
<field name="name">Manufacturing Orders</field>
<field name="res_model">mrp.production</field>
<field name="view_id" ref="mrp_production_tree_view"/>
</record>
</data>
</openerp>
+114
View File
@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Property Group -->
<record id="mrp_property_group_form_view" model="ir.ui.view">
<field name="name">mrp.property.group.form</field>
<field name="model">mrp.property.group</field>
<field name="arch" type="xml">
<form string="Properties categories">
<group>
<field name="name"/>
</group>
<separator string="Description"/>
<field name="description" nolabel="1"/>
</form>
</field>
</record>
<record id="mrp_property_group_action" model="ir.actions.act_window">
<field name="name">Property Groups</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.property.group</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a group of properties.
</p><p>
Define specific property groups that can be assigned to your
bill of materials and sales orders. Properties allows Odoo
to automatically select the right bill of materials according
to properties selected in the sales order by salesperson.
</p><p>
For instance, in the property group "Warranty", you an have
two properties: 1 year warranty, 3 years warranty. Depending
on the propoerties selected in the sales order, Odoo will
schedule a production using the matching bill of materials.
</p>
</field>
</record>
<menuitem id="menu_mrp_property_group_action"
action="mrp_property_group_action"
parent="menu_mrp_configuration"
groups="base.group_no_one,product.group_mrp_properties"
sequence="35"/>
<!-- Property -->
<record id="mrp_property_tree_view" model="ir.ui.view">
<field name="name">mrp.property.tree</field>
<field name="model">mrp.property</field>
<field name="arch" type="xml">
<tree string="Properties">
<field name="name"/>
<field name="composition"/>
<field name="group_id"/>
</tree>
</field>
</record>
<record id="mrp_property_form_view" model="ir.ui.view">
<field name="name">mrp.property.form</field>
<field name="model">mrp.property</field>
<field name="arch" type="xml">
<form string="Properties">
<group col="4">
<field colspan="4" name="name"/>
<field name="group_id"/>
<field name="composition"/>
</group>
<separator string="Description"/>
<field name="description" nolabel="1"/>
</form>
</field>
</record>
<record id="view_mrp_property_search" model="ir.ui.view">
<field name="name">mrp.property.search</field>
<field name="model">mrp.property</field>
<field name="arch" type="xml">
<search string="Search">
<field name="name" string="Name"/>
<field name="composition"/>
<field name="group_id" />
<group expand="0" string="Group By">
<filter string="Property Group" domain="[]" context="{'group_by':'group_id'}"/>
</group>
</search>
</field>
</record>
<record id="mrp_property_action" model="ir.actions.act_window">
<field name="name">Properties</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.property</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_mrp_property_search"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new property.
</p><p>
The Properties in Odoo are used to select the right bill of
materials for manufacturing a product when you have different
ways of building the same product. You can assign several
properties to each bill of materials. When a salesperson
creates a sales order, they can relate it to several properties
and Odoo will automatically select the BoM to use according
the needs.
</p>
</field>
</record>
<menuitem id="menu_mrp_property_action"
action="mrp_property_action"
groups="product.group_mrp_properties"
parent="menu_mrp_configuration"
sequence="30"/>
</data>
</openerp>
+130
View File
@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Routings Workcenter -->
<record id="mrp_routing_workcenter_tree_view" model="ir.ui.view">
<field name="name">mrp.routing.workcenter.tree</field>
<field name="model">mrp.routing.workcenter</field>
<field name="arch" type="xml">
<tree string="Routing Work Centers">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="workcenter_id"/>
<field name="cycle_nbr"/>
<field name="hour_nbr"/>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
</tree>
</field>
</record>
<record id="mrp_routing_workcenter_form_view" model="ir.ui.view">
<field name="name">mrp.routing.workcenter.form</field>
<field name="model">mrp.routing.workcenter</field>
<field name="arch" type="xml">
<form string="Routing Work Centers">
<sheet>
<group>
<group>
<field name="name"/>
<field name="workcenter_id"/>
<field name="sequence" groups="base.group_no_one"/>
</group><group>
<field name="cycle_nbr"/>
<field name="hour_nbr" widget="float_time"/>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
</group>
</group>
<label for="note"/>
<field name="note"/>
</sheet>
</form>
</field>
</record>
<!-- Routings -->
<record id="mrp_routing_form_view" model="ir.ui.view">
<field name="name">mrp.routing.form</field>
<field name="model">mrp.routing</field>
<field name="arch" type="xml">
<form string="Routing">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
</button>
</div>
<group>
<group>
<field name="name"/>
<field name="code"/>
</group>
<group>
<field name="location_id"/>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
</group>
</group>
<notebook>
<page string="Work Center Operations">
<field name="workcenter_lines" string="Operation"/>
</page>
<page string="Notes">
<field name="note"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="mrp_routing_tree_view" model="ir.ui.view">
<field name="name">mrp.routing.tree</field>
<field name="model">mrp.routing</field>
<field name="arch" type="xml">
<tree string="Routing">
<field name="code"/>
<field name="name"/>
<field name="active" invisible="1"/>
<field name="location_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
</record>
<record id="mrp_routing_search_view" model="ir.ui.view">
<field name="name">mrp.routing.search</field>
<field name="model">mrp.routing</field>
<field name="arch" type="xml">
<search string="Routing">
<field name="name" string="Routing" filter_domain="['|',('name','ilike',self),('code','ilike',self)]"/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<field name="location_id"/>
<field name="company_id" groups="base.group_multi_company"/>
<group expand="0" string="Group By...">
<filter string="Production Location" domain="[]" context="{'group_by':'location_id'}"/>
</group>
</search>
</field>
</record>
<record id="mrp_routing_action" model="ir.actions.act_window">
<field name="name">Routings</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.routing</field>
<field name="view_type">form</field>
<field name="view_id" ref="mrp_routing_tree_view"/>
<field name="search_view_id" ref="mrp_routing_search_view"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new routing.
</p><p>
Routings allow you to create and manage the manufacturing
operations that should be followed within your work centers in
order to produce a product. They are attached to bills of
materials that will define the required raw materials.
</p>
</field>
</record>
<menuitem id="menu_mrp_routing_action"
action="mrp_routing_action"
parent="menu_mrp_configuration"
groups="group_mrp_routings"
sequence="50"/>
</data>
</openerp>
+50
View File
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Top menu item -->
<menuitem id="base.menu_mrp_root"
name="Manufacturing"
groups="group_mrp_user,group_mrp_manager"
icon="fa-wrench"
web_icon="mrp,static/description/icon.png"
sequence="35"/>
<menuitem id="menu_mrp_manufacturing"
name="Manufacturing"
parent="base.menu_mrp_root"
sequence="1"/>
<menuitem id="menu_mrp_planning"
name="Planning"
parent="base.menu_mrp_root"
sequence="2"/>
<menuitem id="menu_mrp_bom"
name="Products"
parent="base.menu_mrp_root"
sequence="5"/>
<menuitem id="menu_mrp_configuration"
name="Configuration"
parent="base.menu_mrp_root"
groups="group_mrp_manager"
sequence="100"/>
<menuitem id="menu_pm_resources_config"
name="Resources"
parent="menu_mrp_configuration"
sequence="100"/>
<menuitem id="menu_view_resource_calendar_search_mrp"
action="resource.action_resource_calendar_form"
parent="menu_pm_resources_config"
sequence="10"
groups="base.group_no_one"/>
<menuitem id="menu_view_resource_calendar_leaves_search_mrp"
action="resource.action_resource_calendar_leave_tree"
parent="menu_pm_resources_config"
sequence="20"
groups="base.group_no_one"/>
</data>
</openerp>
+105
View File
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Work Centers -->
<record id="mrp_workcenter_tree_view" model="ir.ui.view">
<field name="name">mrp.workcenter.tree</field>
<field name="model">mrp.workcenter</field>
<field name="arch" type="xml">
<tree string="Work Center">
<field name="code"/>
<field name="name"/>
<field name="active" invisible="1"/>
<field name="resource_type"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
</record>
<record id="mrp_workcenter_view" model="ir.ui.view">
<field name="name">mrp.workcenter.form</field>
<field name="model">mrp.workcenter</field>
<field name="arch" type="xml">
<form string="Work Center">
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
</button>
</div>
<group col="4">
<group>
<field name="name"/>
<field name="resource_type"/>
<field name="calendar_id" widget="selection"/>
</group>
<group>
<field name="code"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
</group>
</group>
<notebook>
<page string="General Information">
<group>
<group string="Capacity Information">
<field name="time_efficiency"/>
<field name="capacity_per_cycle"/>
<field name="time_cycle" widget="float_time"/>
<field name="time_start" widget="float_time"/>
<field name="time_stop" widget="float_time"/>
</group>
<group string="Costing Information">
<field name="product_id" on_change="on_change_product_cost(product_id)"/>
<field name="costs_hour"/>
<field name="costs_hour_account_id" groups="analytic.group_analytic_accounting"/>
<field name="costs_cycle"/>
<field name="costs_cycle_account_id" groups="analytic.group_analytic_accounting"/>
<field name="costs_general_account_id" attrs="{ 'required':['|',('costs_cycle_account_id', '=', True),('costs_hour_account_id', '=', True)]}" groups="analytic.group_analytic_accounting"/>
</group>
</group>
<separator string="Description"/>
<field name="note" nolabel="1"/>
</page>
</notebook>
</form>
</field>
</record>
<record id="view_mrp_workcenter_search" model="ir.ui.view">
<field name="name">mrp.workcenter.search</field>
<field name="model">mrp.workcenter</field>
<field name="arch" type="xml">
<search string="Search for mrp workcenter">
<field name="name" string="Mrp Workcenter" filter_domain="['|',('name','ilike',self),('code','ilike',self)]"/>
<field name="resource_type"/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<group expand="0" string="Group By...">
<filter string="Type" domain="[]" context="{'group_by':'resource_type'}"/>
<filter string="Company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
</group>
</search>
</field>
</record>
<record id="mrp_workcenter_action" model="ir.actions.act_window">
<field name="name">Work Centers</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.workcenter</field>
<field name="view_type">form</field>
<field name="view_id" ref="mrp_workcenter_tree_view"/>
<field name="search_view_id" ref="view_mrp_workcenter_search"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a work center.
</p><p>
Work Centers allow you to create and manage manufacturing
units. They consist of workers and/or machines, which are
considered as units for task assignation as well as capacity
and planning forecast.
</p>
</field>
</record>
<menuitem id="menu_view_resource_search_mrp"
action="mrp_workcenter_action"
groups="group_mrp_routings"
parent="menu_mrp_configuration"
sequence="40"/>
</data>
</openerp>
+33
View File
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Procurements -->
<record id="view_procurement_rule_form_stock_inherit_mrp" model="ir.ui.view">
<field name="name">procurement.rule.form.stock.inherit.mrp</field>
<field name="model">procurement.rule</field>
<field name="inherit_id" ref="stock.view_procurement_rule_form_stock_inherit"/>
<field name="arch" type="xml">
<field name="location_src_id" position="attributes">
<attribute name="attrs">{'required': [('action', '=', 'move')], 'invisible': [('action', 'not in', ['move', 'manufacture'])]}</attribute>
</field>
</field>
</record>
<record id="view_procurement_form_inherit" model="ir.ui.view">
<field name="name">procurement.order.form.inherit</field>
<field name="model">procurement.order</field>
<field name="inherit_id" ref="procurement.procurement_form_view"/>
<field name="arch" type="xml">
<data>
<field name="origin" position="before">
<field name="bom_id" domain="[('product_id','=',product_id)]"/>
<field name="production_id" attrs="{'invisible': [('production_id','=',False)]}"/>
</field>
<field name="origin" position="after">
<field name="property_ids" widget="many2many_tags" groups="product.group_mrp_properties"/>
</field>
</data>
</field>
</record>
</data>
</odoo>
+79
View File
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Product Template -->
<record id="view_mrp_product_template_form_inherited" model="ir.ui.view">
<field name="name">product.form.mrp.inherited</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="stock.view_template_property_form"/>
<field name="arch" type="xml">
<group name="sale_condition" position="inside">
<label for="produce_delay" attrs="{'invisible':[('type','=','service')]}"/>
<div attrs="{'invisible':[('type','=','service')]}">
<field name="produce_delay" class="oe_inline"/> days
</div>
</group>
</field>
</record>
<record id="product_template_search_view_procurment" model="ir.ui.view">
<field name="name">product.template.search.bom</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_search_view"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='consumable']" position="after">
<separator/>
<filter string="Components" name="components" domain="[('bom_ids','not in',[])]"/>
</xpath>
</field>
</record>
<record id="product_template_action" model="ir.actions.act_window">
<field name="name">Products</field>
<field name="res_model">product.template</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="context">{}</field>
</record>
<menuitem id="menu_mrp_product_form"
name="Products"
action="product_template_action"
parent="menu_mrp_bom"/>
<record id="product_template_form_view_bom_button" model="ir.ui.view">
<field name="name">product.template.procurement</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_only_form_view"/>
<field name="groups_id" eval="[(4, ref('mrp.group_mrp_user'))]"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button class="oe_stat_button" name="%(template_open_bom)d" type="action"
attrs="{'invisible':[('type', 'not in', ['product', 'consu'])]}" icon="fa-flask">
<field string="Bill of Materials" name="bom_count" widget="statinfo" />
</button>
<button class="oe_stat_button" name="action_view_mos" type="object"
attrs="{'invisible':[('type', 'not in', ['product', 'consu'])]}" icon="fa-list-alt">
<field string="Manufacturing" name="mo_count" widget="statinfo" />
</button>
</div>
</field>
</record>
<record id="product_product_form_view_bom_button" model="ir.ui.view">
<field name="name">product.product.procurement</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="groups_id" eval="[(4, ref('mrp.group_mrp_user'))]"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button class="oe_stat_button" name="action_view_bom" type="object"
attrs="{'invisible':[('type', 'not in', ['product', 'consu'])]}" icon="fa-flask">
<field string="Bill of Materials" name="bom_count" widget="statinfo" />
</button>
<button class="oe_stat_button" name="%(act_product_mrp_production)d" type="action"
attrs="{'invisible':[('type', 'not in', ['product', 'consu'])]}" icon="fa-list-alt">
<field string="Manufacturing" name="mo_count" widget="statinfo" />
</button>
</div>
</field>
</record>
</data>
</odoo>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Warehouse -->
<record id="view_warehouse_inherited" model="ir.ui.view">
<field name="name">Stock Warehouse Inherited</field>
<field name="model">stock.warehouse</field>
<field name="inherit_id" ref="stock.view_warehouse"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='default_resupply_wh_id']" position="before">
<field name="manufacture_to_resupply" />
</xpath>
</field>
</record>
</odoo>
+1 -1
View File
@@ -3,4 +3,4 @@
import mrp_product_produce
import change_production_qty
import stock_move
import stock_move_consume