From f1afeb680bc65d8e7acdf8f0e34701b6e896cc7e Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 19 Jun 2014 16:58:52 +0200 Subject: [PATCH 001/270] [NOIMP] Create move before other move when routing location is different from production source location --- addons/mrp/mrp.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index f5b6cf466a5..fdca13cd5fd 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -1064,6 +1064,26 @@ class mrp_production(osv.osv): return "make_to_order" return "make_to_stock" + def _create_previous_move(self, cr, uid, production, product, uom_id, qty, uos_id, uos_qty, source_location_id, dest_location_id, context=None): + stock_move = self.pool.get('stock.move') + move_id = stock_move.create(cr, uid, { + 'name': production.name, + 'date': production.date_planned, + 'product_id': product.id, + 'product_uom_qty': qty, + 'product_uom': uom_id, + 'product_uos_qty': uos_id and uos_qty or False, + 'product_uos': uos_id or False, + 'location_id': source_location_id, + 'location_dest_id': dest_location_id, + 'company_id': production.company_id.id, + 'procure_method': self._get_raw_material_procure_method(cr, uid, product, context=context), + #this saves us a browse in create() + 'price_unit': product.standard_price, + 'origin': production.name, + }) + return move_id + def _make_consume_line_from_data(self, cr, uid, production, product, uom_id, qty, uos_id, uos_qty, context=None): stock_move = self.pool.get('stock.move') # Internal shipment is created for Stockable and Consumer Products @@ -1071,9 +1091,11 @@ class mrp_production(osv.osv): return False # Take routing location as a Source Location. source_location_id = production.location_src_id.id - if production.bom_id.routing_id and production.bom_id.routing_id.location_id: + prev_move= False + if production.bom_id.routing_id and production.bom_id.routing_id.location_id.id != source_location_id: + source_location2_id = source_location_id source_location_id = production.bom_id.routing_id.location_id.id - + prev_move = self._create_previous_move(cr, uid, production, product, uom_id, qty, uos_id, uos_qty, source_location2_id, source_location_id, context=context) destination_location_id = production.product_id.property_stock_production.id if not source_location_id: source_location_id = production.location_src_id.id @@ -1088,12 +1110,14 @@ class mrp_production(osv.osv): 'location_id': source_location_id, 'location_dest_id': destination_location_id, 'company_id': production.company_id.id, - 'procure_method': self._get_raw_material_procure_method(cr, uid, product, context=context), + 'procure_method': prev_move and 'make_to_stock' or self._get_raw_material_procure_method(cr, uid, product, context=context), #Make_to_stock avoids creating procurement 'raw_material_production_id': production.id, #this saves us a browse in create() 'price_unit': product.standard_price, 'origin': production.name, }) + if prev_move: + stock_move.write(cr, uid, [prev_move], {'move_dest_id': move_id}, context=context) return move_id def _make_production_consume_line(self, cr, uid, line, context=None): From 12211437f1f7a11edde36b76ea809f69fa7fc207 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 25 Jun 2014 15:03:11 +0200 Subject: [PATCH 002/270] [IMP] Corrections in views + calendar fields isinstance + inventory theoretical qty --- addons/calendar/calendar.py | 2 ++ .../stock/wizard/stock_change_product_qty.py | 2 +- addons/stock_account/product_view.xml | 18 +----------------- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index b6473a7f1a7..dde63479ba1 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -745,6 +745,8 @@ class calendar_event(osv.Model): def _compute(self, cr, uid, ids, fields, arg, context=None): res = {} + if not isinstance(fields, list): + fields = [fields] for meeting_id in ids: res[meeting_id] = {} attendee = self._find_my_attendee(cr, uid, [meeting_id], context) diff --git a/addons/stock/wizard/stock_change_product_qty.py b/addons/stock/wizard/stock_change_product_qty.py index 646a047d63c..326316131a2 100644 --- a/addons/stock/wizard/stock_change_product_qty.py +++ b/addons/stock/wizard/stock_change_product_qty.py @@ -99,7 +99,7 @@ class stock_change_product_qty(osv.osv_memory): 'location_id': data.location_id.id, 'product_id': rec_id, 'product_uom_id': res_original.uom_id.id, - 'th_qty': th_qty, + 'theoretical_qty': th_qty, 'prod_lot_id': data.lot_id.id } inventory_line_obj.create(cr , uid, line_data, context=context) diff --git a/addons/stock_account/product_view.xml b/addons/stock_account/product_view.xml index 960280154fd..bc921d5c307 100644 --- a/addons/stock_account/product_view.xml +++ b/addons/stock_account/product_view.xml @@ -31,14 +31,6 @@ - - - - - product.product.standard.price.form.inherit - product.product - - - - - - - product.normal.stock.acc.property.form.inherit - product.product - - 26 - @@ -73,6 +56,7 @@ + From 89041fafced11ac8e5b3a03682e0807129eb877c Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 25 Jun 2014 18:17:32 +0200 Subject: [PATCH 003/270] [FIX] Take into account that location_id of routing can be empty --- addons/mrp/mrp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index fdca13cd5fd..e13d8bb2c89 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -1092,7 +1092,7 @@ class mrp_production(osv.osv): # Take routing location as a Source Location. source_location_id = production.location_src_id.id prev_move= False - if production.bom_id.routing_id and production.bom_id.routing_id.location_id.id != source_location_id: + if production.bom_id.routing_id and production.bom_id.routing_id.location_id and production.bom_id.routing_id.location_id.id != source_location_id: source_location2_id = source_location_id source_location_id = production.bom_id.routing_id.location_id.id prev_move = self._create_previous_move(cr, uid, production, product, uom_id, qty, uos_id, uos_qty, source_location2_id, source_location_id, context=context) From bdda11ab4054fb224fc2787d6f97f4bf0ac3a6d1 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 26 Jun 2014 17:52:37 +0200 Subject: [PATCH 004/270] [IMP] Update views and production_delay and track_production should go to product_template --- addons/mrp/mrp_view.xml | 6 +++--- addons/mrp/product.py | 16 ++++++++-------- addons/purchase/purchase.py | 4 ---- addons/stock/product_view.xml | 14 +++++++------- addons/stock/stock_view.xml | 27 --------------------------- 5 files changed, 18 insertions(+), 49 deletions(-) diff --git a/addons/mrp/mrp_view.xml b/addons/mrp/mrp_view.xml index 52e0f539b80..fe20552d0fa 100644 --- a/addons/mrp/mrp_view.xml +++ b/addons/mrp/mrp_view.xml @@ -561,10 +561,10 @@ - + product.form.mrp.inherited - product.product - + product.template + diff --git a/addons/mrp/product.py b/addons/mrp/product.py index 97ef9cf0494..6d0d93bd5f5 100644 --- a/addons/mrp/product.py +++ b/addons/mrp/product.py @@ -41,10 +41,17 @@ class product_template(osv.osv): return res _columns = { - "bom_ids": fields.one2many('mrp.bom', 'product_tmpl_id','Bill of Materials'), + 'produce_delay': fields.float('Manufacturing Lead Time', help="Average delay in days to produce this product. In the case of multi-level BOM, the manufacturing lead times of the components will be added."), + 'track_production': fields.boolean('Track Manufacturing Lots', help="Forces to specify a Serial Number for all moves containing this product and generated by a Manufacturing Order"), + 'bom_ids': fields.one2many('mrp.bom', 'product_tmpl_id','Bill of Materials'), 'bom_count': fields.function(_bom_orders_count, string='# Bill of Material', type='integer', multi="_bom_order_count"), 'mo_count': fields.function(_bom_orders_count_mo, string='# Manufacturing Orders', type='integer'), } + + _defaults = { + "produce_delay": 1, + } + def copy(self, cr, uid, id, default=None, context=None): if not default: default = {} @@ -63,15 +70,8 @@ class product_product(osv.osv): return res _columns = { - "produce_delay": fields.float('Manufacturing Lead Time', help="Average delay in days to produce this product. In the case of multi-level BOM, the manufacturing lead times of the components will be added."), - 'track_production': fields.boolean('Track Manufacturing Lots', help="Forces to specify a Serial Number for all moves containing this product and generated by a Manufacturing Order"), 'mo_count': fields.function(_bom_orders_count, string='# Manufacturing Orders', type='integer'), } - - _defaults = { - "produce_delay": 1, - } - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index ca618f76948..db1bce052d3 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -1465,9 +1465,5 @@ class account_invoice_line(osv.Model): readonly=True), } -class product_template(osv.osv): - _inherit = "product.template" - - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/stock/product_view.xml b/addons/stock/product_view.xml index c3941eb1887..3b9ac4053fa 100644 --- a/addons/stock/product_view.xml +++ b/addons/stock/product_view.xml @@ -111,6 +111,13 @@ + + + + + + + @@ -192,13 +199,6 @@ product.product - - - - - - - - - + + +
+ +
+ + + + + + +
+
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 97000987048..34a8c4df50d 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -1390,15 +1390,23 @@ class product_template(osv.Model): for template in self.browse(cr, uid, ids, context=context): res[template.id] = sum([p.purchase_count for p in template.product_variant_ids]) return res + _columns = { 'purchase_ok': fields.boolean('Can be Purchased', help="Specify if the product can be selected in a purchase order line."), 'purchase_count': fields.function(_purchase_count, string='# Purchases', type='integer'), } + _defaults = { 'purchase_ok': 1, 'route_ids': _get_buy_route, } + def action_view_purchases(self, cr, uid, ids, context=None): + products = self._get_products(cr, uid, ids, context=context) + result = self._get_act_window_dict(cr, uid, 'purchase','action_purchase_line_product_tree', context=context) + result['domain'] = "[('product_id','in',[" + ','.join(map(str, products)) + "])]" + return result + class product_product(osv.Model): _name = 'product.product' _inherit = 'product.product' diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml index a1c42123611..445afa256b0 100644 --- a/addons/purchase/purchase_view.xml +++ b/addons/purchase/purchase_view.xml @@ -620,12 +620,6 @@ product.template - - - @@ -634,5 +628,33 @@
+ + + product.template.purchase.button.inherit + product.template + + + + + + + + + + product.product.purchase.button.inherit + product.product + + + + + + + diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 8881e917269..6cd6e815974 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -1278,10 +1278,37 @@ class product_product(osv.Model): product_id: SaleOrderLine.search_count(cr,uid, [('product_id', '=', product_id)], context=context) for product_id in ids } + _columns = { 'sales_count': fields.function(_sales_count, string='# Sales', type='integer'), } +class product_template(osv.Model): + _inherit = 'product.template' + + def _sales_count(self, cr, uid, ids, field_name, arg, context=None): + res = dict.fromkeys(ids, 0) + for template in self.browse(cr, uid, ids, context=context): + res[template.id] = sum([p.sales_count for p in template.product_variant_ids]) + return res + + def action_view_sales(self, cr, uid, ids, context=None): + act_obj = self.pool.get('ir.actions.act_window') + mod_obj = self.pool.get('ir.model.data') + product_ids = [] + for template in self.browse(cr, uid, ids, context=context): + product_ids += [x.id for x in template.product_variant_ids] + result = mod_obj.get_object_reference(cr, uid, 'sale', 'action_order_line_product_tree') + id = result and result[1] or False + result = act_obj.read(cr, uid, [id], context=context)[0] + result['domain'] = "[('product_id','in',[" + ','.join(map(str, product_ids)) + "])]" + return result + + + _columns = { + 'sales_count': fields.function(_sales_count, string='# Sales', type='integer'), + + } # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index 13e5d952006..922ab8bfbc0 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -508,6 +508,21 @@ + + product.template.sale.order.button + product.template + + + + + + + + + res.company.form.inherit diff --git a/addons/stock/product.py b/addons/stock/product.py index 20470e45e9a..832f147028b 100644 --- a/addons/stock/product.py +++ b/addons/stock/product.py @@ -323,8 +323,18 @@ class product_template(osv.osv): res.append(('product_variant_ids', 'in', ids)) return res + + def _product_available_text(self, cr, uid, ids, field_names=None, arg=False, context=None): + res = {} + for product in self.browse(cr, uid, ids, context=context): + res[product.id] = str(product.qty_available) + _(" On Hand") + return res + + + _columns = { '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."), + 'qty_available_text': fields.function(_product_available_text, type='char'), 'property_stock_procurement': fields.property( type='many2one', relation='stock.location', @@ -388,6 +398,42 @@ class product_template(osv.osv): result['domain'] = "[('id','in',[" + ','.join(map(str, route_ids)) + "])]" return result + + def _get_products(self, cr, uid, ids, context=None): + products = [] + for prodtmpl in self.browse(cr, uid, ids, context=None): + products += [x.id for x in prodtmpl.product_variant_ids] + return products + + def _get_act_window_dict(self, cr, uid, module, name, context=None): + mod_obj = self.pool.get('ir.model.data') + act_obj = self.pool.get('ir.actions.act_window') + result = mod_obj.get_object_reference(cr, uid, module, name) + id = result and result[1] or False + result = act_obj.read(cr, uid, [id], context=context)[0] + return result + + def action_open_quants(self, cr, uid, ids, context=None): + products = self._get_products(cr, uid, ids, context=context) + result = self._get_act_window_dict(cr, uid, 'stock','product_open_quants', context=context) + result['domain'] = "[('product_id','in',[" + ','.join(map(str, products)) + "])]" + print "Result open quants:", result + return result + + def action_view_orderpoints(self, cr, uid, ids, context=None): + products = self._get_products(cr, uid, ids, context=context) + result = self._get_act_window_dict(cr, uid, 'stock','product_open_orderpoint', context=context) + result['domain'] = "[('product_id','in',[" + ','.join(map(str, products)) + "])]" + return result + + + def action_view_stock_moves(self, cr, uid, ids, context=None): + products = self._get_products(cr, uid, ids, context=context) + result = self._get_act_window_dict(cr, uid, 'stock','act_product_stock_move_open', context=context) + result['domain'] = "[('product_id','in',[" + ','.join(map(str, products)) + "])]" + return result + + class product_removal_strategy(osv.osv): _name = 'product.removal' _description = 'Removal Strategy' diff --git a/addons/stock/product_view.xml b/addons/stock/product_view.xml index c22429017e7..100e66ca2aa 100644 --- a/addons/stock/product_view.xml +++ b/addons/stock/product_view.xml @@ -127,7 +127,7 @@ - + @@ -143,9 +143,6 @@ {'default_groups_ref': ['base.group_user', 'base.group_sale_manager', 'stock.group_stock_manager']} - - + + + + + + + + product.product.mrp.button + product.product + + +
+ + + + Date: Mon, 14 Jul 2014 10:45:34 +0200 Subject: [PATCH 014/270] [REM] cache setting in _prefetch_field --- openerp/models.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openerp/models.py b/openerp/models.py index f48aa5bc14a..7d19cc74e08 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -3123,12 +3123,11 @@ class BaseModel(object): # fetch records with read() assert self in records and field.name in fnames + result = [] try: result = records.read(list(fnames), load='_classic_write') - except AccessError as e: - # update cache with the exception - records._cache[field] = FailedValue(e) - result = [] + except AccessError: + pass # check the cache, and update it if necessary if field not in self._cache: From 8083afe7c365554e375fb16bd200ec21aa2481e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Param=C3=A9s?= Date: Mon, 14 Jul 2014 12:41:07 +0100 Subject: [PATCH 015/270] [FIX] setup Environment for the report thread --- openerp/service/report.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/openerp/service/report.py b/openerp/service/report.py index 0c2f720f4cb..f7437751e1e 100644 --- a/openerp/service/report.py +++ b/openerp/service/report.py @@ -87,25 +87,26 @@ def exp_report(db, uid, object, ids, datas=None, context=None): self_reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None} def go(id, uid, ids, datas, context): - cr = openerp.registry(db).cursor() - try: - result, format = openerp.report.render_report(cr, uid, ids, object, datas, context) - if not result: - tb = sys.exc_info() - self_reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb) - self_reports[id]['result'] = result - self_reports[id]['format'] = format - self_reports[id]['state'] = True - except Exception, exception: - _logger.exception('Exception: %s\n', exception) - if hasattr(exception, 'name') and hasattr(exception, 'value'): - self_reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value)) - else: - tb = sys.exc_info() - self_reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb) - self_reports[id]['state'] = True - cr.commit() - cr.close() + with openerp.api.Environment.manage(): + cr = openerp.registry(db).cursor() + try: + result, format = openerp.report.render_report(cr, uid, ids, object, datas, context) + if not result: + tb = sys.exc_info() + self_reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb) + self_reports[id]['result'] = result + self_reports[id]['format'] = format + self_reports[id]['state'] = True + except Exception, exception: + _logger.exception('Exception: %s\n', exception) + if hasattr(exception, 'name') and hasattr(exception, 'value'): + self_reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value)) + else: + tb = sys.exc_info() + self_reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb) + self_reports[id]['state'] = True + cr.commit() + cr.close() return True threading.Thread(target=go, args=(id, uid, ids, datas, context)).start() From a1de79b875ca4283eea88c9a7f557124423548ed Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 14 Jul 2014 15:41:40 +0200 Subject: [PATCH 016/270] [FIX] calendar: invalidate cache to avoid (pre)fetching invalid virtual IDs Because the new API basically browses everything, the virtual IDs used by calendar.event are added to the cache and prefetching tries to get them from the database resulting in conversion errors. These ids have to be forcibly evicted to avoid the error. --- addons/calendar/calendar.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index a77eb5ea6f6..920a05c3ebf 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -1567,6 +1567,12 @@ class calendar_event(osv.Model): select = [ids] else: select = ids + + # FIXME: find a better way to not push virtual ids in the cache + # (leading to their prefetching and ultimately a type error when + # postgres tries to convert '14-3489274297' to an integer) + self.invalidate_cache(cr, uid, context=context) + select = map(lambda x: (x, calendar_id2real_id(x)), select) result = [] real_data = super(calendar_event, self).read(cr, uid, [real_id for calendar_id, real_id in select], fields=fields2, context=context, load=load) From b2ddda47e56ee878d38b650eaa4a2e16c416895b Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 15 Jul 2014 08:56:58 +0200 Subject: [PATCH 017/270] [FIX] missing part of initialization in ir.model and ir.model.fields --- openerp/addons/base/ir/ir_model.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index edbd96c6b66..cbbfabe9cb5 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -187,13 +187,16 @@ class ir_model(osv.osv): res = super(ir_model,self).create(cr, user, vals, context) if vals.get('state','base')=='manual': self.instanciate(cr, user, vals['model'], context) + model = self.pool[vals['model']] + model._prepare_setup_fields(cr, SUPERUSER_ID) + model._setup_fields(cr, SUPERUSER_ID) ctx = dict(context, field_name=vals['name'], field_state='manual', select=vals.get('select_level', '0'), update_custom_fields=True) - self.pool[vals['model']]._auto_init(cr, ctx) - self.pool[vals['model']]._auto_end(cr, ctx) # actually create FKs! + model._auto_init(cr, ctx) + model._auto_end(cr, ctx) # actually create FKs! openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname) return res @@ -356,17 +359,21 @@ class ir_model_fields(osv.osv): raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation']) if vals['model'] in self.pool: + model = self.pool[vals['model']] if vals['model'].startswith('x_') and vals['name'] == 'x_name': - self.pool[vals['model']]._rec_name = 'x_name' - self.pool[vals['model']].__init__(self.pool, cr) + model._rec_name = 'x_name' + model.__init__(self.pool, cr) + model._prepare_setup_fields(cr, SUPERUSER_ID) + model._setup_fields(cr, SUPERUSER_ID) + #Added context to _auto_init for special treatment to custom field for select_level ctx = dict(context, field_name=vals['name'], field_state='manual', select=vals.get('select_level', '0'), update_custom_fields=True) - self.pool[vals['model']]._auto_init(cr, ctx) - self.pool[vals['model']]._auto_end(cr, ctx) # actually create FKs! + model._auto_init(cr, ctx) + model._auto_end(cr, ctx) # actually create FKs! openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname) return res From 03977a29e681b4c841cc1e5c65cd7ed478d672dc Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 15 Jul 2014 10:50:49 +0200 Subject: [PATCH 018/270] [FIX] expected browse_record can be an integer If no filter is specified, the geoip library is installed and the user's address can be geolocated, ``country`` is assigned an integer (the id of the matching country) whereas the filtering code expects a browse. fixes #1148 --- addons/website_hr_recruitment/controllers/main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/website_hr_recruitment/controllers/main.py b/addons/website_hr_recruitment/controllers/main.py index c99c6644f9c..a0c17735b2e 100644 --- a/addons/website_hr_recruitment/controllers/main.py +++ b/addons/website_hr_recruitment/controllers/main.py @@ -38,9 +38,10 @@ class website_hr_recruitment(http.Controller): if not country and not department and not office_id: country_code = request.session['geoip'].get('country_code') if country_code: - country_ids = request.registry.get('res.country').search(cr, uid, [('code', '=', country_code)], context=context) + Country = request.registry['res.country'] + country_ids = Country.search(cr, uid, [('code', '=', country_code)], context=context) if country_ids: - country = country_ids[0] + country = Country.browse(cr, uid, country_ids[0], context=context) # Filter the matching one jobs = [j for j in jobs if country==None or j.address_id==None or j.address_id.country_id and j.address_id.country_id.id == country.id] From fcfa5a56ef3c03fc6bfb56007cd5ecfffe4b644c Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 15 Jul 2014 10:55:41 +0200 Subject: [PATCH 019/270] [FIX] website_hr_recruitment: various warnings --- .../controllers/main.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/addons/website_hr_recruitment/controllers/main.py b/addons/website_hr_recruitment/controllers/main.py index a0c17735b2e..388bb8f9156 100644 --- a/addons/website_hr_recruitment/controllers/main.py +++ b/addons/website_hr_recruitment/controllers/main.py @@ -21,15 +21,16 @@ class website_hr_recruitment(http.Controller): context=dict(request.context, show_address=True, no_tag_br=True) cr, uid = request.cr, request.uid + Country = request.registry['res.country'] + Jobs = request.registry['hr.job'] + # Search all available jobs as uid - JobsObj = request.registry['hr.job'] - job_ids = JobsObj.search(cr, uid, [], order="website_published desc,no_of_recruitment desc", context=context) + job_ids = Jobs.search(cr, uid, [], order="website_published desc,no_of_recruitment desc", context=context) # Browse jobs as superuser, because address is restricted - jobs = JobsObj.browse(cr, 1, job_ids, context=context) + jobs = Jobs.browse(cr, 1, job_ids, context=context) # Deduce departments and offices of those jobs - countries = set(j.address_id.country_id for j in jobs if j.address_id and j.address_id.country_id) departments = set(j.department_id for j in jobs if j.department_id) offices = set(j.address_id for j in jobs if j.address_id) countries = set(o.country_id for o in offices if o.country_id) @@ -38,15 +39,17 @@ class website_hr_recruitment(http.Controller): if not country and not department and not office_id: country_code = request.session['geoip'].get('country_code') if country_code: - Country = request.registry['res.country'] country_ids = Country.search(cr, uid, [('code', '=', country_code)], context=context) if country_ids: country = Country.browse(cr, uid, country_ids[0], context=context) # Filter the matching one - jobs = [j for j in jobs if country==None or j.address_id==None or j.address_id.country_id and j.address_id.country_id.id == country.id] - jobs = [j for j in jobs if department==None or j.department_id and j.department_id.id == department.id] - jobs = [j for j in jobs if office_id==None or j.address_id and j.address_id.id == office_id] + if country: + jobs = (j for j in jobs if j.address_id is None or j.address_id.country_id and j.address_id.country_id.id == country.id) + if department: + jobs = (j for j in jobs if j.department_id and j.department_id.id == department.id) + if office_id: + jobs = (j for j in jobs if j.address_id and j.address_id.id == office_id) # Render page return request.website.render("website_hr_recruitment.index", { @@ -57,7 +60,6 @@ class website_hr_recruitment(http.Controller): 'country_id': country, 'department_id': department, 'office_id': office_id, - 'countries': countries }) @http.route('/jobs/add', type='http', auth="user", website=True) From c959f49179cead52d42d15bf4ac50a3936a51e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 15 Jul 2014 11:15:27 +0200 Subject: [PATCH 020/270] [FIX] website_forum: fixed controller creating a new forum tha was crashing. --- addons/website_forum/controllers/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py index 346445f478b..c0a2b8735c5 100644 --- a/addons/website_forum/controllers/main.py +++ b/addons/website_forum/controllers/main.py @@ -79,7 +79,7 @@ class WebsiteForum(http.Controller): forum_id = request.registry['forum.forum'].create(request.cr, request.uid, { 'name': forum_name, }, context=request.context) - return request.redirect("/forum/%s" % slug(forum_id)) + return request.redirect("/forum/%s" % forum_id) @http.route('/forum/notification_read', type='json', auth="user", multilang=True, methods=['POST'], website=True) def notification_read(self, **kwargs): From 92be431236bd93054c5134cb0d4daf8124918e39 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 15 Jul 2014 12:26:55 +0200 Subject: [PATCH 021/270] [REM] asynchronous database creation method It's broken (though easy to fix) and not very useful, if third parties want to create databases asynchronously they can handle the asynchronicity on the client-side (an HTTP request is easy to make asynchronously after all) and call the synchronous `create_database()`. fixes #1137, after a fashion --- openerp/modules/loading.py | 10 +------ openerp/service/db.py | 57 +------------------------------------- 2 files changed, 2 insertions(+), 65 deletions(-) diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index eb703e25eb6..656511e4a04 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -52,7 +52,7 @@ _test_logger = logging.getLogger('openerp.tests') def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None): """Migrates+Updates or Installs all module nodes from ``graph`` :param graph: graph of module nodes to load - :param status: status dictionary for keeping track of progress + :param status: deprecated parameter, unused, left to avoid changing signature in 8.0 :param perform_checks: whether module descriptors should be checked for validity (prints warnings for same cases) :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped @@ -120,9 +120,6 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= if kind in ('demo', 'test'): threading.currentThread().testing = False - if status is None: - status = {} - processed_modules = [] loaded_modules = [] registry = openerp.registry(cr.dbname) @@ -164,7 +161,6 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'): registry.setup_models(cr) init_module_models(cr, package.name, models) - status['progress'] = float(index) / len(graph) # Can't put this line out of the loop: ir.module.module will be # registered by init_module_models() above. @@ -186,7 +182,6 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= _load_data(cr, module_name, idref, mode, kind='data') has_demo = hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed') if has_demo: - status['progress'] = (index + 0.75) / len(graph) _load_data(cr, module_name, idref, mode, kind='demo') cr.execute('update ir_module_module set demo=%s where id=%s', (True, module_id)) modobj.invalidate_cache(cr, SUPERUSER_ID, ['demo'], [module_id]) @@ -272,9 +267,6 @@ def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_m return processed_modules def load_modules(db, force_demo=False, status=None, update_module=False): - # TODO status['progress'] reporting is broken: used twice (and reset each - # time to zero) in load_module_graph, not fine-grained enough. - # It should be a method exposed by the registry. initialize_sys_path() force = [] diff --git a/openerp/service/db.py b/openerp/service/db.py index afe136ed743..048c160916f 100644 --- a/openerp/service/db.py +++ b/openerp/service/db.py @@ -22,17 +22,12 @@ import security _logger = logging.getLogger(__name__) -self_actions = {} -self_id = 0 -self_id_protect = threading.Semaphore() - class DatabaseExists(Warning): pass # This should be moved to openerp.modules.db, along side initialize(). def _initialize_db(id, db_name, demo, lang, user_password): try: - self_actions[id]['progress'] = 0 db = openerp.sql_db.db_connect(db_name) with closing(db.cursor()) as cr: # TODO this should be removed as it is done by RegistryManager.new(). @@ -41,7 +36,7 @@ def _initialize_db(id, db_name, demo, lang, user_password): cr.commit() registry = openerp.modules.registry.RegistryManager.new( - db_name, demo, self_actions[id], update_module=True) + db_name, demo, None, update_module=True) with closing(db.cursor()) as cr: if lang: @@ -54,13 +49,9 @@ def _initialize_db(id, db_name, demo, lang, user_password): registry['res.users'].write(cr, SUPERUSER_ID, [SUPERUSER_ID], values) cr.execute('SELECT login, password FROM res_users ORDER BY login') - self_actions[id].update(users=cr.dictfetchall(), clean=True) cr.commit() - except Exception, e: - self_actions[id].update(clean=False, exception=e) _logger.exception('CREATE DATABASE failed:') - self_actions[id]['traceback'] = traceback.format_exc() def dispatch(method, params): if method in ['create', 'get_progress', 'drop', 'dump', 'restore', 'rename', @@ -90,34 +81,8 @@ def _create_empty_database(name): cr.autocommit(True) # avoid transaction block cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (name, chosen_template)) -def exp_create(db_name, demo, lang, user_password='admin'): - self_id_protect.acquire() - global self_id - self_id += 1 - id = self_id - self_id_protect.release() - - self_actions[id] = {'clean': False} - - _create_empty_database(db_name) - - _logger.info('CREATE DATABASE %s', db_name.lower()) - create_thread = threading.Thread(target=_initialize_db, - args=(id, db_name, demo, lang, user_password)) - create_thread.start() - self_actions[id]['thread'] = create_thread - return id - def exp_create_database(db_name, demo, lang, user_password='admin'): """ Similar to exp_create but blocking.""" - self_id_protect.acquire() - global self_id - self_id += 1 - id = self_id - self_id_protect.release() - - self_actions[id] = {'clean': False} - _logger.info('Create database `%s`.', db_name) _create_empty_database(db_name) _initialize_db(id, db_name, demo, lang, user_password) @@ -137,26 +102,6 @@ def exp_duplicate_database(db_original_name, db_name): shutil.copytree(from_fs, to_fs) return True -def exp_get_progress(id): - if self_actions[id]['thread'].isAlive(): -# return openerp.modules.init_progress[db_name] - return min(self_actions[id].get('progress', 0), 0.95), [] - else: - clean = self_actions[id]['clean'] - if clean: - users = self_actions[id]['users'] - for user in users: - # Remove the None passwords as they can't be marshalled by XML-RPC. - if user['password'] is None: - user['password'] = '' - self_actions.pop(id) - return 1.0, users - else: - a = self_actions.pop(id) - exc, tb = a['exception'], a['traceback'] - raise Exception, exc, tb - - def _drop_conn(cr, db_name): # Try to terminate all other connections that might prevent # dropping the database From 29a6dad80308807f8d9e880c7589984197b35c30 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 15 Jul 2014 13:21:57 +0200 Subject: [PATCH 022/270] [REM] threaded yaml import (useless and unused) --- openerp/tools/yaml_import.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/openerp/tools/yaml_import.py b/openerp/tools/yaml_import.py index 9ce79fc5513..a041cf511be 100644 --- a/openerp/tools/yaml_import.py +++ b/openerp/tools/yaml_import.py @@ -963,19 +963,4 @@ def yaml_import(cr, module, yamlfile, kind, idref=None, mode='init', noupdate=Fa # keeps convention of convert.py convert_yaml_import = yaml_import -def threaded_yaml_import(db_name, module_name, file_name, delay=0): - def f(): - time.sleep(delay) - cr = None - fp = None - try: - cr = sql_db.db_connect(db_name).cursor() - fp = misc.file_open(file_name) - convert_yaml_import(cr, module_name, fp, {}, 'update', True) - finally: - if cr: cr.close() - if fp: fp.close() - threading.Thread(target=f).start() - - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 027435a375bc5cecca9b7de4289bd9cdf9490932 Mon Sep 17 00:00:00 2001 From: odony Date: Tue, 15 Jul 2014 13:23:37 +0200 Subject: [PATCH 023/270] [FIX] README: update build badge for 8.0 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d76976405b3..d5570e7a028 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](http://runbot.odoo.com/runbot/badge/default/1/master.svg)](http://runbot.odoo.com/runbot) +[![Build Status](http://runbot.odoo.com/runbot/badge/default/1/8.0.svg)](http://runbot.odoo.com/runbot) Odoo ---- From 3f8fd0dad6de23ba304e8f6b156cca44d227885a Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 15 Jul 2014 11:24:22 +0200 Subject: [PATCH 024/270] [IMP] website_hr_recruitment: new API --- addons/website/models/website.py | 2 +- .../controllers/main.py | 63 ++++++++++--------- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 1f641a5abf5..9c9215fdf07 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -114,7 +114,7 @@ def slugify(s, max_length=None): def slug(value): if isinstance(value, orm.browse_record): # [(id, name)] = value.name_get() - id, name = value.id, value[value._rec_name] + id, name = value.id, value.display_name else: # assume name_search result tuple id, name = value diff --git a/addons/website_hr_recruitment/controllers/main.py b/addons/website_hr_recruitment/controllers/main.py index 388bb8f9156..8916551675f 100644 --- a/addons/website_hr_recruitment/controllers/main.py +++ b/addons/website_hr_recruitment/controllers/main.py @@ -6,6 +6,8 @@ from openerp import http from openerp.tools.translate import _ from openerp.http import request +from openerp.addons.website.models.website import slug + class website_hr_recruitment(http.Controller): @http.route([ '/jobs', @@ -16,19 +18,17 @@ class website_hr_recruitment(http.Controller): '/jobs/country//office/', '/jobs/department//office/', '/jobs/country//department//office/', - ], type='http', auth="public", website=True) + ], type='http', auth="public", website=True) def jobs(self, country=None, department=None, office_id=None): - context=dict(request.context, show_address=True, no_tag_br=True) - cr, uid = request.cr, request.uid + env = request.env(context=dict(request.env.context, show_address=True, no_tag_br=True)) - Country = request.registry['res.country'] - Jobs = request.registry['hr.job'] - - # Search all available jobs as uid - job_ids = Jobs.search(cr, uid, [], order="website_published desc,no_of_recruitment desc", context=context) + Country = env['res.country'] + Jobs = env['hr.job'] + # List jobs available to current UID + job_ids = Jobs.search([], order="website_published desc,no_of_recruitment desc").ids # Browse jobs as superuser, because address is restricted - jobs = Jobs.browse(cr, 1, job_ids, context=context) + jobs = Jobs.sudo().browse(job_ids) # Deduce departments and offices of those jobs departments = set(j.department_id for j in jobs if j.department_id) @@ -36,12 +36,11 @@ class website_hr_recruitment(http.Controller): countries = set(o.country_id for o in offices if o.country_id) # Default search by user country - if not country and not department and not office_id: + if not (country or department or office_id): country_code = request.session['geoip'].get('country_code') if country_code: - country_ids = Country.search(cr, uid, [('code', '=', country_code)], context=context) - if country_ids: - country = Country.browse(cr, uid, country_ids[0], context=context) + countries_ = Country.search([('code', '=', country_code)]) + country = countries[0] if countries_ else None # Filter the matching one if country: @@ -64,31 +63,33 @@ class website_hr_recruitment(http.Controller): @http.route('/jobs/add', type='http', auth="user", website=True) def jobs_add(self, **kwargs): - cr, uid, context = request.cr, request.uid, request.context - value = { + job = request.env['hr.job'].create({ 'name': _('New Job Offer'), - } - job_id = request.registry.get('hr.job').create(cr, uid, value, context=context) - return request.redirect("/jobs/detail/%s?enable_editor=1" % job_id) + }) + return request.redirect("/jobs/detail/%s?enable_editor=1" % slug(job)) - @http.route(['/jobs/detail/'], type='http', auth="public", website=True) + @http.route('/jobs/detail/', type='http', auth="public", website=True) def jobs_detail(self, job, **kwargs): - return request.website.render("website_hr_recruitment.detail", { 'job': job, 'main_object': job }) + return request.render("website_hr_recruitment.detail", { + 'job': job, + 'main_object': job, + }) - @http.route(['/jobs/apply/'], type='http', auth="public", website=True) + @http.route('/jobs/apply/', type='http', auth="public", website=True) def jobs_apply(self, job): error = {} default = {} if 'website_hr_recruitment_error' in request.session: error = request.session.pop('website_hr_recruitment_error') default = request.session.pop('website_hr_recruitment_default') - return request.website.render("website_hr_recruitment.apply", { 'job': job, 'error': error, 'default': default}) + return request.render("website_hr_recruitment.apply", { + 'job': job, + 'error': error, + 'default': default, + }) - @http.route(['/jobs/thankyou'], methods=['POST'], type='http', auth="public", website=True) + @http.route('/jobs/thankyou', methods=['POST'], type='http', auth="public", website=True) def jobs_thankyou(self, **post): - cr, uid, context = request.cr, request.uid, request.context - imd = request.registry['ir.model.data'] - error = {} for field_name in ["partner_name", "phone", "email_from"]: if not post.get(field_name): @@ -101,8 +102,10 @@ class website_hr_recruitment(http.Controller): request.session['website_hr_recruitment_default'] = post return request.redirect('/jobs/apply/%s' % post.get("job_id")) + # public user can't create applicants (duh) + env = request.env(user=SUPERUSER_ID) value = { - 'source_id' : imd.xmlid_to_res_id(cr, SUPERUSER_ID, 'hr_recruitment.source_website_company'), + 'source_id' : env.ref('hr_recruitment.source_website_company').id, 'name': '%s\'s Application' % post.get('partner_name'), } for f in ['email_from', 'partner_name', 'description']: @@ -112,7 +115,7 @@ class website_hr_recruitment(http.Controller): # Retro-compatibility for saas-3. "phone" field should be replace by "partner_phone" in the template in trunk. value['partner_phone'] = post.pop('phone', False) - applicant_id = request.registry['hr.applicant'].create(cr, SUPERUSER_ID, value, context=context) + applicant_id = env['hr.applicant'].create(value).id if post['ufile']: attachment_value = { 'name': post['ufile'].filename, @@ -122,7 +125,7 @@ class website_hr_recruitment(http.Controller): 'datas': base64.encodestring(post['ufile'].read()), 'datas_fname': post['ufile'].filename, } - request.registry['ir.attachment'].create(cr, SUPERUSER_ID, attachment_value, context=context) - return request.website.render("website_hr_recruitment.thankyou", {}) + env['ir.attachment'].create(attachment_value) + return request.render("website_hr_recruitment.thankyou", {}) # vim :et: From d4b923aec9fc342830c4de9d2f901c177a860f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 15 Jul 2014 12:54:38 +0200 Subject: [PATCH 025/270] [FIX] project_mrp: prevent crash during installation if stock is not installed; use default view for product coming from the product module that is indeed in the dependencies. --- addons/project_mrp/project_mrp_view.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/project_mrp/project_mrp_view.xml b/addons/project_mrp/project_mrp_view.xml index 90ab6682cd5..482d97bee31 100644 --- a/addons/project_mrp/project_mrp_view.xml +++ b/addons/project_mrp/project_mrp_view.xml @@ -14,9 +14,9 @@ product.form.view.inherit product.template - + - + From c363451f959c5daabc9a37bcbd42930cc851c2e1 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Tue, 15 Jul 2014 16:30:38 +0200 Subject: [PATCH 026/270] [FIX] sale: no warn onchange pricelist and no line The popup warning that changing the pricelist will not update the existing order lines should not be diplayed it there is no order line set. It was the case until 8.0. The change of behavior of on changes has broken this feature: Now, the on change return [(6, 0, [])] for an empy order line list, instead of just []. --- addons/sale/sale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 8881e917269..0cac2e8b511 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -291,7 +291,7 @@ class sale_order(osv.osv): value = { 'currency_id': self.pool.get('product.pricelist').browse(cr, uid, pricelist_id, context=context).currency_id.id } - if not order_lines: + if not order_lines or order_lines == [(6, 0, [])]: return {'value': value} warning = { 'title': _('Pricelist Warning!'), From 3b636a5df0b34779cb3f9cdc2f2dcf11ef93163f Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 15 Jul 2014 18:38:06 +0200 Subject: [PATCH 027/270] [WIP] Simplify procurement check and sales order workflow and that way phantom boms will work --- addons/procurement/procurement.py | 2 +- addons/sale/sale.py | 72 +++++++++++++------------------ addons/sale/sale_workflow.xml | 4 +- addons/sale_stock/sale_stock.py | 9 ---- addons/stock/procurement.py | 23 +++------- addons/stock/stock.py | 30 ++++++------- 6 files changed, 53 insertions(+), 87 deletions(-) diff --git a/addons/procurement/procurement.py b/addons/procurement/procurement.py index df8534e703d..2848858a088 100644 --- a/addons/procurement/procurement.py +++ b/addons/procurement/procurement.py @@ -195,7 +195,7 @@ class procurement_order(osv.osv): def run(self, cr, uid, ids, context=None): for procurement_id in ids: - #we intentionnaly do the browse under the for loop to avoid caching all ids which would be ressource greedy + #we intentionnaly do the browse under the for loop to avoid caching all ids which would be resource greedy #and useless as we'll make a refresh later that will invalidate all the cache (and thus the next iteration #will fetch all the ids again) procurement = self.browse(cr, uid, procurement_id, context=context) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 6cd6e815974..bdb87ad61f4 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -741,49 +741,7 @@ class sale_order(osv.osv): order.write(val) return True - # if mode == 'finished': - # returns True if all lines are done, False otherwise - # if mode == 'canceled': - # returns True if there is at least one canceled line, False otherwise - def test_state(self, cr, uid, ids, mode, *args): - assert mode in ('finished', 'canceled'), _("invalid mode for test_state") - finished = True - canceled = False - write_done_ids = [] - write_cancel_ids = [] - for order in self.browse(cr, uid, ids, context={}): - #TODO: Need to rethink what happens when cancelling - for line in order.order_line: - states = [x.state for x in line.procurement_ids] - cancel = states and all([x == 'cancel' for x in states]) - doneorcancel = all([x in ('done', 'cancel') for x in states]) - if cancel: - canceled = True - if line.state != 'exception': - write_cancel_ids.append(line.id) - if not doneorcancel: - finished = False - if doneorcancel and not cancel: - write_done_ids.append(line.id) - - if write_done_ids: - self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'}) - if write_cancel_ids: - self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'}) - - if mode == 'finished': - return finished - elif mode == 'canceled': - return canceled - - - def procurement_lines_get(self, cr, uid, ids, *args): - res = [] - for order in self.browse(cr, uid, ids, context={}): - for line in order.order_line: - res += [x.id for x in line.procurement_ids] - return res def onchange_fiscal_position(self, cr, uid, ids, fiscal_position, order_lines, context=None): '''Update taxes of order lines for each line where a product is defined @@ -828,6 +786,20 @@ class sale_order(osv.osv): order_line.append(line) return {'value': {'order_line': order_line}} + def test_procurements_done(self, cr, uid, ids, context=None): + for sale in self.browse(cr, uid, ids, context=context): + for line in sale.order_line: + if not all([x.state == 'done' for x in line.procurement_ids]): + return False + return True + + def test_procurements_except(self, cr, uid, ids, context=None): + for sale in self.browse(cr, uid, ids, context=context): + for line in sale.order_line: + if any([x.state == 'cancel' for x in line.procurement_ids]): + return True + return False + # TODO add a field price_unit_uos # - update it on change product and unit price @@ -924,6 +896,8 @@ class sale_order_line(osv.osv): 'delay': 0.0, } + + def _get_line_qty(self, cr, uid, line, context=None): if line.product_uos: return line.product_uos_qty or 0.0 @@ -1268,6 +1242,20 @@ class procurement_order(osv.osv): 'sale_line_id': fields.many2one('sale.order.line', string='Sale Order Line'), } + def write(self, cr, uid, ids, vals, context=None): + if isinstance(ids, (int, long)): + ids = [ids] + res = super(procurement_order, self).write(cr, uid, ids, vals, context=context) + from openerp import workflow + if vals.get('state') in ['done', 'cancel', 'exception']: + for proc in self.browse(cr, uid, ids, context=context): + if proc.sale_line_id and proc.sale_line_id.order_id and proc.move_ids: + order_id = proc.sale_line_id.order_id.id + if self.pool.get('sale.order').test_procurements_done(cr, uid, [order_id], context=context): + workflow.trg_validate(uid, 'sale.order', order_id, 'ship_end', cr) + if self.pool.get('sale.order').test_procurements_except(cr, uid, [order_id], context=context): + workflow.trg_validate(uid, 'sale.order', order_id, 'ship_except', cr) + return res class product_product(osv.Model): _inherit = 'product.product' diff --git a/addons/sale/sale_workflow.xml b/addons/sale/sale_workflow.xml index 831a0a9d12c..1dd093f4e1d 100644 --- a/addons/sale/sale_workflow.xml +++ b/addons/sale/sale_workflow.xml @@ -262,9 +262,7 @@ - procurement.order - procurement_lines_get() - test_state('finished') + ship_end diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py index 20bac48c562..ffe96dd6cb3 100644 --- a/addons/sale_stock/sale_stock.py +++ b/addons/sale_stock/sale_stock.py @@ -350,15 +350,6 @@ class sale_order_line(osv.osv): class stock_move(osv.osv): _inherit = 'stock.move' - def action_cancel(self, cr, uid, ids, context=None): - sale_ids = [] - for move in self.browse(cr, uid, ids, context=context): - if move.procurement_id and move.procurement_id.sale_line_id: - sale_ids.append(move.procurement_id.sale_line_id.order_id.id) - if sale_ids: - self.pool.get('sale.order').signal_workflow(cr, uid, sale_ids, 'ship_except') - return super(stock_move, self).action_cancel(cr, uid, ids, context=context) - def _create_invoice_line_from_vals(self, cr, uid, move, invoice_line_vals, context=None): invoice_line_id = super(stock_move, self)._create_invoice_line_from_vals(cr, uid, move, invoice_line_vals, context=context) if move.procurement_id and move.procurement_id.sale_line_id: diff --git a/addons/stock/procurement.py b/addons/stock/procurement.py index b8d7a03c9f2..2024b016ef3 100644 --- a/addons/stock/procurement.py +++ b/addons/stock/procurement.py @@ -222,27 +222,18 @@ class procurement_order(osv.osv): ''' if procurement.rule_id and procurement.rule_id.action == 'move': uom_obj = self.pool.get('product.uom') - done_test_list = [] - done_cancel_test_list = [] - qty_done = 0 - for move in procurement.move_ids: - done_test_list.append(move.state == 'done') - done_cancel_test_list.append(move.state in ('done', 'cancel')) - qty_done += move.product_qty if move.state == 'done' else 0 - qty_done = uom_obj._compute_qty(cr, uid, procurement.product_id.uom_id.id, qty_done, procurement.product_uom.id) - at_least_one_done = any(done_test_list) + cancel_test_list = [x.state == 'cancel' for x in procurement.move_ids] + done_cancel_test_list = [x.state in ('done', 'cancel') for x in procurement.move_ids] + at_least_one_cancel = any(cancel_test_list) all_done_or_cancel = all(done_cancel_test_list) + all_cancel = all(cancel_test_list) if not all_done_or_cancel: return False - elif all_done_or_cancel and procurement.product_qty == qty_done: + elif all_done_or_cancel and not all_cancel: return True - elif at_least_one_done: - #some move cancelled and some validated - self.message_post(cr, uid, [procurement.id], body=_('Some stock moves have been cancelled for this procurement. Run the procurement again to trigger a move for the remaining quantity or change the procurement quantity to finish it directly'), context=context) - else: - #all move are cancelled + elif all_cancel: self.message_post(cr, uid, [procurement.id], body=_('All stock moves have been cancelled for this procurement.'), context=context) - self.write(cr, uid, [procurement.id], {'state': 'exception'}, context=context) + self.write(cr, uid, [procurement.id], {'state': 'cancel'}, context=context) return False return super(procurement_order, self)._check(cr, uid, procurement, context) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 938a759da46..2a316fa3461 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2148,20 +2148,18 @@ class stock_move(osv.osv): if move.propagate: procurement_ids = procurement_obj.search(cr, uid, [('move_dest_id', '=', move.id)], context=context) procurement_obj.cancel(cr, uid, procurement_ids, context=context) - elif move.move_dest_id: - #cancel chained moves - if move.propagate: - self.action_cancel(cr, uid, [move.move_dest_id.id], context=context) - # If we have a long chain of moves to be cancelled, it is easier for the user to handle - # only the last procurement which will go into exception, instead of all procurements - # along the chain going into exception. We need to check if there are no split moves not cancelled however - if move.procurement_id: - proc = move.procurement_id - if all([x.state == 'cancel' for x in proc.move_ids if x.id != move.id]): - procurement_obj.write(cr, uid, [proc.id], {'state': 'cancel'}) - - elif move.move_dest_id.state == 'waiting': - self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}, context=context) + else: + if move.move_dest_id: + if move.propagate: + self.action_cancel(cr, uid, [move.move_dest_id.id], context=context) + elif move.move_dest_id.state == 'waiting': + #If waiting, the chain will be broken and we are not sure if we can still wait for it (=> could take from stock instead) + self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}, context=context) + if move.procurement_id: + # Does the same as procurement check, only eliminating a refresh + proc = move.procurement_id + if all([x.state == 'cancel' for x in proc.move_ids if x.id != move.id]): + procurement_obj.write(cr, uid, [proc.id], {'state': 'cancel'}) return self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}, context=context) def _check_package_from_moves(self, cr, uid, ids, context=None): @@ -3934,7 +3932,8 @@ class stock_warehouse_orderpoint(osv.osv): continue procurement_qty = uom_obj._compute_qty_obj(cr, uid, procurement.product_uom, procurement.product_qty, procurement.product_id.uom_id, context=context) for move in procurement.move_ids: - if move.state not in ('draft', 'cancel'): + #need to add the moves in draft as they aren't in the virtual quantity + moves that have not been created yet + if move.state not in ('draft'): #if move is already confirmed, assigned or done, the virtual stock is already taking this into account so it shouldn't be deducted procurement_qty -= move.product_qty qty += procurement_qty @@ -3950,7 +3949,6 @@ class stock_warehouse_orderpoint(osv.osv): for rule in self.browse(cr, uid, ids, context=context): if rule.product_id.uom_id.category_id.id != rule.product_uom.category_id.id: return False - return True def action_view_proc_to_process(self, cr, uid, ids, context=None): From bff521b23fd0d10788a8e9bc294d43e0949fba61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Maes?= Date: Fri, 11 Jul 2014 15:15:12 +0200 Subject: [PATCH 028/270] [FIX] bus, im_chat, website_livechat :fix date in js, bus cr.commit and access rules for portal --- addons/bus/bus.py | 2 +- addons/im_chat/im_chat.py | 3 ++- addons/im_chat/security/ir.model.access.csv | 4 ++++ addons/im_chat/static/src/js/im_chat.js | 3 ++- addons/website_livechat/__openerp__.py | 3 ++- addons/website_livechat/website_livechat_data.xml | 10 ++++++++++ 6 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 addons/website_livechat/website_livechat_data.xml diff --git a/addons/bus/bus.py b/addons/bus/bus.py index dc6ff6d7f1a..d3801743fdb 100644 --- a/addons/bus/bus.py +++ b/addons/bus/bus.py @@ -51,8 +51,8 @@ class ImBus(osv.Model): "channel" : json_dump(channel), "message" : json_dump(message) } - cr.commit() self.pool['bus.bus'].create(cr, openerp.SUPERUSER_ID, values) + cr.commit() if random.random() < 0.01: self.gc(cr, uid) if channels: diff --git a/addons/im_chat/im_chat.py b/addons/im_chat/im_chat.py index 3617962aa16..4ca27282c6b 100644 --- a/addons/im_chat/im_chat.py +++ b/addons/im_chat/im_chat.py @@ -136,7 +136,8 @@ class im_chat_session(osv.Model): if session_id: # get the image of the user res = self.pool["res.users"].read(cr, uid, [user_id], ["image_small"])[0] - image_b64 = res["image_small"] + if res["image_small"]: + image_b64 = res["image_small"] return image_b64 class im_chat_message(osv.Model): diff --git a/addons/im_chat/security/ir.model.access.csv b/addons/im_chat/security/ir.model.access.csv index a85541d1aa4..c1caaf14ce0 100644 --- a/addons/im_chat/security/ir.model.access.csv +++ b/addons/im_chat/security/ir.model.access.csv @@ -3,3 +3,7 @@ access_im_chat_message,im_chat.message,model_im_chat_message,base.group_user,1,0 access_im_chat_session,im_chat.session,model_im_chat_session,base.group_user,1,1,1,0 access_im_chat_conversation_state,im_chat.conversation_state,model_im_chat_conversation_state,base.group_user,1,1,1,0 access_im_chat_presence,im_chat.presence,model_im_chat_presence,base.group_user,1,1,1,1 +access_im_chat_message_portal,im_chat.message,model_im_chat_message,base.group_portal,1,0,1,0 +access_im_chat_session_portal,im_chat.session,model_im_chat_session,base.group_portal,1,1,1,0 +access_im_chat_conversation_state_portal,im_chat.conversation_state,model_im_chat_conversation_state,base.group_portal,1,1,1,0 +access_im_chat_presence_portal,im_chat.presence,model_im_chat_presence,base.group_portal,1,1,1,1 \ No newline at end of file diff --git a/addons/im_chat/static/src/js/im_chat.js b/addons/im_chat/static/src/js/im_chat.js index b08cd0d1e29..3ee54dbeddc 100644 --- a/addons/im_chat/static/src/js/im_chat.js +++ b/addons/im_chat/static/src/js/im_chat.js @@ -293,7 +293,7 @@ } m.message = self.escape_keep_url(m.message); m.message = self.smiley(m.message); - m.create_date = Date.parse(m.create_date).setTimezone("UTC").toString("yyyy-dd-MM HH:mm:ss"); + m.create_date = Date.parse(m.create_date).setTimezone("UTC").toString("yyyy-MM-dd HH:mm:ss"); return m; }); this.set("messages", _.sortBy(this.get("messages").concat(messages), function(m){ return m.id; })); @@ -528,6 +528,7 @@ }); }, switch_display: function() { + this.calc_box(); var fct = _.bind(function(place) { this.set("right_offset", place + this.$el.outerWidth()); }, this); diff --git a/addons/website_livechat/__openerp__.py b/addons/website_livechat/__openerp__.py index e41f2072a42..1e87f362778 100644 --- a/addons/website_livechat/__openerp__.py +++ b/addons/website_livechat/__openerp__.py @@ -13,6 +13,7 @@ For website built with Odoo CMS, this module include a chat button on your Websi 'installable': True, 'data': [ 'views/website_livechat.xml', - 'views/res_config.xml' + 'views/res_config.xml', + 'website_livechat_data.xml' ], } diff --git a/addons/website_livechat/website_livechat_data.xml b/addons/website_livechat/website_livechat_data.xml new file mode 100644 index 00000000000..55c59895664 --- /dev/null +++ b/addons/website_livechat/website_livechat_data.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file From e23a36c1a53c623824b24958660dc619021d41a4 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 16 Jul 2014 10:06:15 +0200 Subject: [PATCH 029/270] [FIX] Make sure that when cancelling a move the linked procurements are checked the right way --- addons/sale/sale.py | 2 +- addons/stock/stock.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index bdb87ad61f4..d115e81b601 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -718,7 +718,7 @@ class sale_order(osv.osv): procurement_obj.check(cr, uid, [x.id for x in line.procurement_ids if x.state not in ['cancel', 'done']]) line.refresh() #run again procurement that are in exception in order to trigger another move - proc_ids += [x.id for x in line.procurement_ids if x.state == 'exception'] + proc_ids += [x.id for x in line.procurement_ids if x.state in ('exception', 'cancel')] elif sale_line_obj.need_procurement(cr, uid, [line.id], context=context): if (line.state == 'done') or not line.product_id: continue diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 2a316fa3461..8732ad93593 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2138,6 +2138,7 @@ class stock_move(osv.osv): """ procurement_obj = self.pool.get('procurement.order') context = context or {} + procs_to_check = [] for move in self.browse(cr, uid, ids, context=context): if move.state == 'done': raise osv.except_osv(_('Operation Forbidden!'), @@ -2157,10 +2158,12 @@ class stock_move(osv.osv): self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}, context=context) if move.procurement_id: # Does the same as procurement check, only eliminating a refresh - proc = move.procurement_id - if all([x.state == 'cancel' for x in proc.move_ids if x.id != move.id]): - procurement_obj.write(cr, uid, [proc.id], {'state': 'cancel'}) - return self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}, context=context) + procs_to_check.append(move.procurement_id.id) + + res = self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}, context=context) + if procs_to_check: + procurement_obj.check(cr, uid, procs_to_check, context=context) + return res def _check_package_from_moves(self, cr, uid, ids, context=None): pack_obj = self.pool.get("stock.quant.package") From db715f99f48cfad38ad8fd36d05ba6d00ac87587 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 16 Jul 2014 10:23:57 +0200 Subject: [PATCH 030/270] [IMP] Change reception to receipt --- addons/mrp/report/mrp_report_view.xml | 2 +- addons/product/product.py | 6 +++--- addons/product_email_template/data/product_demo.xml | 4 ++-- addons/stock/stock_view.xml | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/addons/mrp/report/mrp_report_view.xml b/addons/mrp/report/mrp_report_view.xml index c5772ffd240..db2a2f8d68f 100644 --- a/addons/mrp/report/mrp_report_view.xml +++ b/addons/mrp/report/mrp_report_view.xml @@ -87,7 +87,7 @@ report.mrp.inout form graph,tree - Weekly Stock Value Variation enables you to track the stock value evolution linked to manufacturing activities, receptions of products and delivery orders. + Weekly Stock Value Variation enables you to track the stock value evolution linked to manufacturing activities, receipts of products and delivery orders. diff --git a/addons/product/product.py b/addons/product/product.py index f0a207a8064..f8b38c68ebe 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -492,7 +492,7 @@ class product_template(osv.osv): help="A precise description of the Product, used only for internal information purposes."), 'description_purchase': fields.text('Purchase Description',translate=True, help="A description of the Product that you want to communicate to your suppliers. " - "This description will be copied to every Purchase Order, Reception and Supplier Invoice/Refund."), + "This description will be copied to every Purchase Order, Receipt and Supplier Invoice/Refund."), 'description_sale': fields.text('Sale Description',translate=True, help="A description of the Product that you want to communicate to your customers. " "This description will be copied to every Sale Order, Delivery Order and Customer Invoice/Refund"), @@ -550,7 +550,7 @@ class product_template(osv.osv): "the picking order and is mainly used if you use the EDI module."), 'seller_ids': fields.one2many('product.supplierinfo', 'product_tmpl_id', 'Supplier'), 'seller_delay': fields.related('seller_ids','delay', type='integer', string='Supplier Lead Time', - help="This is the average delay in days between the purchase order confirmation and the reception of goods for this product and for the default supplier. It is used by the scheduler to order requests based on reordering delays."), + help="This is the average delay in days between the purchase order confirmation and the receipts for this product and for the default supplier. It is used by the scheduler to order requests based on reordering delays."), 'seller_qty': fields.related('seller_ids','qty', type='float', string='Supplier Quantity', help="This is minimum quantity to purchase from Main Supplier."), 'seller_id': fields.related('seller_ids','name', type='many2one', relation='res.partner', string='Main Supplier', @@ -1162,7 +1162,7 @@ class product_supplierinfo(osv.osv): 'min_qty': fields.float('Minimal Quantity', required=True, help="The minimal quantity to purchase to this supplier, expressed in the supplier Product Unit of Measure if not empty, in the default unit of measure of the product otherwise."), 'qty': fields.function(_calc_qty, store=True, type='float', string='Quantity', multi="qty", help="This is a quantity which is converted into Default Unit of Measure."), 'product_tmpl_id' : fields.many2one('product.template', 'Product Template', required=True, ondelete='cascade', select=True, oldname='product_id'), - 'delay' : fields.integer('Delivery Lead Time', required=True, help="Lead time in days between the confirmation of the purchase order and the reception of the products in your warehouse. Used by the scheduler for automatic computation of the purchase order planning."), + 'delay' : fields.integer('Delivery Lead Time', required=True, help="Lead time in days between the confirmation of the purchase order and the receipt of the products in your warehouse. Used by the scheduler for automatic computation of the purchase order planning."), 'pricelist_ids': fields.one2many('pricelist.partnerinfo', 'suppinfo_id', 'Supplier Pricelist', copy=True), 'company_id':fields.many2one('res.company','Company',select=1), } diff --git a/addons/product_email_template/data/product_demo.xml b/addons/product_email_template/data/product_demo.xml index bc5290fbd17..7fcc7c09035 100644 --- a/addons/product_email_template/data/product_demo.xml +++ b/addons/product_email_template/data/product_demo.xml @@ -257,7 +257,7 @@
  • Request for Quotation
  • Purchase order
  • Invoicing & Invoice control
  • -
  • Incoming order (Complete/partial reception)
  • +
  • Incoming order (Complete/partial receival)
  • Purchase requisition
  • @@ -465,7 +465,7 @@ - From the sales perspective, you will have a manufacturing order generated based on a request coming from a customer without any manual operation. This integration of those two aplications allows you to perform the following flow in an automated way: Sale order –> Stock level verification –> Production of the missing units –> Reception in stock of the finished goods –> Delivery to the customer. + From the sales perspective, you will have a manufacturing order generated based on a request coming from a customer without any manual operation. This integration of those two aplications allows you to perform the following flow in an automated way: Sale order –> Stock level verification –> Production of the missing units –> Receival in stock of the finished goods –> Delivery to the customer. diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 238239fd9f3..c322739a874 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -1158,7 +1158,7 @@ Reception Picking (By Stock Move) From stock_partial_move_view --> - + stock.move.tree2 stock.move @@ -1188,7 +1188,7 @@ - + stock.move.tree3 stock.move @@ -1210,7 +1210,7 @@ form tree,form - + {'product_receive': True, 'search_default_future': True}

    From 99556240299a8350f7b73534056e19ed2b1b3b39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 15 Jul 2014 16:44:55 +0200 Subject: [PATCH 031/270] website_forum: fixed issue when voting. The method should work in batch. Previously to this implementation having one post with a vote bypassed the other posts without vote when called with several ids. --- addons/website_forum/models/forum.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py index caa9dff6e57..edcddfe8d67 100644 --- a/addons/website_forum/models/forum.py +++ b/addons/website_forum/models/forum.py @@ -256,6 +256,8 @@ class Post(osv.Model): def vote(self, cr, uid, ids, upvote=True, context=None): Vote = self.pool['forum.post.vote'] vote_ids = Vote.search(cr, uid, [('post_id', 'in', ids), ('user_id', '=', uid)], context=context) + new_vote = '1' if upvote else '-1' + voted_forum_ids = set() if vote_ids: for vote in Vote.browse(cr, uid, vote_ids, context=context): if upvote: @@ -263,9 +265,9 @@ class Post(osv.Model): else: new_vote = '0' if vote.vote == '1' else '-1' Vote.write(cr, uid, vote_ids, {'vote': new_vote}, context=context) - else: + voted_forum_ids.add(vote.post_id.id) + for post_id in set(ids) - voted_forum_ids: for post_id in ids: - new_vote = '1' if upvote else '-1' Vote.create(cr, uid, {'post_id': post_id, 'vote': new_vote}, context=context) return {'vote_count': self._get_vote_count(cr, uid, ids, None, None, context=context)[ids[0]]} From f8d8179a92214566af8812cb187d859d5690417e Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Wed, 18 Jun 2014 18:19:01 +0200 Subject: [PATCH 032/270] [FIX] website_sale: add shop/pricelist controller --- addons/website_sale/controllers/main.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 525530fca79..bcd86aff7a4 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -238,6 +238,12 @@ class website_sale(http.Controller): context=dict(context, mail_create_nosubcribe=True)) return werkzeug.utils.redirect(request.httprequest.referrer + "#comments") + @http.route(['/shop/pricelist'], type='http', auth="public", website=True) + def pricelist(self, promo, **post): + cr, uid, context = request.cr, request.uid, request.context + request.website.sale_get_order(code=promo, context=context) + return request.redirect("/shop/cart") + @http.route(['/shop/cart'], type='http', auth="public", website=True) def cart(self, **post): order = request.website.sale_get_order() From 0aad6b04986a481ad820824729bf81b698a93669 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 16 Jul 2014 11:58:22 +0200 Subject: [PATCH 033/270] [IMP] Check other comments on pull request: xmlid, no split in context + continue improve receipts instead of receptions --- addons/account/account_view.xml | 2 +- .../account_analytic_default.py | 5 ++- .../account_anglo_saxon/test/anglo_saxon.yml | 2 +- .../test/anglo_saxon_avg_fifo.yml | 2 +- addons/mrp/mrp.py | 38 +++++++++---------- addons/mrp/product.py | 4 +- addons/mrp/stock.py | 3 +- .../data/product_demo.xml | 2 +- addons/purchase/__openerp__.py | 4 +- addons/purchase/partner_view.xml | 4 +- addons/purchase/purchase.py | 12 +++--- addons/purchase/purchase_view.xml | 4 +- addons/purchase/stock.py | 4 +- addons/purchase/stock_view.xml | 4 +- addons/sale/sale.py | 5 +-- addons/stock/doc/stock.rst | 2 +- addons/stock/product.py | 22 +++++------ addons/stock/product_view.xml | 16 ++++---- addons/stock/stock.py | 16 ++++---- addons/stock/stock_view.xml | 6 +-- addons/stock/test/packing.yml | 2 +- addons/stock/test/packingneg.yml | 2 +- addons/stock/test/shipment.yml | 2 +- addons/stock/test/wiseoperator.yml | 2 +- .../test/cancellation_propagated.yml | 2 +- addons/stock_dropshipping/test/lifo_price.yml | 6 +-- .../stock_dropshipping/test/stock_combi.yml | 2 +- 27 files changed, 85 insertions(+), 90 deletions(-) diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index 5e80bfdadfe..22b4ceccdfc 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -1925,7 +1925,7 @@ - + diff --git a/addons/account_analytic_default/account_analytic_default.py b/addons/account_analytic_default/account_analytic_default.py index 6212e69ceb7..f24ba556f91 100644 --- a/addons/account_analytic_default/account_analytic_default.py +++ b/addons/account_analytic_default/account_analytic_default.py @@ -136,7 +136,7 @@ class product_template(osv.Model): def _rules_count(self, cr, uid, ids, field_name, arg, context=None): Analytic = self.pool['account.analytic.default'] res = {} - for product_tmpl_id in self.browse(cr, uid, ids): + for product_tmpl_id in self.browse(cr, uid, ids, context=context): res[product_tmpl_id.id] = sum([p.rules_count for p in product_tmpl_id.product_variant_ids]) return res @@ -147,8 +147,9 @@ class product_template(osv.Model): def action_view_rules(self, cr, uid, ids, context=None): products = self._get_products(cr, uid, ids, context=context) - result = self._get_act_window_dict(cr, uid, 'account_analytic_default','action_product_default_list', context=context) + result = self._get_act_window_dict(cr, uid, 'account_analytic_default.action_product_default_list', context=context) result['domain'] = "[('product_id','in',[" + ','.join(map(str, products)) + "])]" + # Remove context so it is not going to filter on product_id with active_id of template result['context'] = "{}" return result diff --git a/addons/account_anglo_saxon/test/anglo_saxon.yml b/addons/account_anglo_saxon/test/anglo_saxon.yml index c98de7c47db..32f5fd12dee 100644 --- a/addons/account_anglo_saxon/test/anglo_saxon.yml +++ b/addons/account_anglo_saxon/test/anglo_saxon.yml @@ -124,7 +124,7 @@ - !workflow {model: purchase.order, ref: purchase_order_001, action: purchase_confirm} - - Reception is ready for process so now done the reception. + Receipt is ready for process so now we do it. - !python {model: stock.picking}: | picking_id = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001"), context=context).picking_ids[0] diff --git a/addons/account_anglo_saxon/test/anglo_saxon_avg_fifo.yml b/addons/account_anglo_saxon/test/anglo_saxon_avg_fifo.yml index 03442260295..daeefc09730 100644 --- a/addons/account_anglo_saxon/test/anglo_saxon_avg_fifo.yml +++ b/addons/account_anglo_saxon/test/anglo_saxon_avg_fifo.yml @@ -132,7 +132,7 @@ - !workflow {model: purchase.order, ref: purchase_order_001_fifo, action: purchase_confirm} - - Reception is ready for process so now done the reception. + Receipt is ready for process so now we do it. - !python {model: stock.picking}: | picking_id = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001_fifo"), context=context).picking_ids[0] diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index 586b8750090..e2e94233861 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -1049,25 +1049,21 @@ class mrp_production(osv.osv): return "make_to_order" return "make_to_stock" - def _create_previous_move(self, cr, uid, production, product, uom_id, qty, uos_id, uos_qty, source_location_id, dest_location_id, context=None): + def _create_previous_move(self, cr, uid, move_id, product, source_location_id, dest_location_id, context=None): + ''' + When the routing gives a different location than the raw material location of the production order, + we should create an extra move from the raw material location to the location of the routing, which + precedes the consumption line (chained) + ''' stock_move = self.pool.get('stock.move') - move_id = stock_move.create(cr, uid, { - 'name': production.name, - 'date': production.date_planned, - 'product_id': product.id, - 'product_uom_qty': qty, - 'product_uom': uom_id, - 'product_uos_qty': uos_id and uos_qty or False, - 'product_uos': uos_id or False, + move = stock_move.copy(cr, uid, move_id, default = { 'location_id': source_location_id, 'location_dest_id': dest_location_id, - 'company_id': production.company_id.id, 'procure_method': self._get_raw_material_procure_method(cr, uid, product, context=context), - #this saves us a browse in create() - 'price_unit': product.standard_price, - 'origin': production.name, - }) - return move_id + 'raw_material_production_id': False, + 'move_dest_id': move_id, + }, context=context) + return move def _make_consume_line_from_data(self, cr, uid, production, product, uom_id, qty, uos_id, uos_qty, context=None): stock_move = self.pool.get('stock.move') @@ -1076,14 +1072,13 @@ class mrp_production(osv.osv): return False # Take routing location as a Source Location. source_location_id = production.location_src_id.id + prod_location_id = source_location_id prev_move= False if production.bom_id.routing_id and production.bom_id.routing_id.location_id and production.bom_id.routing_id.location_id.id != source_location_id: - source_location2_id = source_location_id source_location_id = production.bom_id.routing_id.location_id.id - prev_move = self._create_previous_move(cr, uid, production, product, uom_id, qty, uos_id, uos_qty, source_location2_id, source_location_id, context=context) + prev_move = True + destination_location_id = production.product_id.property_stock_production.id - if not source_location_id: - source_location_id = production.location_src_id.id move_id = stock_move.create(cr, uid, { 'name': production.name, 'date': production.date_planned, @@ -1100,9 +1095,10 @@ class mrp_production(osv.osv): #this saves us a browse in create() 'price_unit': product.standard_price, 'origin': production.name, - }) + }, context=context) + if prev_move: - stock_move.write(cr, uid, [prev_move], {'move_dest_id': move_id}, context=context) + prev_move = self._create_previous_move(cr, uid, move_id, product, prod_location_id, source_location_id, context=context) return move_id def _make_production_consume_line(self, cr, uid, line, context=None): diff --git a/addons/mrp/product.py b/addons/mrp/product.py index 96f78c0f9e8..e767687512e 100644 --- a/addons/mrp/product.py +++ b/addons/mrp/product.py @@ -55,7 +55,7 @@ class product_template(osv.osv): def action_view_mos(self, cr, uid, ids, context=None): products = self._get_products(cr, uid, ids, context=context) - result = self._get_act_window_dict(cr, uid, 'mrp','act_product_mrp_production', context=context) + result = self._get_act_window_dict(cr, uid, 'mrp.act_product_mrp_production', context=context) result['domain'] = "[('product_id','in',[" + ','.join(map(str, products)) + "])]" result['context'] = "{}" return result @@ -80,7 +80,7 @@ class product_product(osv.osv): products = set() for product in self.browse(cr, uid, ids, context=context): products.add(product.product_tmpl_id.id) - result = tmpl_obj._get_act_window_dict(cr, uid, 'mrp','product_open_bom', context=context) + result = tmpl_obj._get_act_window_dict(cr, uid, 'mrp.product_open_bom', context=context) result['context'] = "{}" result['domain'] = "[('product_tmpl_id','in',[" + ','.join(map(str, list(products))) + "])]" return result diff --git a/addons/mrp/stock.py b/addons/mrp/stock.py index 91265abb2da..4b442077a5d 100644 --- a/addons/mrp/stock.py +++ b/addons/mrp/stock.py @@ -86,8 +86,9 @@ class StockMove(osv.osv): 'state': state, 'name': line['name'], 'procurement_id': move.procurement_id.id, + 'split_from': move.id, #Needed in order to keep purchase connection, but will be removed by unlink } - mid = move_obj.copy(cr, uid, move.id, default=valdef, context={'split': True}) + mid = move_obj.copy(cr, uid, move.id, default=valdef) to_explode_again_ids.append(mid) #delete the move with original product which is not relevant anymore diff --git a/addons/product_email_template/data/product_demo.xml b/addons/product_email_template/data/product_demo.xml index 7fcc7c09035..edf2d8d5841 100644 --- a/addons/product_email_template/data/product_demo.xml +++ b/addons/product_email_template/data/product_demo.xml @@ -257,7 +257,7 @@

  • Request for Quotation
  • Purchase order
  • Invoicing & Invoice control
  • -
  • Incoming order (Complete/partial receival)
  • +
  • Incoming order (Complete/partial receipt)
  • Purchase requisition
  • diff --git a/addons/purchase/__openerp__.py b/addons/purchase/__openerp__.py index 746bb50f59d..a926276f22a 100644 --- a/addons/purchase/__openerp__.py +++ b/addons/purchase/__openerp__.py @@ -25,7 +25,7 @@ 'version': '1.1', 'category': 'Purchase Management', 'sequence': 19, - 'summary': 'Purchase Orders, Receptions, Supplier Invoices', + 'summary': 'Purchase Orders, Receipts, Supplier Invoices', 'description': """ Manage goods requirement by Purchase Orders easily ================================================== @@ -40,7 +40,7 @@ Dashboard / Reports for Purchase Management will include: * Request for Quotations * Purchase Orders Waiting Approval * Monthly Purchases by Category -* Receptions Analysis +* Receipt Analysis * Purchase Analysis """, 'author': 'OpenERP SA', diff --git a/addons/purchase/partner_view.xml b/addons/purchase/partner_view.xml index eb8f01e047d..8cc354160f4 100644 --- a/addons/purchase/partner_view.xml +++ b/addons/purchase/partner_view.xml @@ -25,7 +25,7 @@ This supplier has no purchase order. Click to create a new RfQ.

    The request for quotation is the first step of the purchases flow. Once - converted into a purchase order, you will be able to control the reception + converted into a purchase order, you will be able to control the receipt of the products and the supplier invoice.

    @@ -60,7 +60,7 @@ Click here to record a supplier invoice.

    Supplier invoices can be pre-generated based on purchase - orders or receptions. This allows you to control invoices + orders or receipts. This allows you to control invoices you receive from your supplier according to the draft document in OpenERP.

    diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 1d948de1ba3..c7ce205df6e 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -232,7 +232,7 @@ class purchase_order(osv.osv): "to 'Confirmed'. Then the supplier must confirm the order to change " "the status to 'Approved'. When the purchase order is paid and " "received, the status becomes 'Done'. If a cancel action occurs in " - "the invoice or in the reception of goods, the status becomes " + "the invoice or in the receipt of goods, the status becomes " "in exception.", select=True, copy=False), 'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', @@ -244,7 +244,7 @@ class purchase_order(osv.osv): 'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', copy=False, help="Invoices generated for a purchase order"), - 'picking_ids': fields.function(_get_picking_ids, method=True, type='one2many', relation='stock.picking', string='Picking List', help="This is the list of reception operations that have been generated for this purchase order."), + 'picking_ids': fields.function(_get_picking_ids, method=True, type='one2many', relation='stock.picking', string='Picking List', help="This is the list of receipts that have been generated for this purchase order."), 'shipped':fields.boolean('Received', readonly=True, select=True, copy=False, help="It indicates that a picking has been done"), 'shipped_rate': fields.function(_shipped_rate, string='Received Ratio', type='float'), @@ -255,7 +255,7 @@ class purchase_order(osv.osv): readonly=True, states={'draft':[('readonly',False)], 'sent':[('readonly',False)]}, help="Based on Purchase Order lines: place individual lines in 'Invoice Control / On Purchase Order lines' from where you can selectively create an invoice.\n" \ "Based on generated invoice: create a draft invoice you can validate later.\n" \ - "Based on incoming shipments: let you create an invoice when receptions are validated." + "Based on incoming shipments: let you create an invoice when receipts are validated." ), 'minimum_planned_date':fields.function(_minimum_planned_date, fnct_inv=_set_minimum_planned_date, string='Expected Date', type='date', select=True, help="This is computed as the minimum scheduled date of all purchase order lines' products.", store = { @@ -678,7 +678,7 @@ class purchase_order(osv.osv): if pick.state not in ('draft', 'cancel'): raise osv.except_osv( _('Unable to cancel the purchase order %s.') % (purchase.name), - _('First cancel all receptions related to this purchase order.')) + _('First cancel all receipts related to this purchase order.')) self.pool.get('stock.picking') \ .signal_workflow(cr, uid, map(attrgetter('id'), purchase.picking_ids), 'button_cancel') for inv in purchase.invoice_ids: @@ -1404,7 +1404,7 @@ class product_template(osv.Model): def action_view_purchases(self, cr, uid, ids, context=None): products = self._get_products(cr, uid, ids, context=context) - result = self._get_act_window_dict(cr, uid, 'purchase','action_purchase_line_product_tree', context=context) + result = self._get_act_window_dict(cr, uid, 'purchase.action_purchase_line_product_tree', context=context) result['domain'] = "[('product_id','in',[" + ','.join(map(str, products)) + "])]" return result @@ -1438,7 +1438,7 @@ class mail_compose_message(osv.Model): class account_invoice(osv.Model): """ Override account_invoice to add Chatter messages on the related purchase - orders, logging the invoice reception or payment. """ + orders, logging the invoice receipt or payment. """ _inherit = 'account.invoice' def invoice_validate(self, cr, uid, ids, context=None): diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml index 33e3228602b..16b763d299c 100644 --- a/addons/purchase/purchase_view.xml +++ b/addons/purchase/purchase_view.xml @@ -87,7 +87,7 @@ - @@ -109,7 +109,7 @@

    Use this menu to control the invoices to be received from your supplier. OpenERP generates draft invoices from your purchase - orders or receptions, according to your settings. + orders or receipts, according to your settings.

    Once you receive a supplier invoice, you can match it with the draft invoice and validate it. diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index 80c5f54fcd5..a966ac8308f 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -48,7 +48,7 @@ class stock_move(osv.osv): def copy(self, cr, uid, id, default=None, context=None): default = default or {} context = context or {} - if not default.get('split_from') and not context.get('split'): + if not default.get('split_from'): #we don't want to propagate the link to the purchase order line except in case of move split default['purchase_line_id'] = False return super(stock_move, self).copy(cr, uid, id, default, context) @@ -102,7 +102,7 @@ class stock_picking(osv.osv): _columns = { 'reception_to_invoice': fields.function(_get_to_invoice, type='boolean', string='Invoiceable on incoming shipment?', - help='Does the picking contains some moves related to a purchase order invoiceable on the reception?', + help='Does the picking contains some moves related to a purchase order invoiceable on the receipt?', store={ 'stock.move': (_get_picking_to_recompute, ['purchase_line_id', 'picking_id'], 10), }), diff --git a/addons/purchase/stock_view.xml b/addons/purchase/stock_view.xml index acdecd2d400..174dc99d4f5 100644 --- a/addons/purchase/stock_view.xml +++ b/addons/purchase/stock_view.xml @@ -72,10 +72,10 @@

    Click to create a new incoming shipment.

    - Here you can track all the product receptions of purchase + Here you can track all the product receipts of purchase orders where the invoicing is "Based on Incoming Shipments", and for which you have not received a supplier invoice yet. - You can generate a supplier invoice based on those receptions. + You can generate a supplier invoice based on those receipts.

    diff --git a/addons/sale/sale.py b/addons/sale/sale.py index d115e81b601..1dbbaae4346 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -1287,9 +1287,8 @@ class product_template(osv.Model): product_ids = [] for template in self.browse(cr, uid, ids, context=context): product_ids += [x.id for x in template.product_variant_ids] - result = mod_obj.get_object_reference(cr, uid, 'sale', 'action_order_line_product_tree') - id = result and result[1] or False - result = act_obj.read(cr, uid, [id], context=context)[0] + result = mod_obj.xmlid_to_res_id(cr, uid, 'sale.action_order_line_product_tree',raise_if_not_found=True) + result = act_obj.read(cr, uid, [result], context=context)[0] result['domain'] = "[('product_id','in',[" + ','.join(map(str, product_ids)) + "])]" return result diff --git a/addons/stock/doc/stock.rst b/addons/stock/doc/stock.rst index 42b9c79b8a5..c43729889a9 100644 --- a/addons/stock/doc/stock.rst +++ b/addons/stock/doc/stock.rst @@ -27,7 +27,7 @@ To satisfy the need for a counterpart to each stock movement, the software suppo * Partner locations (suppliers and customers), * Virtual locations as counterparts for production, inventory and scrap. -Partner locations represent your customers' and suppliers' stocks. To reconcile them with your accounts, these stores play the role of third-party accounts. Reception from a supplier can be shown by the movement of goods from a partner location to a physical location in your own company. As you see, supplier locations usually show negative stocks and customer locations usually show positive stocks. +Partner locations represent your customers' and suppliers' stocks. To reconcile them with your accounts, these stores play the role of third-party accounts. Receipt from a supplier can be shown by the movement of goods from a partner location to a physical location in your own company. As you see, supplier locations usually show negative stocks and customer locations usually show positive stocks. Virtual locations as counterparts for production are used in manufacturing operations. Manufacturing is characterized by the consumption of raw materials and the production of finished products. Virtual locations are used for the counterparts of these two operations. diff --git a/addons/stock/product.py b/addons/stock/product.py index b4afce43420..a0dbf68bb6f 100644 --- a/addons/stock/product.py +++ b/addons/stock/product.py @@ -175,7 +175,7 @@ class product_product(osv.osv): return res _columns = { - 'reception_count': fields.function(_stock_move_count, string="Reception", type='integer', multi='pickings'), + 'reception_count': fields.function(_stock_move_count, string="Receipt", type='integer', multi='pickings'), 'delivery_count': fields.function(_stock_move_count, string="Delivery", type='integer', multi='pickings'), 'qty_available_text': fields.function(_product_available_text, type='char'), 'qty_available': fields.function(_product_available, multi='qty_available', @@ -244,7 +244,7 @@ class product_product(osv.osv): if fields: if location_info.usage == 'supplier': if fields.get('virtual_available'): - res['fields']['virtual_available']['string'] = _('Future Receptions') + res['fields']['virtual_available']['string'] = _('Future Receipts') if fields.get('qty_available'): res['fields']['qty_available']['string'] = _('Received Qty') @@ -392,9 +392,8 @@ class product_template(osv.osv): 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 = mod_obj.xmlid_to_res_id(cr, uid, 'stock.action_routes_form', raise_if_not_found=True) + result = act_obj.read(cr, uid, [result], context=context)[0] result['domain'] = "[('id','in',[" + ','.join(map(str, route_ids)) + "])]" return result @@ -405,24 +404,23 @@ class product_template(osv.osv): products += [x.id for x in prodtmpl.product_variant_ids] return products - def _get_act_window_dict(self, cr, uid, module, name, context=None): + def _get_act_window_dict(self, cr, uid, name, context=None): mod_obj = self.pool.get('ir.model.data') act_obj = self.pool.get('ir.actions.act_window') - result = mod_obj.get_object_reference(cr, uid, module, name) - id = result and result[1] or False - result = act_obj.read(cr, uid, [id], context=context)[0] + result = mod_obj.xmlid_to_res_id(cr, uid, name, raise_if_not_found=True) + result = act_obj.read(cr, uid, [result], context=context)[0] return result def action_open_quants(self, cr, uid, ids, context=None): products = self._get_products(cr, uid, ids, context=context) - result = self._get_act_window_dict(cr, uid, 'stock','product_open_quants', context=context) + result = self._get_act_window_dict(cr, uid, 'stock.product_open_quants', context=context) result['domain'] = "[('product_id','in',[" + ','.join(map(str, products)) + "])]" result['context'] = "{'search_default_locationgroup': 1, 'search_default_internal_loc': 1}" return result def action_view_orderpoints(self, cr, uid, ids, context=None): products = self._get_products(cr, uid, ids, context=context) - result = self._get_act_window_dict(cr, uid, 'stock','product_open_orderpoint', context=context) + result = self._get_act_window_dict(cr, uid, 'stock.product_open_orderpoint', context=context) result['domain'] = "[('product_id','in',[" + ','.join(map(str, products)) + "])]" result['context'] = "{}" return result @@ -430,7 +428,7 @@ class product_template(osv.osv): def action_view_stock_moves(self, cr, uid, ids, context=None): products = self._get_products(cr, uid, ids, context=context) - result = self._get_act_window_dict(cr, uid, 'stock','act_product_stock_move_open', context=context) + result = self._get_act_window_dict(cr, uid, 'stock.act_product_stock_move_open', context=context) result['domain'] = "[('product_id','in',[" + ','.join(map(str, products)) + "])]" result['context'] = "{}" return result diff --git a/addons/stock/product_view.xml b/addons/stock/product_view.xml index 715186e3a3c..df89820ff6d 100644 --- a/addons/stock/product_view.xml +++ b/addons/stock/product_view.xml @@ -32,20 +32,20 @@ - Receptions + Receipts stock.move ir.actions.act_window form tree,form - +

    - Click to register a reception for this product. + Click to register a receipt for this product.

    - Here you will find the history of all receptions related to - this product, as well as all future receptions you are waiting + Here you will find the history of all receipts related to + this product, as well as all future receipts you are waiting from your suppliers.

    @@ -57,7 +57,7 @@ ir.actions.act_window form tree,form - + @@ -193,10 +193,10 @@ - Receptions + Receipts - Reception + Receipt Deliveries diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 8732ad93593..7cd4ac19000 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2770,7 +2770,7 @@ class stock_warehouse(osv.osv): 'in_type_id': fields.many2one('stock.picking.type', 'In Type'), 'int_type_id': fields.many2one('stock.picking.type', 'Internal Type'), 'crossdock_route_id': fields.many2one('stock.location.route', 'Crossdock Route'), - 'reception_route_id': fields.many2one('stock.location.route', 'Reception Route'), + 'reception_route_id': fields.many2one('stock.location.route', 'Receipt Route'), 'delivery_route_id': fields.many2one('stock.location.route', 'Delivery Route'), 'resupply_from_wh': fields.boolean('Resupply From Other Warehouses'), 'resupply_wh_ids': fields.many2many('stock.warehouse', 'stock_wh_resupply_table', 'supplied_wh_id', 'supplier_wh_id', 'Resupply Warehouses'), @@ -3072,7 +3072,7 @@ class stock_warehouse(osv.osv): for pull_rule in pull_rules_list: pull_obj.create(cr, uid, vals=pull_rule, context=context) - #update reception route and rules: unlink the existing rules of the warehouse reception route and recreate it + #update receipt route and rules: unlink the existing rules of the warehouse receipt route and recreate it pull_obj.unlink(cr, uid, [pu.id for pu in warehouse.reception_route_id.pull_ids], context=context) push_obj.unlink(cr, uid, [pu.id for pu in warehouse.reception_route_id.push_ids], context=context) route_name, values = routes_dict[new_reception_step] @@ -3082,7 +3082,7 @@ class stock_warehouse(osv.osv): for push_rule in push_rules_list: push_obj.create(cr, uid, vals=push_rule, context=context) for pull_rule in pull_rules_list: - #all pull rules in reception route are mto, because we don't want to wait for the scheduler to trigger an orderpoint on input location + #all pull rules in receipt route are mto, because we don't want to wait for the scheduler to trigger an orderpoint on input location pull_rule['procure_method'] = 'make_to_order' pull_obj.create(cr, uid, vals=pull_rule, context=context) @@ -3136,7 +3136,7 @@ class stock_warehouse(osv.osv): max_sequence = max_sequence and max_sequence[0]['sequence'] or 0 in_type_id = picking_type_obj.create(cr, uid, vals={ - 'name': _('Receptions'), + 'name': _('Receipts'), 'warehouse_id': warehouse.id, 'code': 'incoming', 'sequence_id': in_seq_id, @@ -3258,9 +3258,9 @@ class stock_warehouse(osv.osv): customer_loc, supplier_loc = self._get_partner_locations(cr, uid, ids, context=context) return { - 'one_step': (_('Reception in 1 step'), []), - 'two_steps': (_('Reception in 2 steps'), [(warehouse.wh_input_stock_loc_id, warehouse.lot_stock_id, warehouse.int_type_id.id)]), - 'three_steps': (_('Reception in 3 steps'), [(warehouse.wh_input_stock_loc_id, warehouse.wh_qc_stock_loc_id, warehouse.int_type_id.id), (warehouse.wh_qc_stock_loc_id, warehouse.lot_stock_id, warehouse.int_type_id.id)]), + 'one_step': (_('Receipt in 1 step'), []), + 'two_steps': (_('Receipt in 2 steps'), [(warehouse.wh_input_stock_loc_id, warehouse.lot_stock_id, warehouse.int_type_id.id)]), + 'three_steps': (_('Receipt in 3 steps'), [(warehouse.wh_input_stock_loc_id, warehouse.wh_qc_stock_loc_id, warehouse.int_type_id.id), (warehouse.wh_qc_stock_loc_id, warehouse.lot_stock_id, warehouse.int_type_id.id)]), 'crossdock': (_('Cross-Dock'), [(warehouse.wh_input_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.int_type_id.id), (warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id.id)]), 'ship_only': (_('Ship Only'), [(warehouse.lot_stock_id, customer_loc, warehouse.out_type_id.id)]), 'pick_ship': (_('Pick + Ship'), [(warehouse.lot_stock_id, warehouse.wh_output_stock_loc_id, warehouse.pick_type_id.id), (warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id.id)]), @@ -3313,7 +3313,7 @@ class stock_warehouse(osv.osv): def _check_reception_resupply(self, cr, uid, warehouse, new_location, context=None): """ - Will check if the resupply routes to this warehouse follow the changes of number of reception steps + Will check if the resupply routes to this warehouse follow the changes of number of receipt steps """ #Check routes that are being delivered by this warehouse and change the rule coming from transit location route_obj = self.pool.get("stock.location.route") diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index c322739a874..b53f922e5e0 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -1155,7 +1155,7 @@ @@ -1203,7 +1203,7 @@
    - + Incoming Products stock.move ir.actions.act_window @@ -1214,7 +1214,7 @@ {'product_receive': True, 'search_default_future': True}

    - Click to register a product reception. + Click to register a product receipt.

    Here you can receive individual products, no matter what purchase order or picking order they come from. You will find diff --git a/addons/stock/test/packing.yml b/addons/stock/test/packing.yml index 70e844ca4eb..b889d93419d 100644 --- a/addons/stock/test/packing.yml +++ b/addons/stock/test/packing.yml @@ -52,7 +52,7 @@ new_pack1 = stock_pack.create(cr, uid, {'product_id': ref('product1'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick1'), 'lot_id': lot_a, 'result_package_id': package2, 'product_qty': 120, 'location_id': ref('stock_location_suppliers'), 'location_dest_id': ref('stock_location_stock')}, context=context) new_pack2 = stock_pack.create(cr, uid, {'product_id': ref('product1'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick1'), 'result_package_id': package3, 'product_qty': 60, 'location_id': ref('stock_location_suppliers'), 'location_dest_id': ref('stock_location_stock')}, context=context) - - Transfer the reception + Transfer the receipt - !python {model: stock.picking}: | self.do_transfer(cr, uid, [ref('pick1')], context=context) diff --git a/addons/stock/test/packingneg.yml b/addons/stock/test/packingneg.yml index a42c9bb12e3..1bd642d3d44 100644 --- a/addons/stock/test/packingneg.yml +++ b/addons/stock/test/packingneg.yml @@ -52,7 +52,7 @@ new_pack1 = stock_pack.create(cr, uid, {'product_id': ref('product_neg'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick_neg'), 'lot_id': lot_a, 'result_package_id': package2, 'product_qty': 120, 'location_id': ref('stock_location_suppliers'), 'location_dest_id': ref('stock_location_stock')}, context=context) new_pack2 = stock_pack.create(cr, uid, {'product_id': ref('product_neg'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick_neg'), 'result_package_id': package3, 'product_qty': 60, 'location_id': ref('stock_location_suppliers'), 'location_dest_id': ref('stock_location_stock')}, context=context) - - Transfer the reception + Transfer the receipt - !python {model: stock.picking}: | self.do_transfer(cr, uid, [ref('pick_neg')], context=context) diff --git a/addons/stock/test/shipment.yml b/addons/stock/test/shipment.yml index 2d520918bac..46abd3a38f9 100644 --- a/addons/stock/test/shipment.yml +++ b/addons/stock/test/shipment.yml @@ -55,7 +55,7 @@ }) backorder.do_transfer() - - I check incomming shipment after reception. + I check incomming shipment after receipt. - !python {model: stock.picking}: | shipment = self.browse(cr, uid, self.search(cr, uid, [('backorder_id', '=', ref("incomming_shipment"))]))[0] diff --git a/addons/stock/test/wiseoperator.yml b/addons/stock/test/wiseoperator.yml index cfe1180b545..78721d5f58b 100644 --- a/addons/stock/test/wiseoperator.yml +++ b/addons/stock/test/wiseoperator.yml @@ -36,7 +36,7 @@ stock_pack.write(cr, uid, record.pack_operation_ids[0].id, {'result_package_id': package1, 'product_qty': 4, 'location_dest_id': ref('stock_location_components')}) new_pack1 = stock_pack.create(cr, uid, {'product_id': ref('product_wise'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick1'), 'product_qty': 6.0, 'location_id': ref('stock_location_suppliers'), 'location_dest_id': ref('stock_location_14')}, context=context) - - Transfer the reception + Transfer the receipt - !python {model: stock.picking}: | self.do_transfer(cr, uid, [ref('pick1')], context=context) diff --git a/addons/stock_dropshipping/test/cancellation_propagated.yml b/addons/stock_dropshipping/test/cancellation_propagated.yml index 71f5e75385d..30a4ae338cc 100644 --- a/addons/stock_dropshipping/test/cancellation_propagated.yml +++ b/addons/stock_dropshipping/test/cancellation_propagated.yml @@ -1,5 +1,5 @@ - - I first create a warehouse with pick-pack-ship and reception in 2 steps + I first create a warehouse with pick-pack-ship and receipt in 2 steps - !record {model: stock.warehouse, id: wh_pps}: name: WareHouse PickPackShip diff --git a/addons/stock_dropshipping/test/lifo_price.yml b/addons/stock_dropshipping/test/lifo_price.yml index a9b641bab77..4d0634e0b7a 100644 --- a/addons/stock_dropshipping/test/lifo_price.yml +++ b/addons/stock_dropshipping/test/lifo_price.yml @@ -57,7 +57,7 @@ !assert {model: purchase.order, id: purchase_order_lifo1}: - state == 'approved' - - Process the reception of purchase order 1 + Process the receipt of purchase order 1 - !python {model: stock.picking}: | order = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_lifo1"), context=context).picking_ids[0] @@ -72,7 +72,7 @@ - !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_lifo2} - - Process the reception of purchase order 2 + Process the receipt of purchase order 2 - !python {model: stock.picking}: | order = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_lifo2"), context=context).picking_ids[0] @@ -81,7 +81,7 @@ Check the standard price should not have changed - !python {model: product.product}: | - assert self.browse(cr, uid, ref("product_lifo_icecream")).standard_price == 70.0, 'Standard price as lifo price of second reception incorrect!' + assert self.browse(cr, uid, ref("product_lifo_icecream")).standard_price == 70.0, 'Standard price as lifo price of second receipt incorrect!' - Let us send some goods - diff --git a/addons/stock_dropshipping/test/stock_combi.yml b/addons/stock_dropshipping/test/stock_combi.yml index aa3a991838e..d4fb786e109 100644 --- a/addons/stock_dropshipping/test/stock_combi.yml +++ b/addons/stock_dropshipping/test/stock_combi.yml @@ -1,5 +1,5 @@ - - Create a warehouse and set it as having a 3-step delivery flow (Pick > Pack > Ship) and a 2-step reception route + Create a warehouse and set it as having a 3-step delivery flow (Pick > Pack > Ship) and a 2-step receipt route - - Create product Combi1 that is MTO / purchase and provide a supplier for it From 28ad04a4de10adabf6298392d6cc160caa776293 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 16 Jul 2014 13:50:56 +0200 Subject: [PATCH 034/270] [IMP] Only show number of variants when more than one + bug cursor Compute Minimum Stock Rules --- addons/product/product_view.xml | 2 +- addons/stock/wizard/orderpoint_procurement.py | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index 106cc04a598..007f2c08741 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -270,7 +270,7 @@ - + Variants

    diff --git a/addons/stock/wizard/orderpoint_procurement.py b/addons/stock/wizard/orderpoint_procurement.py index f898dac4305..4a591bc92d5 100644 --- a/addons/stock/wizard/orderpoint_procurement.py +++ b/addons/stock/wizard/orderpoint_procurement.py @@ -26,10 +26,11 @@ import threading from openerp.osv import fields,osv +from openerp.api import Environment class procurement_compute(osv.osv_memory): _name = 'procurement.orderpoint.compute' - _description = 'Automatic Order Point' + _description = 'Compute Minimum Stock Rules' def _procure_calculation_orderpoint(self, cr, uid, ids, context=None): @@ -40,16 +41,16 @@ class procurement_compute(osv.osv_memory): @param ids: List of IDs selected @param context: A standard dictionary """ - proc_obj = self.pool.get('procurement.order') - #As this function is in a new thread, I need to open a new cursor, because the old one may be closed - new_cr = self.pool.cursor() - user_obj = self.pool.get('res.users') - user = user_obj.browse(new_cr, uid, uid, context=context) - for comp in user.company_ids: - proc_obj._procure_orderpoint_confirm(new_cr, uid, use_new_cursor=new_cr.dbname, company_id = comp.id, context=context) - #close the new cursor - new_cr.close() - return {} + with Environment.manage(): + proc_obj = self.pool.get('procurement.order') + #As this function is in a new thread, I need to open a new cursor, because the old one may be closed + new_cr = self.pool.cursor() + user_obj = self.pool.get('res.users') + company_id = user_obj.browse(new_cr, uid, uid, context=context).company_id.id + proc_obj._procure_orderpoint_confirm(new_cr, uid, use_new_cursor=new_cr.dbname, company_id = company_id, context=context) + #close the new cursor + new_cr.close() + return {} def procure_calculation(self, cr, uid, ids, context=None): """ @@ -59,6 +60,7 @@ class procurement_compute(osv.osv_memory): @param ids: List of IDs selected @param context: A standard dictionary """ + threaded_calculation = threading.Thread(target=self._procure_calculation_orderpoint, args=(cr, uid, ids, context)) threaded_calculation.start() return {'type': 'ir.actions.act_window_close'} From 7286f4e42448bd1487ef329c7ff6c8a212f6a3bd Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 16 Jul 2014 14:07:43 +0200 Subject: [PATCH 035/270] [FIX] ensure Field strings are decoded before comparing them to one another fixes #773 --- addons/web/controllers/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index b74efc9c452..16c245fc31a 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1330,7 +1330,7 @@ class Export(http.Controller): fields['.id'] = fields.pop('id', {'string': 'ID'}) fields_sequence = sorted(fields.iteritems(), - key=lambda field: field[1].get('string', '')) + key=lambda field: openerp.tools.ustr(field[1].get('string', ''))) records = [] for field_name, field in fields_sequence: From fd0c52550af8b84301fcf10717a37115714880e6 Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Wed, 16 Jul 2014 14:59:22 +0200 Subject: [PATCH 036/270] base_gengo: [FIX] change http status code for gengo_callback because status 100 make response very slow [FIX] Cron for gengo was always in mode 'do missed' what we don't want, replace 0 by False [FIX] Remove unused regexp to find view_id from js which was breaking the count words --- addons/base_gengo/controller/gengo_callback.py | 9 +++++---- addons/base_gengo/gengo_sync_schedular_data.xml | 4 ++-- addons/website_gengo/static/src/js/website_gengo.js | 1 - 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/addons/base_gengo/controller/gengo_callback.py b/addons/base_gengo/controller/gengo_callback.py index 44fa9b9390a..328be0ae357 100644 --- a/addons/base_gengo/controller/gengo_callback.py +++ b/addons/base_gengo/controller/gengo_callback.py @@ -18,17 +18,18 @@ class website_gengo(http.Controller): @http.route('/website/gengo_callback', type='http', auth='none') def gengo_callback(self, **post): + print "IN website/gengo_callback" cr, uid, context = request.cr, openerp.SUPERUSER_ID, request.context translation_pool = request.registry['ir.translation'] if post and post.get('job') and post.get('pgk'): if post.get('pgk') != self.get_gengo_key(cr): - return Response("Bad authentication - 403/412", status=412) + return Response("Bad authentication", status=104) job = json.loads(post['job'], 'utf-8') tid = job.get('custom_data', False) if (job.get('status') == 'approved') and tid: term = translation_pool.browse(cr, uid, int(tid), context=context) if term.src != job.get('body_src'): - return Response("Text Altered - Not saved", status=100) + return Response("Text Altered - Not saved", status=418) domain = [ '|', ('id', "=", int(tid)), @@ -49,5 +50,5 @@ class website_gengo(http.Controller): translation_pool.write(cr, uid, all_ir_tanslations, vals, context=context) return Response("OK", status=200) else: - return Response("No terms found", status=104) - return Response("Not saved", status=100) + return Response("No terms found", status=412) + return Response("Not saved", status=418) diff --git a/addons/base_gengo/gengo_sync_schedular_data.xml b/addons/base_gengo/gengo_sync_schedular_data.xml index e916489e5f7..0af74d8ccf1 100644 --- a/addons/base_gengo/gengo_sync_schedular_data.xml +++ b/addons/base_gengo/gengo_sync_schedular_data.xml @@ -8,7 +8,7 @@ 6 hours -1 - 0 + @@ -21,7 +21,7 @@ 6 hours -1 - 0 + diff --git a/addons/website_gengo/static/src/js/website_gengo.js b/addons/website_gengo/static/src/js/website_gengo.js index 4b7d9c34b4f..60aecb3ff45 100644 --- a/addons/website_gengo/static/src/js/website_gengo.js +++ b/addons/website_gengo/static/src/js/website_gengo.js @@ -89,7 +89,6 @@ }, translation_gengo_info: function () { var repr = $(document.documentElement).data('mainObject'); - var view_id = repr.match(/.+\((.+), (\d+)\)/)[2]; var translated_ids = []; $('.oe_translatable_text').not(".oe_translatable_inprogress").each(function(){ translated_ids.push($(this).attr('data-oe-translation-id')); From b1f1596aef5e8968d22abb63f9e7bf8da72b009b Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 16 Jul 2014 16:22:34 +0200 Subject: [PATCH 037/270] [FIX] "prefetching" removing even the records specifically asked for 16d6744 turns out to not be great, because it filters out the todos for prefetched fields (rather than those just for the field being asked) there are situations where it ends up not fetching the records it was originally asked for and breaks a bunch of stuff e.g. unreconcile line in bank statements Force the ids explicitly asked for back in the fetched set, so that the prefetch is at most a noop, rco will have to take an actual look at it. --- openerp/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/models.py b/openerp/models.py index 519290327dd..7b17c366ec6 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -3182,7 +3182,7 @@ class BaseModel(object): for field in (self._fields[name] for name in field_names): prefetch.update(self._in_cache_without(field).ids) todo.update(self.env.todo.get(field, empty).ids) - records = self.browse(prefetch - todo) + records = self.browse(prefetch - todo | set(self.ids)) result = [] for sub_ids in cr.split_for_in_conditions(records.ids): From 6ac1deccf64e35de062ed960f84bd8a794d310d4 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 16 Jul 2014 22:51:43 +0200 Subject: [PATCH 038/270] [FIX] mail, website_mail: specific mailing-list footer + better unsubscribe option - remove the default footer for mail.group messages, replace with specific footer with archive and unsubscribe link - remove the automatic addition of user signature in mail.group messages, as many of them will be posted via the mail gateway and already contain a user signature. - make it easier to unsubscribe even when not logged in, as followers who have not signed up will have no way to login short of signing up. - remove tests looking for user signature in mail.group posts --- addons/mail/mail_followers.py | 9 ++--- addons/mail/tests/test_mail_features.py | 8 ----- addons/website_mail/static/src/js/follow.js | 24 +++++++------ addons/website_mail/views/website_mail.xml | 3 +- .../website_mail_group/models/mail_group.py | 36 ++++++++++++++++++- .../views/website_mail_group.xml | 3 ++ 6 files changed, 59 insertions(+), 24 deletions(-) diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py index 1e2c6c8d9e6..a4eee04a69c 100644 --- a/addons/mail/mail_followers.py +++ b/addons/mail/mail_followers.py @@ -138,10 +138,10 @@ class mail_notification(osv.Model): company = "%s" % (website_url, user.company_id.name) else: company = user.company_id.name - sent_by = _('Sent by %(company)s using %(openerp)s.') + sent_by = _('Sent by %(company)s using %(odoo)s.') signature_company = '%s' % (sent_by % { 'company': company, - 'openerp': "OpenERP" + 'odoo': "Odoo" }) footer = tools.append_content_to_html(footer, signature_company, plaintext=False, container_tag='div') @@ -174,8 +174,9 @@ class mail_notification(osv.Model): # compute email body (signature, company data) body_html = message.body - user_id = message.author_id and message.author_id.user_ids and message.author_id.user_ids[0] and message.author_id.user_ids[0].id or None - if user_signature: + # add user signature except for mail groups, where users are usually adding their own signatures already + if user_signature and message.model != 'mail.group': + user_id = message.author_id and message.author_id.user_ids and message.author_id.user_ids[0] and message.author_id.user_ids[0].id or None signature_company = self.get_signature_footer(cr, uid, user_id, res_model=message.model, res_id=message.res_id, context=context) body_html = tools.append_content_to_html(body_html, signature_company, plaintext=False, container_tag='div') diff --git a/addons/mail/tests/test_mail_features.py b/addons/mail/tests/test_mail_features.py index bea80b729a4..ff9ce5d82cc 100644 --- a/addons/mail/tests/test_mail_features.py +++ b/addons/mail/tests/test_mail_features.py @@ -468,14 +468,10 @@ class test_mail(TestMail): 'message_post: notification email subject incorrect') self.assertIn(_body1, sent_email['body'], 'message_post: notification email body incorrect') - self.assertIn(user_raoul.signature, sent_email['body'], - 'message_post: notification email body should contain the sender signature') self.assertIn('Pigs rules', sent_email['body_alternative'], 'message_post: notification email body alternative should contain the body') self.assertNotIn('

    ', sent_email['body_alternative'], 'message_post: notification email body alternative still contains html') - self.assertIn(user_raoul.signature, sent_email['body_alternative'], - 'message_post: notification email body alternative should contain the sender signature') self.assertFalse(sent_email['references'], 'message_post: references should be False when sending a message that is not a reply') @@ -539,14 +535,10 @@ class test_mail(TestMail): 'message_post: notification email subject incorrect') self.assertIn(html_sanitize(_body2), sent_email['body'], 'message_post: notification email does not contain the body') - self.assertIn(user_raoul.signature, sent_email['body'], - 'message_post: notification email body should contain the sender signature') self.assertIn('Pigs rocks', sent_email['body_alternative'], 'message_post: notification email body alternative should contain the body') self.assertNotIn('

    ', sent_email['body_alternative'], 'message_post: notification email body alternative still contains html') - self.assertIn(user_raoul.signature, sent_email['body_alternative'], - 'message_post: notification email body alternative should contain the sender signature') self.assertIn(msg_message_id, sent_email['references'], 'message_post: notification email references lacks parent message message_id') # Test: attachments + download diff --git a/addons/website_mail/static/src/js/follow.js b/addons/website_mail/static/src/js/follow.js index 18c36378328..a8b351efa6c 100644 --- a/addons/website_mail/static/src/js/follow.js +++ b/addons/website_mail/static/src/js/follow.js @@ -40,17 +40,21 @@ } this.$target.removeClass('has-error'); - openerp.jsonRpc('/website_mail/follow', 'call', { - 'id': +this.$target.data('id'), - 'object': this.$target.data('object'), - 'message_is_follower': this.$target.attr("data-follow") || "off", - 'email': $email.length ? $email.val() : false, - }).then(function (follow) { - self.toggle_subscription(follow, self.email); - }); + var email = $email.length ? $email.val() : false; + if (email) { + openerp.jsonRpc('/website_mail/follow', 'call', { + 'id': +this.$target.data('id'), + 'object': this.$target.data('object'), + 'message_is_follower': this.$target.attr("data-follow") || "off", + 'email': email, + }).then(function (follow) { + self.toggle_subscription(follow, email); + }); + } }, toggle_subscription: function(follow, email) { console.log(follow, email); + follow = follow || (!email && this.$target.attr('data-unsubscribe')); if (follow) { this.$target.find(".js_follow_btn").addClass("hidden"); this.$target.find(".js_unfollow_btn").removeClass("hidden"); @@ -60,8 +64,8 @@ this.$target.find(".js_unfollow_btn").addClass("hidden"); } this.$target.find('input.js_follow_email') - .val(email ? email : "") - .attr("disabled", follow || (email.length && this.is_user) ? "disabled" : false); + .val(email || "") + .attr("disabled", email && (follow || this.is_user) ? "disabled" : false); this.$target.attr("data-follow", follow ? 'on' : 'off'); }, }); diff --git a/addons/website_mail/views/website_mail.xml b/addons/website_mail/views/website_mail.xml index 3b6d1bb96d6..8e365c1aaf5 100644 --- a/addons/website_mail/views/website_mail.xml +++ b/addons/website_mail/views/website_mail.xml @@ -5,7 +5,8 @@