diff --git a/addons/account/product_view.xml b/addons/account/product_view.xml index 73b31bbb02a..9c1b0ee39b7 100644 --- a/addons/account/product_view.xml +++ b/addons/account/product_view.xml @@ -1,57 +1,52 @@ - - product.normal.form.inherit - product.product + + product.template.form.inherit + product.template 5 - + - + + + + attrs="{'readonly': [('is_product_variant', '=', True)]}"/> + attrs="{'readonly':[ '|', ('sale_ok','=',0), ('is_product_variant', '=', True)]}"/> + attrs="{'readonly': [('is_product_variant', '=', True)]}"/> + attrs="{'readonly': [('is_product_variant', '=', True)]}"/> - + - - product.template.product.form.inherit + + product.template.search product.template - + primary + - - - - - - - - - - - - - - - + + + + + + + - - product.category.property.form.inherit product.category diff --git a/addons/account_anglo_saxon/product_view.xml b/addons/account_anglo_saxon/product_view.xml index c06decf2c92..5a589131218 100644 --- a/addons/account_anglo_saxon/product_view.xml +++ b/addons/account_anglo_saxon/product_view.xml @@ -1,29 +1,18 @@ - - product.normal.form.inherit.stock - product.product - - - - - - - product.template.product.form.inherit + product.normal.form.inherit.stock product.template + diff --git a/addons/event_sale/event_sale_view.xml b/addons/event_sale/event_sale_view.xml index 8987354acdd..a049565074a 100644 --- a/addons/event_sale/event_sale_view.xml +++ b/addons/event_sale/event_sale_view.xml @@ -1,18 +1,19 @@ - - product.product - + + + product.template + -
- -
diff --git a/addons/sale/sale.py b/addons/sale/sale.py index e3b59c00015..70dece22651 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -880,7 +880,7 @@ class sale_order_line(osv.osv): 'order_id': fields.many2one('sale.order', 'Order Reference', required=True, ondelete='cascade', select=True, readonly=True, states={'draft':[('readonly',False)]}), 'name': fields.text('Description', required=True, readonly=True, states={'draft': [('readonly', False)]}), 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of sales order lines."), - 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True, readonly=True, states={'draft': [('readonly', False)]}), + 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True, readonly=True, states={'draft': [('readonly', False)]}, ondelete='restrict'), 'invoice_lines': fields.many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True), 'invoiced': fields.function(_fnct_line_invoiced, string='Invoiced', type='boolean', store={ diff --git a/addons/sale_mrp/test/cancellation_propagated.yml b/addons/sale_mrp/test/cancellation_propagated.yml index 0005e15db53..6a5c557a958 100644 --- a/addons/sale_mrp/test/cancellation_propagated.yml +++ b/addons/sale_mrp/test/cancellation_propagated.yml @@ -16,18 +16,19 @@ uom_id: product.product_uom_unit uom_po_id: product.product_uom_unit - - I create a bom for this product + I create a bom for this product - !record {model: mrp.bom, id: mrp_bom_test1}: company_id: base.main_company name: My BOM + product_tmpl_id: product_manu_product_template product_id: product_manu + product_uom: product.product_uom_unit product_qty: 1.0 type: normal - bom_lines: - - company_id: base.main_company - name: GrapWorks Software - product_id: product.product_product_44 + bom_line_ids: + - product_id: product.product_product_44 + product_uom: product.product_uom_unit product_qty: 1.0 - And set routes on product to be MTO and manufacture diff --git a/addons/sale_mrp/test/sale_mrp.yml b/addons/sale_mrp/test/sale_mrp.yml index 85e25b502f3..11b0af8be6f 100644 --- a/addons/sale_mrp/test/sale_mrp.yml +++ b/addons/sale_mrp/test/sale_mrp.yml @@ -54,10 +54,10 @@ company_id: base.main_company name: Slider Mobile product_efficiency: 1.0 + product_tmpl_id: product_product_slidermobile0_product_template product_id: product_product_slidermobile0 product_qty: 1.0 product_uom: product.product_uom_unit - product_uos_qty: 0.0 sequence: 0.0 type: normal - diff --git a/addons/sale_mrp/tests/test_move_explode.py b/addons/sale_mrp/tests/test_move_explode.py index 5bb8e8550cc..88dab7135b1 100644 --- a/addons/sale_mrp/tests/test_move_explode.py +++ b/addons/sale_mrp/tests/test_move_explode.py @@ -35,9 +35,9 @@ class TestMoveExplode(common.TransactionCase): self.mrp_bom = self.registry('mrp.bom') #product that has a phantom bom - self.product_bom_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_4')[1] + self.product_bom_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_3')[1] #bom with that product - self.bom_id = self.ir_model_data.get_object_reference(cr, uid, 'mrp', 'mrp_bom_24')[1] + self.bom_id = self.ir_model_data.get_object_reference(cr, uid, 'mrp', 'mrp_bom_9')[1] #partner agrolait self.partner_id = self.ir_model_data.get_object_reference(cr, uid, 'base', 'res_partner_1')[1] @@ -55,5 +55,5 @@ class TestMoveExplode(common.TransactionCase): move_ids = [x.id for x in browse_move_ids] #we should have same amount of move as the component in the phatom bom bom = self.mrp_bom.browse(cr, uid, self.bom_id, context=context) - bom_component_length = self.mrp_bom._bom_explode(cr, uid, bom, 1, []) + bom_component_length = self.mrp_bom._bom_explode(cr, uid, bom, self.product_bom_id, 1, []) self.assertEqual(len(move_ids), len(bom_component_length[0])) diff --git a/addons/stock/product.py b/addons/stock/product.py index 54353cd010c..732ab25cd80 100644 --- a/addons/stock/product.py +++ b/addons/stock/product.py @@ -222,14 +222,9 @@ class product_product(osv.osv): "any of its children.\n" "Otherwise, this includes goods leaving any Stock " "Location with 'internal' type."), - 'track_incoming': fields.boolean('Track Incoming Lots', help="Forces to specify a Serial Number for all moves containing this product and coming from a Supplier Location"), - 'track_outgoing': fields.boolean('Track Outgoing Lots', help="Forces to specify a Serial Number for all moves containing this product and going to a Customer Location"), - 'track_all': fields.boolean('Full Lots Traceability', help="Forces to specify a Serial Number on each and every operation related to this product"), 'location_id': fields.dummy(string='Location', relation='stock.location', type='many2one'), 'warehouse_id': fields.dummy(string='Warehouse', relation='stock.warehouse', type='many2one'), 'orderpoint_ids': fields.one2many('stock.warehouse.orderpoint', 'product_id', 'Minimum Stock Rules'), - 'route_ids': fields.many2many('stock.location.route', 'stock_route_product', 'product_id', 'route_id', 'Routes', domain="[('product_selectable', '=', True)]", - help="Depending on the modules installed, this will allow you to define the route of the product: whether it will be bought, manufactured, MTO/MTS,..."), } def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): @@ -275,25 +270,52 @@ class product_product(osv.osv): res['fields']['qty_available']['string'] = _('Produced Qty') return res - def action_view_routes(self, cr, uid, ids, context=None): - route_obj = self.pool.get("stock.location.route") - act_obj = self.pool.get('ir.actions.act_window') - mod_obj = self.pool.get('ir.model.data') - product_route_ids = set() - for product in self.browse(cr, uid, ids, context=context): - product_route_ids |= set([r.id for r in product.route_ids]) - product_route_ids |= set([r.id for r in product.categ_id.total_route_ids]) - route_ids = route_obj.search(cr, uid, ['|', ('id', 'in', list(product_route_ids)), ('warehouse_selectable', '=', True)], context=context) - result = mod_obj.get_object_reference(cr, uid, 'stock', 'action_routes_form') - id = result and result[1] or False - result = act_obj.read(cr, uid, [id], context=context)[0] - result['domain'] = "[('id','in',[" + ','.join(map(str, route_ids)) + "])]" - return result - class product_template(osv.osv): _name = 'product.template' _inherit = 'product.template' + + def _product_available(self, cr, uid, ids, name, arg, context=None): + res = dict.fromkeys(ids, 0) + for product in self.browse(cr, uid, ids, context=context): + res[product.id] = { + # "reception_count": sum([p.reception_count for p in product.product_variant_ids]), + # "delivery_count": sum([p.delivery_count for p in product.product_variant_ids]), + "qty_available": sum([p.qty_available for p in product.product_variant_ids]), + "virtual_available": sum([p.virtual_available for p in product.product_variant_ids]), + "incoming_qty": sum([p.incoming_qty for p in product.product_variant_ids]), + "outgoing_qty": sum([p.outgoing_qty for p in product.product_variant_ids]), + } + return res + + def _search_product_quantity(self, cr, uid, obj, name, domain, context): + prod = self.pool.get("product.product") + res = [] + for field, operator, value in domain: + #to prevent sql injections + assert field in ('qty_available', 'virtual_available', 'incoming_qty', 'outgoing_qty'), 'Invalid domain left operand' + assert operator in ('<', '>', '=', '<=', '>='), 'Invalid domain operator' + assert isinstance(value, (float, int)), 'Invalid domain right operand' + + if operator == '=': + operator = '==' + + product_ids = prod.search(cr, uid, [], context=context) + ids = [] + if product_ids: + #TODO: use a query instead of this browse record which is probably making the too much requests, but don't forget + #the context that can be set with a location, an owner... + for element in prod.browse(cr, uid, product_ids, context=context): + if eval(str(element[field]) + operator + str(value)): + ids.append(element.id) + res.append(('product_variant_ids', 'in', ids)) + return res + _columns = { + 'valuation':fields.selection([('manual_periodic', 'Periodical (manual)'), + ('real_time','Real Time (automated)'),], 'Inventory Valuation', + help="If real-time valuation is enabled for a product, the system will automatically write journal entries corresponding to stock moves." \ + "The inventory variation account set on the product category will represent the current inventory value, and the stock input and stock output account will hold the counterpart moves for incoming and outgoing products." + , required=True), 'type': fields.selection([('product', 'Stockable Product'), ('consu', 'Consumable'), ('service', 'Service')], 'Product Type', required=True, help="Consumable: Will not imply stock management for this product. \nStockable product: Will imply stock management for this product."), 'property_stock_procurement': fields.property( type='many2one', @@ -317,12 +339,47 @@ class product_template(osv.osv): 'loc_rack': fields.char('Rack', size=16), 'loc_row': fields.char('Row', size=16), 'loc_case': fields.char('Case', size=16), + 'track_incoming': fields.boolean('Track Incoming Lots', help="Forces to specify a Serial Number for all moves containing this product and coming from a Supplier Location"), + 'track_outgoing': fields.boolean('Track Outgoing Lots', help="Forces to specify a Serial Number for all moves containing this product and going to a Customer Location"), + 'track_all': fields.boolean('Full Lots Traceability', help="Forces to specify a Serial Number on each and every operation related to this product"), + + # sum of product variant qty + # 'reception_count': fields.function(_product_available, multi='qty_available', + # fnct_search=_search_product_quantity, type='float', string='Quantity On Hand'), + # 'delivery_count': fields.function(_product_available, multi='qty_available', + # fnct_search=_search_product_quantity, type='float', string='Quantity On Hand'), + 'qty_available': fields.function(_product_available, multi='qty_available', + fnct_search=_search_product_quantity, type='float', string='Quantity On Hand'), + 'virtual_available': fields.function(_product_available, multi='qty_available', + fnct_search=_search_product_quantity, type='float', string='Quantity Available'), + 'incoming_qty': fields.function(_product_available, multi='qty_available', + fnct_search=_search_product_quantity, type='float', string='Incoming'), + 'outgoing_qty': fields.function(_product_available, multi='qty_available', + fnct_search=_search_product_quantity, type='float', string='Outgoing'), + + 'route_ids': fields.many2many('stock.location.route', 'stock_route_product', 'product_id', 'route_id', 'Routes', domain="[('product_selectable', '=', True)]", + help="Depending on the modules installed, this will allow you to define the route of the product: whether it will be bought, manufactured, MTO/MTS,..."), } _defaults = { 'sale_delay': 7, + 'valuation': 'manual_periodic', } + def action_view_routes(self, cr, uid, ids, context=None): + route_obj = self.pool.get("stock.location.route") + act_obj = self.pool.get('ir.actions.act_window') + mod_obj = self.pool.get('ir.model.data') + product_route_ids = set() + for product in self.browse(cr, uid, ids, context=context): + product_route_ids |= set([r.id for r in product.route_ids]) + product_route_ids |= set([r.id for r in product.categ_id.total_route_ids]) + route_ids = route_obj.search(cr, uid, ['|', ('id', 'in', list(product_route_ids)), ('warehouse_selectable', '=', True)], context=context) + result = mod_obj.get_object_reference(cr, uid, 'stock', 'action_routes_form') + id = result and result[1] or False + result = act_obj.read(cr, uid, [id], context=context)[0] + result['domain'] = "[('id','in',[" + ','.join(map(str, route_ids)) + "])]" + return result class product_removal_strategy(osv.osv): _name = 'product.removal' diff --git a/addons/stock/product_view.xml b/addons/stock/product_view.xml index aaecf4e48e2..103133d0ae0 100644 --- a/addons/stock/product_view.xml +++ b/addons/stock/product_view.xml @@ -2,70 +2,17 @@ - - product.search.stock.form - product.product - - - - - - - - - - - - - - - - - product.stock.tree.inherit product.product - - - - - {'red':virtual_available<0, 'blue':virtual_available>=0 and state in ('draft', 'end', 'obsolete'), 'black':virtual_available>=0 and state not in ('draft', 'end', 'obsolete')} - - - - - product.template.stock.property.form.inherit - product.template - - - - - - - - - - - - - - - - - - - - {'default_groups_ref': ['base.group_user', 'base.group_sale_manager', 'stock.group_stock_manager']} - + + + @@ -109,12 +56,117 @@
+ + + + product.template.search.stock.form + product.template + + + + + + + + + + + + + + + + + product.template.stock.property.form.inherit + product.template + + + + + + + + + + + + + + + + + + + + + + + + {'default_groups_ref': ['base.group_user', 'base.group_sale_manager', 'stock.group_stock_manager']} + + +