From bcc08ee4227048e05089bac2d5b8e1d5539db82c Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Fri, 19 Sep 2014 11:06:17 +0200 Subject: [PATCH 01/25] [FIX] event: translate templates into user language When sending an email of the registration/confirmation, the context was lost and the tempate sticked in en_US. With the patch, the template will use the current user's language. --- addons/event/event.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/event/event.py b/addons/event/event.py index 2b2f9377773..937884fd087 100644 --- a/addons/event/event.py +++ b/addons/event/event.py @@ -370,7 +370,7 @@ class event_registration(osv.osv): else: template_id = registration.event_id.email_registration_id.id if template_id: - mail_message = self.pool.get('email.template').send_mail(cr,uid,template_id,registration.id) + self.pool.get('email.template').send_mail(cr,uid,template_id,registration.id, context=context) return True def mail_user_confirm(self, cr, uid, ids, context=None): @@ -380,7 +380,7 @@ class event_registration(osv.osv): for registration in self.browse(cr, uid, ids, context=context): template_id = registration.event_id.email_confirmation_id.id if template_id: - mail_message = self.pool.get('email.template').send_mail(cr,uid,template_id,registration.id) + self.pool.get('email.template').send_mail(cr,uid,template_id,registration.id, context=context) return True def onchange_contact_id(self, cr, uid, ids, contact, partner, context=None): From 6b9b948a3c655b3d8961d2138d75d59f4bdb5de1 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Fri, 19 Sep 2014 11:34:41 +0200 Subject: [PATCH 02/25] [IMP] stock: display translated selection value in message When a user tried to delete a done or canceled picking, the error messages used to display the key of the selection field ('done' or 'cancel') which was surprising in other languages than English. This patch takes the string value of the selection field, keeping the context to get the translated value (opw 613068) --- addons/stock/stock.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index e3e7d539771..14d60c2b56e 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1204,7 +1204,9 @@ class stock_picking(osv.osv): context = {} for pick in self.browse(cr, uid, ids, context=context): if pick.state in ['done','cancel']: - raise osv.except_osv(_('Error!'), _('You cannot remove the picking which is in %s state!')%(pick.state,)) + # retrieve the string value of field in user's language + state = dict(self.fields_get(cr, uid, context=context)['state']['selection']).get(pick.state, pick.state) + raise osv.except_osv(_('Error!'), _('You cannot remove the picking which is in %s state!')%(state,)) else: ids2 = [move.id for move in pick.move_lines] ctx = context.copy() From 9b87d6f371cc936293e0e728fe6d17cbdcb512a5 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Fri, 19 Sep 2014 14:29:43 +0200 Subject: [PATCH 03/25] [FIX] purchase: keep fallback behaviour that was removed at rev 8973217 If no valid invocie is found on the purchase order line, use the price and currency of the line (coherent with help message) --- addons/purchase/stock.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index fc9c5cc7940..e8ea8f8f067 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -128,14 +128,23 @@ class stock_partial_picking(osv.osv_memory): # Overridden to inject the purchase price as true 'cost price' when processing # incoming pickings. def _product_cost_for_average_update(self, cr, uid, move): - if move.purchase_line_id and move.purchase_line_id and move.purchase_line_id.invoice_lines: - cost = move.price_unit - for inv_line in move.purchase_line_id.invoice_lines: - if inv_line.invoice_id.state not in ('draft', 'cancel'): - inv_currency = inv_line.invoice_id.currency_id.id - company_currency = inv_line.invoice_id.company_id.currency_id.id - cost = self.pool.get('res.currency').compute(cr, uid, inv_currency, company_currency, inv_line.price_unit, round=False, context={'date': inv_line.invoice_id.date_invoice}) - return {'cost': cost, 'currency': company_currency} + purchase_line = move.purchase_line_id + if move.picking_id.purchase_id and purchase_line: + if any([x.invoice_id.state not in ('draft', 'cancel') for x in purchase_line.invoice_lines]): + # use price set on validated invoices + cost = move.price_unit + for inv_line in purchase_line.invoice_lines: + if inv_line.invoice_id.state not in ('draft', 'cancel'): + inv_currency = inv_line.invoice_id.currency_id.id + company_currency = inv_line.invoice_id.company_id.currency_id.id + cost = self.pool.get('res.currency').compute(cr, uid, inv_currency, company_currency, inv_line.price_unit, round=False, context={'date': inv_line.invoice_id.date_invoice}) + return {'cost': cost, 'currency': company_currency} + else: + # use price set on the purchase order + pur_currency = purchase_line.order_id.currency_id.id + company_currency = purchase_line.company_id.currency_id.id + cost = self.pool.get('res.currency').compute(cr, uid, pur_currency, company_currency, purchase_line.price_unit, round=False, context={'date': purchase_line.date_order}) + return {'cost': cost, 'currency': company_currency} return super(stock_partial_picking, self)._product_cost_for_average_update(cr, uid, move) def __get_help_text(self, cursor, user, picking_id, context=None): From 4c031b95d03eff0b8c2a365139a78f5f62244d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 22 Sep 2014 11:18:41 +0200 Subject: [PATCH 04/25] [FIX] correctly forward context in graph view the new graph view silently ignored the context when doing its rpc read_group. Usually, it's not really a problem, which is why it is only now being fixed, but some models actually use the context in read_group. (for ex, account_entries_report) --- addons/web_graph/static/src/js/graph_view.js | 1 + addons/web_graph/static/src/js/pivot_table.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/addons/web_graph/static/src/js/graph_view.js b/addons/web_graph/static/src/js/graph_view.js index 25a1f932cc4..b4b2fb38f06 100644 --- a/addons/web_graph/static/src/js/graph_view.js +++ b/addons/web_graph/static/src/js/graph_view.js @@ -86,6 +86,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ col_group_by = groupbys.col_group_by; if (!this.graph_widget) { + this.widget_config.context = context; if (group_by.length) { this.widget_config.row_groupby = group_by; } diff --git a/addons/web_graph/static/src/js/pivot_table.js b/addons/web_graph/static/src/js/pivot_table.js index 7de629a95c2..2d68cb43932 100644 --- a/addons/web_graph/static/src/js/pivot_table.js +++ b/addons/web_graph/static/src/js/pivot_table.js @@ -13,6 +13,7 @@ openerp.web_graph.PivotTable = openerp.web.Class.extend({ init: function (model, domain, fields, options) { this.cells = []; this.domain = domain; + this.context = options.context; this.no_data = true; this.model = model; this.fields = fields; @@ -379,6 +380,7 @@ openerp.web_graph.PivotTable = openerp.web.Class.extend({ return this.model.query(field_ids) .filter(domain) + .context(this.context) .group_by(groupby.field) .then(function (results) { var groups = _.filter(results, function (group) { From ee4c67dc10acf168e446d3cd8b56eb07fa1fef2b Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Mon, 22 Sep 2014 12:16:46 +0200 Subject: [PATCH 05/25] [FIX] account_voucher: convertion rate of purchase receipt The conversion of currencies in vouchers are made based on payment_rate_currency_id field (instead of usual currency_id). This field was not present in the purchase view and not correctly set when we change the journal (and thus the currency) which was leading to rates of 1. With a rate of 1, the amount is equal to the currency amount (which is rarely correct). opw 611254 --- addons/account_voucher/account_voucher.py | 2 +- addons/account_voucher/voucher_sales_purchase_view.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py index 4b19ae6b824..25046a68ed6 100644 --- a/addons/account_voucher/account_voucher.py +++ b/addons/account_voucher/account_voucher.py @@ -918,7 +918,7 @@ class account_voucher(osv.osv): currency_id = journal.currency.id else: currency_id = journal.company_id.currency_id.id - vals['value'].update({'currency_id': currency_id}) + vals['value'].update({'currency_id': currency_id, 'payment_rate_currency_id': currency_id}) #in case we want to register the payment directly from an invoice, it's confusing to allow to switch the journal #without seeing that the amount is expressed in the journal currency, and not in the invoice currency. So to avoid #this common mistake, we simply reset the amount to 0 if the currency is not the invoice currency. diff --git a/addons/account_voucher/voucher_sales_purchase_view.xml b/addons/account_voucher/voucher_sales_purchase_view.xml index 2dd53832529..20ac768258c 100644 --- a/addons/account_voucher/voucher_sales_purchase_view.xml +++ b/addons/account_voucher/voucher_sales_purchase_view.xml @@ -222,6 +222,7 @@ + From 1f5c6f16d92e302a1a010be474f1ec150d3291f2 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Mon, 22 Sep 2014 15:00:53 +0200 Subject: [PATCH 06/25] [FIX] auth_ldap: remove unused sql column --- addons/auth_ldap/users_ldap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/auth_ldap/users_ldap.py b/addons/auth_ldap/users_ldap.py index e253874d33a..6a6414c0037 100644 --- a/addons/auth_ldap/users_ldap.py +++ b/addons/auth_ldap/users_ldap.py @@ -251,7 +251,7 @@ class users(osv.osv): return user_id registry = RegistryManager.get(db) with registry.cursor() as cr: - cr.execute("SELECT id, active FROM res_users WHERE lower(login)=%s", (login,)) + cr.execute("SELECT id FROM res_users WHERE lower(login)=%s", (login,)) res = cr.fetchone() if res: return False From faa04a43de9e515eb7e02bd0064848f7c35d7f0a Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Mon, 22 Sep 2014 15:58:24 +0200 Subject: [PATCH 07/25] [FIX] account_voucher: import invoices, sign according to transaction type --- .../account_voucher/wizard/account_statement_from_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/account_voucher/wizard/account_statement_from_invoice.py b/addons/account_voucher/wizard/account_statement_from_invoice.py index 4644bdee62d..41d3560e278 100644 --- a/addons/account_voucher/wizard/account_statement_from_invoice.py +++ b/addons/account_voucher/wizard/account_statement_from_invoice.py @@ -78,10 +78,11 @@ class account_statement_from_invoice_lines(osv.osv_memory): 'invoice_id': line.invoice.id}) type = 'general' ttype = amount < 0 and 'payment' or 'receipt' - sign = 1 + sign = 1 if ttype == 'receipt' else -1 if line.journal_id.type in ('sale', 'sale_refund'): type = 'customer' ttype = 'receipt' + sign = 1 elif line.journal_id.type in ('purchase', 'purchase_refund'): type = 'supplier' ttype = 'payment' From caf1ad8a6218a99222777912253dc038073aef3e Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Mon, 22 Sep 2014 16:33:09 +0200 Subject: [PATCH 08/25] [FIX] website_blog: portal users should not be able to read unpublished blog posts --- addons/website_blog/security/website_blog.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/website_blog/security/website_blog.xml b/addons/website_blog/security/website_blog.xml index 97efcb82eab..76653607faf 100644 --- a/addons/website_blog/security/website_blog.xml +++ b/addons/website_blog/security/website_blog.xml @@ -6,7 +6,7 @@ Blog Post: public: published only [('website_published', '=', True)] - + From 9c77f794b44a0b18d548385ae3ec9f49c6f85a94 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Tue, 23 Sep 2014 18:53:17 +0200 Subject: [PATCH 09/25] [FIX] widget html: rendering issue in mail composer on Firefox The cleditor width does not include the margins. Setting 100% will make the editable area too large (104%) on Firefox (opw 611700). This issue was already fixed in newer releases, from commit 9247c37de7d67beb479905e218656665dc12391f Nevertheless, it introduced a new issue: while editing the form, the content of widgets html which were located in hidden notebook pages weren't displayed when displaying the page content. opw-614448 --- addons/web/static/src/js/view_form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 08050c7d686..439aaf0ed89 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2720,7 +2720,7 @@ instance.web.form.FieldTextHtml = instance.web.form.AbstractField.extend(instanc if (! this.get("effective_readonly")) { self._updating_editor = false; this.$textarea = this.$el.find('textarea'); - var width = ((this.node.attrs || {}).editor_width || '100%'); + var width = ((this.node.attrs || {}).editor_width || 'calc(100% - 4px)'); var height = ((this.node.attrs || {}).editor_height || 250); this.$textarea.cleditor({ width: width, // width not including margins, borders or padding From 4a064b93747f51b77bda893531d7c09f4019571b Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 24 Sep 2014 10:27:40 +0200 Subject: [PATCH 10/25] [FIX] stock: error on stock.partial.move creation The stock_partial_move wizard removes the required attribute for the field picking_id on a stock.partial.move. This means that we could get moves without picking_id and the previous line was failing ('NoneType' object has no attribute 'currency_id'). opw 614531 --- addons/stock/wizard/stock_partial_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/stock/wizard/stock_partial_picking.py b/addons/stock/wizard/stock_partial_picking.py index 4d364490943..5419d2dda2a 100644 --- a/addons/stock/wizard/stock_partial_picking.py +++ b/addons/stock/wizard/stock_partial_picking.py @@ -163,7 +163,7 @@ class stock_partial_picking(osv.osv_memory): 'move_id' : move.id, 'location_id' : move.location_id.id, 'location_dest_id' : move.location_dest_id.id, - 'currency': move.picking_id.company_id.currency_id.id, + 'currency': move.picking_id and move.picking_id.company_id.currency_id.id or False, } if move.picking_id.type == 'in' and move.product_id.cost_method == 'average': partial_move.update(update_cost=True, **self._product_cost_for_average_update(cr, uid, move)) From 5c2fb121caa8e3b12c208a50d119457f947fe137 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Wed, 24 Sep 2014 13:07:26 +0200 Subject: [PATCH 11/25] [FIX] web_calendar: reset dataset index if search result empty --- addons/web_calendar/static/src/js/calendar.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/addons/web_calendar/static/src/js/calendar.js b/addons/web_calendar/static/src/js/calendar.js index f2cc0f74fcd..2c7edaf13e6 100644 --- a/addons/web_calendar/static/src/js/calendar.js +++ b/addons/web_calendar/static/src/js/calendar.js @@ -393,6 +393,13 @@ instance.web_calendar.CalendarView = instance.web.View.extend({ }).done(function(events) { self.dataset_events = events; self.events_loaded(events); + if (self.dataset.index === null) { + if (events.length) { + self.dataset.index = 0; + } + } else if (self.dataset.index >= events.length) { + self.dataset.index = events.length ? 0 : null; + } }); }); }, From 60699cd7142985fe64954e13b562c14c8c461dfb Mon Sep 17 00:00:00 2001 From: dhr-odoo Date: Tue, 1 Jul 2014 16:50:59 +0530 Subject: [PATCH 12/25] [FIX] point_of_sale: invoice not created in backend Missing arguments that lead to the invoice not created in the backend (opw 609470). --- addons/point_of_sale/static/src/js/models.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 774f0bc036a..66b9d67eeaa 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -427,14 +427,15 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // called from within a mutex. // this method returns a deferred that always succeeds when all orders have been tried to be sent, // even if none of them could actually be sent. - _flush_all_orders: function () { + _flush_all_orders: function ( order, options) { var self = this; + orders = order || self.db.get_orders() self.set('synch', { state: 'connecting', pending: self.get('synch').pending }); - return self._save_to_server(self.db.get_orders()).done(function () { - var pending = self.db.get_orders().length; + return self._save_to_server( orders, options).done(function () { + var pending = orders.length; self.set('synch', { state: pending ? 'connecting' : 'connected', pending: pending From 933d098e7d321c5aca3680178b9cc3fdd8c12823 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 24 Sep 2014 14:34:14 +0200 Subject: [PATCH 13/25] [IMP] stock: do not generate empty backorders If every line of a partial delivery is at zero, do not generate empty backorders (opw 608680) --- addons/stock/stock.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 14d60c2b56e..4535b51dd8f 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1297,11 +1297,12 @@ class stock_picking(osv.osv): product_avail[product.id] += qty - + # every line of the picking is empty, do not generate anything + empty_picking = not any(q for q in move_product_qty.values() if q > 0) for move in too_few: product_qty = move_product_qty[move.id] - if not new_picking: + if not new_picking and not empty_picking: new_picking_name = pick.name self.write(cr, uid, [pick.id], {'name': sequence_obj.get(cr, uid, @@ -1367,6 +1368,8 @@ class stock_picking(osv.osv): wf_service.trg_write(uid, 'stock.picking', pick.id, cr) delivered_pack_id = new_picking self.message_post(cr, uid, new_picking, body=_("Back order %s has been created.") % (pick.name), context=context) + elif empty_picking: + delivered_pack_id = pick.id else: self.action_move(cr, uid, [pick.id], context=context) wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr) From 126ba0a9a8a1aaaf959a1c2c0f235640e766a9de Mon Sep 17 00:00:00 2001 From: Ravi Gohil Date: Mon, 9 Jun 2014 14:58:30 +0530 Subject: [PATCH 14/25] [FIX] crm: correct open lead or opportunity view from calls When opening a lead/opportunity from the phonecalls view, we did not open the correct view (always the lead). This will use the type of the crm.lead to determine which view should be used, opw 608493. --- addons/crm/crm_lead.py | 5 +++++ addons/crm/crm_phonecall_view.xml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 6c4ef607c0d..b04eae9e31f 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -165,6 +165,11 @@ class crm_lead(base_stage, format_address, osv.osv): return result, fold def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): + if view_type == 'form' and context and context.get('opportunity_id'): + # TODO: replace by get_formview_action call + lead_type = self.browse(cr, user, context['opportunity_id'], context=context).type + view_lead_xml_id = 'crm_case_form_view_oppor' if lead_type == 'opportunity' else 'crm_case_form_view_leads' + _, view_id = self.pool['ir.model.data'].get_object_reference(cr, user, 'crm', view_lead_xml_id) res = super(crm_lead,self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu) if view_type == 'form': res['arch'] = self.fields_view_get_address(cr, user, res['arch'], context=context) diff --git a/addons/crm/crm_phonecall_view.xml b/addons/crm/crm_phonecall_view.xml index 21e18d8affc..4737065be6e 100644 --- a/addons/crm/crm_phonecall_view.xml +++ b/addons/crm/crm_phonecall_view.xml @@ -110,7 +110,7 @@ domain="[('object_id.model', '=', 'crm.phonecall')]"/> - + From b5c8ced8ecd7cf6a270565ee70d88ff6f53dcd54 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 16 Sep 2014 15:21:07 +0200 Subject: [PATCH 15/25] [IMP] stock: Optimize stock_move.setlast_tracking() When setlast_tracking is called on a large number of moves in a picking (e.g. when splitting moves in a picking), the time to complete grows exponentially. The reason is that it loops over all the moves of a picking, even if it keeps only the last tracking. The method now uses a search() with a limit so it doesn't need to browse all the moves. Added test to check the behaviour of setlast_tracking Fixes #2448 --- addons/stock/__openerp__.py | 1 + addons/stock/stock.py | 26 +++++++++------ addons/stock/test/setlast_tracking.yml | 44 ++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 addons/stock/test/setlast_tracking.yml diff --git a/addons/stock/__openerp__.py b/addons/stock/__openerp__.py index dd2a996f6dd..12f42a4c2e2 100644 --- a/addons/stock/__openerp__.py +++ b/addons/stock/__openerp__.py @@ -95,6 +95,7 @@ Dashboard / Reports for Warehouse Management will include: 'test/opening_stock.yml', 'test/shipment.yml', 'test/stock_report.yml', + 'test/setlast_tracking.yml', ], 'installable': True, 'application': True, diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 4535b51dd8f..f117bdbc27d 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2214,16 +2214,24 @@ class stock_move(osv.osv): return count def setlast_tracking(self, cr, uid, ids, context=None): - tracking_obj = self.pool.get('stock.tracking') - picking = self.browse(cr, uid, ids, context=context)[0].picking_id - if picking: - last_track = [line.tracking_id.id for line in picking.move_lines if line.tracking_id] - if not last_track: - last_track = tracking_obj.create(cr, uid, {}, context=context) + assert len(ids) == 1, "1 ID expected, got %s" % (ids, ) + tracking_obj = self.pool['stock.tracking'] + move = self.browse(cr, uid, ids[0], context=context) + picking_id = move.picking_id.id + if picking_id: + move_ids = self.search(cr, uid, [ + ('picking_id', '=', picking_id), + ('tracking_id', '!=', False) + ], limit=1, order='tracking_id DESC', context=context) + if move_ids: + tracking_move = self.browse(cr, uid, move_ids[0], + context=context) + tracking_id = tracking_move.tracking_id.id else: - last_track.sort() - last_track = last_track[-1] - self.write(cr, uid, ids, {'tracking_id': last_track}) + tracking_id = tracking_obj.create(cr, uid, {}, context=context) + self.write(cr, uid, move.id, + {'tracking_id': tracking_id}, + context=context) return True # diff --git a/addons/stock/test/setlast_tracking.yml b/addons/stock/test/setlast_tracking.yml new file mode 100644 index 00000000000..eae25b05ec4 --- /dev/null +++ b/addons/stock/test/setlast_tracking.yml @@ -0,0 +1,44 @@ +- + In order to check if the last tracking is set on moves, I create a picking +- + !record {model: stock.picking, id: shipment_tracking}: + type: out + location_dest_id: stock_location_output +- + !record {model: stock.move, id: shipment_tracking_move1}: + picking_id: shipment_tracking + product_id: product_product_6 + product_uom: product.product_uom_unit + product_qty: 10.0 + location_id: location_monitor + location_dest_id: stock_location_output +- + !record {model: stock.move, id: shipment_tracking_move2}: + picking_id: shipment_tracking + product_id: product_product_6 + product_uom: product.product_uom_unit + product_qty: 10.0 + location_id: location_monitor + location_dest_id: stock_location_output +- + Then I call setlast_tracking on the moves and check if the tracking is correct +- + !python {model: stock.move}: | + move_id = ref('shipment_tracking_move1') + move = self.browse(cr, uid, move_id) + assert not move.tracking_id, "Move1 must not have a tracking at this point" + tracking_ids = self.pool['stock.tracking'].search(cr, uid, []) + self.setlast_tracking(cr, uid, [move_id]) + move.refresh() + tracking_id = move.tracking_id.id + assert tracking_id, "The move must have a tracking_id" + assert tracking_id not in tracking_ids, "The tracking must be a new one" + + move2_id = ref('shipment_tracking_move2') + move2 = self.browse(cr, uid, move2_id) + assert not move2.tracking_id, "Move2 must not have a tracking at this point" + self.setlast_tracking(cr, uid, [move2_id]) + move2.refresh() + tracking2_id = move2.tracking_id.id + assert tracking2_id, "Move2 must have a tracking_id" + assert tracking2_id == tracking_id, "The second move must have the same tracking than the previous one" From 1933e926ff7457b6f116296190ec55eb908de9b3 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 24 Sep 2014 17:00:24 +0200 Subject: [PATCH 16/25] [INP] portal_sale: remove buggy context Even if this should never be used in normal portal environment, if a user with enough access rights tries to create an invoice, this would crash. We don't need context here (opw 612591). --- addons/portal_sale/portal_sale_view.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/portal_sale/portal_sale_view.xml b/addons/portal_sale/portal_sale_view.xml index ac56cc9ddfc..2bdc41c09f9 100644 --- a/addons/portal_sale/portal_sale_view.xml +++ b/addons/portal_sale/portal_sale_view.xml @@ -64,7 +64,6 @@ account.invoice tree,form [('type','in',['out_invoice','out_refund'])] - {'type':['out_invoice','out_refund'], 'journal_type': 'sale'} We haven't sent you any invoice. From d4972ffdb6b9356a524eef1dbc11f455ff4473f2 Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Tue, 23 Sep 2014 17:39:14 +0200 Subject: [PATCH 17/25] [FIX] product,float_utils: perform ceiling via float_round with new rounding_method UP Modified product ceiling() to use float_round() with special mode for rounding UP (away from zero), avoiding pathological cases where float representations errors were ceiling to the superior unit. Also added correspding tests for rounding_method=UP Fixes issue #1125, and replaces PR #1126. --- addons/product/_common.py | 5 +---- openerp/addons/base/test/base_test.yml | 13 ++++++++++-- openerp/tools/float_utils.py | 29 +++++++++++++++++++------- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/addons/product/_common.py b/addons/product/_common.py index c05dcee66a2..f44f6b11a4c 100644 --- a/addons/product/_common.py +++ b/addons/product/_common.py @@ -20,9 +20,6 @@ ############################################################################## from openerp import tools -import math - - def rounding(f, r): # TODO for trunk: log deprecation warning # _logger.warning("Deprecated rounding method, please use tools.float_round to round floats.") @@ -32,4 +29,4 @@ def rounding(f, r): def ceiling(f, r): if not r: return f - return math.ceil(f / r) * r + return tools.float_round(f, precision_rounding=r, rounding_method='UP') diff --git a/openerp/addons/base/test/base_test.yml b/openerp/addons/base/test/base_test.yml index 11f243592fa..5a30bc22529 100644 --- a/openerp/addons/base/test/base_test.yml +++ b/openerp/addons/base/test/base_test.yml @@ -198,8 +198,8 @@ - !python {model: res.currency}: | from tools import float_compare, float_is_zero, float_round, float_repr - def try_round(amount, expected, precision_digits=3, float_round=float_round, float_repr=float_repr): - result = float_repr(float_round(amount, precision_digits=precision_digits), + def try_round(amount, expected, precision_digits=3, float_round=float_round, float_repr=float_repr, rounding_method='HALF-UP'): + result = float_repr(float_round(amount, precision_digits=precision_digits, rounding_method=rounding_method), precision_digits=precision_digits) assert result == expected, 'Rounding error: got %s, expected %s' % (result, expected) try_round(2.6745, '2.675') @@ -213,6 +213,15 @@ try_round(457.4554, '457.455') try_round(-457.4554, '-457.455') + # Try some rounding value with rounding method UP instead of HALF-UP + # We use 8.175 because when normalizing 8.175 with precision_digits=3 it gives + # us 8175,0000000001234 as value, and if not handle correctly the rounding UP + # value will be incorrect (should be 8,175 and not 8,176) + try_round(8.175, '8.175', rounding_method='UP') + try_round(8.1751, '8.176', rounding_method='UP') + try_round(-8.175, '-8.175', rounding_method='UP') + try_round(-8.1751, '-8.175', rounding_method='UP') + # Extended float range test, inspired by Cloves Almeida's test on bug #882036. fractions = [.0, .015, .01499, .675, .67499, .4555, .4555, .45555] expecteds = ['.00', '.02', '.01', '.68', '.67', '.46', '.456', '.4556'] diff --git a/openerp/tools/float_utils.py b/openerp/tools/float_utils.py index 5c934114e3c..79f5a34c7f6 100644 --- a/openerp/tools/float_utils.py +++ b/openerp/tools/float_utils.py @@ -29,10 +29,11 @@ def _float_check_precision(precision_digits=None, precision_rounding=None): return 10 ** -precision_digits return precision_rounding -def float_round(value, precision_digits=None, precision_rounding=None): - """Return ``value`` rounded to ``precision_digits`` - decimal digits, minimizing IEEE-754 floating point representation - errors, and applying HALF-UP (away from zero) tie-breaking rule. +def float_round(value, precision_digits=None, precision_rounding=None, rounding_method='HALF-UP'): + """Return ``value`` rounded to ``precision_digits`` decimal digits, + minimizing IEEE-754 floating point representation errors, and applying + the tie-breaking rule selected with ``rounding_method``, by default + HALF-UP (away from zero). Precision must be given by ``precision_digits`` or ``precision_rounding``, not both! @@ -41,6 +42,9 @@ def float_round(value, precision_digits=None, precision_rounding=None): :param float precision_rounding: decimal number representing the minimum non-zero value at the desired precision (for example, 0.01 for a 2-digit precision). + :param rounding_method: the rounding method used: 'HALF-UP' or 'UP', the first + one rounding up to the closest number with the rule that number>=0.5 is + rounded up to 1, and the latest one always rounding up. :return: rounded float """ rounding_factor = _float_check_precision(precision_digits=precision_digits, @@ -52,7 +56,7 @@ def float_round(value, precision_digits=None, precision_rounding=None): # we normalize the value before rounding it as an integer, and de-normalize # after rounding: e.g. float_round(1.3, precision_rounding=.5) == 1.5 - # TIE-BREAKING: HALF-UP + # TIE-BREAKING: HALF-UP (for normal rounding) # We want to apply HALF-UP tie-breaking rules, i.e. 0.5 rounds away from 0. # Due to IEE754 float/double representation limits, the approximation of the # real value may be slightly below the tie limit, resulting in an error of @@ -66,8 +70,19 @@ def float_round(value, precision_digits=None, precision_rounding=None): normalized_value = value / rounding_factor # normalize epsilon_magnitude = math.log(abs(normalized_value), 2) epsilon = 2**(epsilon_magnitude-53) - normalized_value += cmp(normalized_value,0) * epsilon - rounded_value = round(normalized_value) # round to integer + if rounding_method == 'HALF-UP': + normalized_value += cmp(normalized_value,0) * epsilon + rounded_value = round(normalized_value) # round to integer + + # TIE-BREAKING: UP (for ceiling operations) + # When rounding the value up, we instead subtract the epsilon value + # as the the approximation of the real value may be slightly *above* the + # tie limit, this would result in incorrectly rounding up to the next number + + elif rounding_method == 'UP': + normalized_value -= cmp(normalized_value,0) * epsilon + rounded_value = math.ceil(normalized_value) # ceil to integer + result = rounded_value * rounding_factor # de-normalize return result From 311c77bb885d815aeb64ac5e89d3665f4c2dd172 Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Wed, 24 Sep 2014 16:09:28 +0200 Subject: [PATCH 18/25] [FIX] product: _compute_qty: first round before ceiling, to avoid pathological cases Fixes problem when we try to sell 12 units of a product and change it to 1 dozen, the algorithm was then trying to recompute the original amount and was getting 12,0000048 as a result which was then passed to the ceiling method, getting 13.0! See also previous commit and issue #1125, PR #1126 --- addons/product/product.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/addons/product/product.py b/addons/product/product.py index 83ab8183f7a..da0ce6f9040 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -29,6 +29,7 @@ from openerp.osv import osv, fields from openerp.tools.translate import _ import openerp.addons.decimal_precision as dp +from openerp.tools.float_utils import float_round def ean_checksum(eancode): """returns the checksum of an ean string of length 13, returns -1 if the string has the wrong length""" @@ -176,7 +177,10 @@ class product_uom(osv.osv): raise osv.except_osv(_('Error!'), _('Conversion from Product UoM %s to Default UoM %s is not possible as they both belong to different Category!.') % (from_unit.name,to_unit.name,)) else: return qty - amount = qty / from_unit.factor + # First round to the precision of the original unit, so that + # float representation errors do not bias the following ceil() + # e.g. with 1 / (1/12) we could get 12.0000048, ceiling to 13! + amount = float_round(qty/from_unit.factor, precision_rounding=from_unit.rounding) if to_unit: amount = ceiling(amount * to_unit.factor, to_unit.rounding) return amount From 81f56c9edafa42c96972d9ad25b1d4f53dd960db Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 24 Sep 2014 17:19:22 +0200 Subject: [PATCH 19/25] [IMP] crm: remove TODO posted by past me --- addons/crm/crm_lead.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index a5315ab3a52..8d004721110 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -153,11 +153,10 @@ class crm_lead(format_address, osv.osv): return result, fold def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): - if view_type == 'form' and context and context.get('opportunity_id'): - # TODO: replace by get_formview_action call - lead_type = self.browse(cr, user, context['opportunity_id'], context=context).type - view_lead_xml_id = 'crm_case_form_view_oppor' if lead_type == 'opportunity' else 'crm_case_form_view_leads' - _, view_id = self.pool['ir.model.data'].get_object_reference(cr, user, 'crm', view_lead_xml_id) + if context and context.get('opportunity_id'): + action = self._get_formview_action(cr, user, context['opportunity_id'], context=context) + if action.get('views') and any(view_id for view_id in action['views'] if view_id[1] == view_type): + view_id = next(view_id[0] for view_id in action['views'] if view_id[1] == view_type) res = super(crm_lead, self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu) if view_type == 'form': res['arch'] = self.fields_view_get_address(cr, user, res['arch'], context=context) From 7b17133b532c5023135d8e4254734a92df4ac6d6 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 25 Sep 2014 11:51:15 +0200 Subject: [PATCH 20/25] [FIX] portal_sale: force empty context The context was removed by 1933e926. --- addons/portal_sale/portal_sale_view.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/portal_sale/portal_sale_view.xml b/addons/portal_sale/portal_sale_view.xml index 2bdc41c09f9..80dff872377 100644 --- a/addons/portal_sale/portal_sale_view.xml +++ b/addons/portal_sale/portal_sale_view.xml @@ -64,6 +64,7 @@ account.invoice tree,form [('type','in',['out_invoice','out_refund'])] + {} We haven't sent you any invoice. From 5f6d324db64f54402c545823fb9039f3c2ec7805 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Thu, 25 Sep 2014 13:40:45 +0200 Subject: [PATCH 21/25] [FIX] crm: merge the phonecalls during an opportunities merge --- addons/crm/crm_lead.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index b04eae9e31f..00c6f931ee3 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -626,6 +626,13 @@ class crm_lead(base_stage, format_address, osv.osv): attachment.write(values) return True + def _merge_opportunity_phonecalls(self, cr, uid, opportunity_id, opportunities, context=None): + phonecall_obj = self.pool['crm.phonecall'] + for opportunity in opportunities: + for phonecall_id in phonecall_obj.search(cr, uid, [('opportunity_id', '=', opportunity.id)], context=context): + phonecall_obj.write(cr, uid, phonecall_id, {'opportunity_id': opportunity_id}, context=context) + return True + def merge_opportunity(self, cr, uid, ids, context=None): """ Different cases of merge: @@ -663,6 +670,7 @@ class crm_lead(base_stage, format_address, osv.osv): # Merge messages and attachements into the first opportunity self._merge_opportunity_history(cr, uid, highest.id, tail_opportunities, context=context) self._merge_opportunity_attachments(cr, uid, highest.id, tail_opportunities, context=context) + self._merge_opportunity_phonecalls(cr, uid, highest.id, tail_opportunities, context=context) # Merge notifications about loss of information opportunities = [highest] From 77f86f1591232549dd910fb495f3f8964fdc5bbc Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Thu, 25 Sep 2014 16:58:53 +0200 Subject: [PATCH 22/25] [FIX] payment: transaction formview display issue Using group without defining the col attribute will let the form renderer choose the right display to avoid screwing up the layout --- addons/payment/views/payment_acquirer.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/payment/views/payment_acquirer.xml b/addons/payment/views/payment_acquirer.xml index 5dbd748bda7..ac17fe1b636 100644 --- a/addons/payment/views/payment_acquirer.xml +++ b/addons/payment/views/payment_acquirer.xml @@ -99,7 +99,7 @@
- + From 074c7b6c2516a3c6bd761808edb01be2fbba19bf Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 25 Sep 2014 17:00:34 +0200 Subject: [PATCH 23/25] [FIX] res_users: do not copy login_date when duplicating users --- openerp/addons/base/res/res_users.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index a018ee6e17d..909b14d5601 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -368,6 +368,12 @@ class res_users(osv.osv): default['login'] = _("%s (copy)") % user2copy['login'] return super(res_users, self).copy(cr, uid, id, default, context) + def copy_data(self, cr, uid, ids, default=None, context=None): + if default is None: + default = {} + default.update({'login_date': False}) + return super(res_users, self).copy_data(cr, uid, ids, default, context=context) + @tools.ormcache(skiparg=2) def context_get(self, cr, uid, context=None): user = self.browse(cr, SUPERUSER_ID, uid, context) From af37b3022b9dc944e88846dd58135de758798928 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Fri, 26 Sep 2014 10:17:50 +0200 Subject: [PATCH 24/25] [FIX] cron: doall false by default As in most cases, we do not want this doall --- openerp/addons/base/ir/ir_cron.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_cron.py b/openerp/addons/base/ir/ir_cron.py index 3bcc00f326a..25927c80721 100644 --- a/openerp/addons/base/ir/ir_cron.py +++ b/openerp/addons/base/ir/ir_cron.py @@ -85,7 +85,6 @@ class ir_cron(osv.osv): 'interval_type' : 'months', 'numbercall' : 1, 'active' : 1, - 'doall' : 1 } def _check_args(self, cr, uid, ids, context=None): From 0305010f2ff553798102b806758974f354325112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Fri, 26 Sep 2014 11:38:06 +0200 Subject: [PATCH 25/25] [FIX] fix context problem in graph view The group_by query expects the context to have group_by_no_leaf = true, so we can not just blindly forward the context to the groupby query. This is a defensive way to fix the problem, to avoid other possible crashes. But the context shouldn't have group_by_no_leaf anyway, it does not make sense to explicitely do that in the action --- addons/web_graph/static/src/js/graph_view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/web_graph/static/src/js/graph_view.js b/addons/web_graph/static/src/js/graph_view.js index b4b2fb38f06..ed8711f9ac8 100644 --- a/addons/web_graph/static/src/js/graph_view.js +++ b/addons/web_graph/static/src/js/graph_view.js @@ -86,7 +86,8 @@ instance.web_graph.GraphView = instance.web.View.extend({ col_group_by = groupbys.col_group_by; if (!this.graph_widget) { - this.widget_config.context = context; + this.widget_config.context = _.clone(context); + this.widget_config.context.group_by_no_leaf = true; if (group_by.length) { this.widget_config.row_groupby = group_by; }