diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index b08a17092a9..38ad5ecffc1 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -248,7 +248,7 @@ class account_payment(models.Model): invoice_defaults = self.resolve_2many_commands('invoice_ids', rec.get('invoice_ids')) if invoice_defaults and len(invoice_defaults) == 1: invoice = invoice_defaults[0] - rec['communication'] = invoice['reference'] + rec['communication'] = invoice['reference'] or invoice['name'] or invoice['number'] rec['currency_id'] = invoice['currency_id'][0] rec['payment_type'] = invoice['type'] in ('out_invoice', 'in_refund') and 'inbound' or 'outbound' rec['partner_type'] = MAP_INVOICE_TYPE_PARTNER_TYPE[invoice['type']] diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 39dd7b1f3e3..76c70e5db11 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -274,7 +274,7 @@ class base_action_rule(osv.osv): # check conditions, and execute actions on the records that satisfy them for action in actions: - action._process(action._filter_post(pre[action])) + action._process(action._filter_post(records)) # call original method return unlink.origin(self, **kwargs) diff --git a/addons/crm_partner_assign/wizard/crm_channel_interested.py b/addons/crm_partner_assign/wizard/crm_channel_interested.py index ac9bd931464..ae80f44e375 100644 --- a/addons/crm_partner_assign/wizard/crm_channel_interested.py +++ b/addons/crm_partner_assign/wizard/crm_channel_interested.py @@ -41,7 +41,7 @@ class crm_lead_forward_to_partner(osv.TransientModel): if wizard.comment: message += '

%s

' % wizard.comment for active_id in context.get('active_ids', []): - lead_obj.message_post(cr, uid, active_id, body=message, subtype="mail.mt_comment", context=context) + lead_obj.message_post(cr, uid, active_id, body=message, subtype="mail.mt_note", context=context) if values: lead_obj.write(cr, SUPERUSER_ID, context.get('active_ids', []), values) lead_obj.set_tag_assign(cr, SUPERUSER_ID, context.get('active_ids', []), False) diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py index bdce2f9de44..b694b882ca9 100644 --- a/addons/hr_holidays/hr_holidays.py +++ b/addons/hr_holidays/hr_holidays.py @@ -450,6 +450,19 @@ class hr_holidays(osv.osv): return True def holidays_confirm(self, cr, uid, ids, context=None): + for record in self.browse(cr, uid, ids, context=context): + if record.department_id: + # Subscribe the followers of the department following the `confirmed` subtype + # It's done manually because `message_auto_subscribe` only works on fields for which + # the value is passed to the `create` or `write` methods, + # it doesn't work for related/computed fields + # This can be removed as soon as `department_id` on `hr.holidays` becomes a regular + # fields or as soon as `message_auto_subscribe` works with related/computed fields. + confirmed_subtype = self.pool['ir.model.data'].xmlid_to_object(cr, uid, 'hr_holidays.mt_department_holidays_confirmed', context=context) + self.message_subscribe(cr, uid, [record.id], [ + follower.partner_id.id for follower in record.department_id.message_follower_ids + if confirmed_subtype in follower.subtype_ids + ], context=context) return self.write(cr, uid, ids, {'state': 'confirm'}) def holidays_refuse(self, cr, uid, ids, context=None): diff --git a/addons/mail/static/src/js/client_action.js b/addons/mail/static/src/js/client_action.js index 704c3d4064c..04e1ed13ba8 100644 --- a/addons/mail/static/src/js/client_action.js +++ b/addons/mail/static/src/js/client_action.js @@ -283,12 +283,13 @@ var ChatAction = Widget.extend(ControlPanelMixin, { }, unselect_message: function() { - if (this.channel.type !== 'static' && !this.channel.mass_mailing) { - this.basic_composer.toggle(true); - this.basic_composer.focus(); + this.basic_composer.toggle(this.channel.type !== 'static' && !this.channel.mass_mailing); + this.extended_composer.toggle(this.channel.type !== 'static' && this.channel.mass_mailing); + if (!config.device.touch) { + var composer = this.channel.mass_mailing ? this.extended_composer : this.basic_composer; + composer.focus(); } this.$el.removeClass('o_mail_selection_mode'); - this.extended_composer.toggle(this.channel.mass_mailing); this.thread.unselect(); this.selected_message = null; }, @@ -424,10 +425,6 @@ var ChatAction = Widget.extend(ControlPanelMixin, { .find('.o_mail_chat_button_unstar_all') .toggle(channel.id === "channel_starred"); - self.basic_composer.toggle(channel.type !== 'static' && !channel.mass_mailing); - self.extended_composer.toggle(channel.type !== 'static' && channel.mass_mailing); - self.$el.removeClass('o_mail_selection_mode'); - self.$('.o_mail_chat_channel_item') .removeClass('o_active') .filter('[data-channel-id=' + channel.id + ']') @@ -443,14 +440,14 @@ var ChatAction = Widget.extend(ControlPanelMixin, { // Update control panel before focusing the composer, otherwise focus is on the searchview self.update_cp(); - if (!config.device.touch) { - var composer = channel.mass_mailing ? self.extended_composer : self.basic_composer; - composer.focus(); - } if (config.device.size_class === config.device.SIZES.XS) { self.$('.o_mail_chat_sidebar').hide(); } + // Display and focus the adequate composer, and unselect possibly selected message + // to prevent sending messages as reply to that message + self.unselect_message(); + self.action_manager.do_push_state({ action: self.action.id, active_id: self.channel.id, diff --git a/addons/mail/static/src/js/systray.js b/addons/mail/static/src/js/systray.js index af442f5606e..5e73042a7aa 100644 --- a/addons/mail/static/src/js/systray.js +++ b/addons/mail/static/src/js/systray.js @@ -111,9 +111,7 @@ var MessagingMenu = Widget.extend({ // Generate last message preview (inline message body and compute date to display) _.each(channels_preview, function (channel) { - channel.last_message_preview = - channel.last_message.displayed_author + ': ' + - chat_manager.get_message_body_preview(channel.last_message.body); + channel.last_message_preview = chat_manager.get_message_body_preview(channel.last_message.body); if (channel.last_message.date.isSame(new Date(), 'd')) { // today channel.last_message_date = channel.last_message.date.format('LT'); } else { diff --git a/addons/mail/static/src/xml/systray.xml b/addons/mail/static/src/xml/systray.xml index 4a987fdf992..2ce26f4d485 100644 --- a/addons/mail/static/src/xml/systray.xml +++ b/addons/mail/static/src/xml/systray.xml @@ -45,6 +45,7 @@
+ :
diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index b94428f91fe..871fe2a6a10 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -802,13 +802,7 @@ class mrp_production(osv.osv): return True def _compute_costs_from_production(self, cr, uid, ids, context=None): - """ Generate workcenter costs and rectify the value of the quant - - Must be called at the end of the production. - The value of the quant was not possible to compute before the end of - the manufacturing order due to the cost of raw manterial and production - costs. The price of the quant was not set in get_price_unit. - """ + """ Generate workcenter costs in analytic accounts""" for production in self.browse(cr, uid, ids): total_cost = self._costs_generate(cr, uid, production) @@ -956,6 +950,16 @@ class mrp_production(osv.osv): total_cost += sum([x.inventory_value for x in consumed_move.quant_ids if x.qty > 0]) return total_cost + def _calculate_workcenter_cost(self, cr, uid, production_id, context=None): + """ Compute the planned production cost from the workcenters """ + production = self.browse(cr, uid, production_id, context=context) + total_cost = 0.0 + for wc_line in production.workcenter_lines: + wc = wc_line.workcenter_id + total_cost += wc_line.hour*wc.costs_hour + wc_line.cycle*wc.costs_cycle + + return total_cost + def action_produce(self, cr, uid, production_id, production_qty, production_mode, wiz=False, context=None): """ To produce final product based on production mode (consume/consume&produce). If Production mode is consume, all stock move lines of raw materials will be done/consumed. @@ -1011,18 +1015,25 @@ class mrp_production(osv.osv): total_consume_moves.append(extra_move_id) if production_mode == 'consume_produce': + # add production lines that have already been consumed since the last 'consume & produce' + last_production_date = production.move_created_ids2 and max(production.move_created_ids2.mapped('date')) or False + already_consumed_lines = production.move_lines2.filtered(lambda l: l.date > last_production_date) + total_consume_moves += already_consumed_lines.ids + price_unit = 0 for produce_product in production.move_created_ids: is_main_product = (produce_product.product_id.id == production.product_id.id) and production.product_id.cost_method=='real' if is_main_product: total_cost = self._calculate_total_cost(cr, uid, total_consume_moves, context=context) - price_unit = total_cost / production_qty_uom + production_cost = self._calculate_workcenter_cost(cr, uid, production_id, context=context) + price_unit = (total_cost + production_cost) / production_qty_uom + subproduct_factor = self._get_subproduct_factor(cr, uid, production.id, produce_product.id, context=context) lot_id = False if wiz: lot_id = wiz.lot_id.id qty = min(subproduct_factor * production_qty_uom, produce_product.product_qty) #Needed when producing more than maximum quantity - if is_main_product: + if is_main_product and price_unit: stock_mov_obj.write(cr, uid, [produce_product.id], {'price_unit': price_unit}, context=context) new_moves = stock_mov_obj.action_consume(cr, uid, [produce_product.id], qty, location_id=produce_product.location_id.id, restrict_lot_id=lot_id, context=context) diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index 7db53e35f8e..35a0a02e1e8 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -39,6 +39,7 @@ + diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index 507db3cfbd8..10885c1d9cf 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -1023,7 +1023,9 @@ var ClientListScreenWidget = ScreenWidget.extend({ if (this.editing_client) { this.$('.detail.barcode').val(code.code); } else if (this.pos.db.get_partner_by_barcode(code.code)) { - this.display_client_details('show',this.pos.db.get_partner_by_barcode(code.code)); + var partner = this.pos.db.get_partner_by_barcode(code.code); + this.new_client = partner; + this.display_client_details('show', partner); } }, perform_search: function(query, associate_result){ diff --git a/addons/product_expiry/product_expiry.py b/addons/product_expiry/product_expiry.py index 25390891aa5..9e46ad72aaf 100644 --- a/addons/product_expiry/product_expiry.py +++ b/addons/product_expiry/product_expiry.py @@ -40,7 +40,7 @@ class stock_production_lot(osv.osv): # Assign dates according to products data def create(self, cr, uid, vals, context=None): context = dict(context or {}) - context['product_id'] = vals.get('product_id', context.get('default_product_id')) + context['product_id'] = vals.get('product_id', context.get('default_product_id') or context.get('product_id')) return super(stock_production_lot, self).create(cr, uid, vals, context=context) _defaults = { diff --git a/addons/sale_mrp/sale_mrp.py b/addons/sale_mrp/sale_mrp.py index 8dc89814a3c..c168e0e1770 100644 --- a/addons/sale_mrp/sale_mrp.py +++ b/addons/sale_mrp/sale_mrp.py @@ -144,6 +144,4 @@ class AccountInvoiceLine(models.Model): average_price_unit += factor * self._compute_average_price(prod_qty_done, prod_quantity, prod_moves) price_unit = average_price_unit or price_unit price_unit = uom_obj._compute_qty_obj(self.uom_id, price_unit, self.product_id.uom_id, round=False) - return price_unit - else: - return price_unit + return price_unit diff --git a/addons/website_project_issue/controllers/main.py b/addons/website_project_issue/controllers/main.py index 942fec9da53..98f06c0c281 100644 --- a/addons/website_project_issue/controllers/main.py +++ b/addons/website_project_issue/controllers/main.py @@ -10,15 +10,7 @@ class WebsiteAccount(website_account): def account(self): response = super(WebsiteAccount, self).account() user = request.env.user - # TDE FIXME: shouldn't that be mnaged by the access rule itself ? - # portal projects where you or someone from your company are a follower - project_issues = request.env['project.issue'].search([ - '&', - ('project_id.privacy_visibility', '=', 'portal'), - '|', - ('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]), - ('message_partner_ids', 'child_of', [user.partner_id.id]) - ]) + project_issues = request.env['project.issue'].search([]) response.qcontext.update({'issues': project_issues}) return response diff --git a/doc/reference/orm.rst b/doc/reference/orm.rst index 10ac368e950..fa986038bb6 100644 --- a/doc/reference/orm.rst +++ b/doc/reference/orm.rst @@ -639,6 +639,7 @@ Model Reference .. automethod:: write .. automethod:: read + .. automethod:: read_group .. rubric:: Research diff --git a/openerp/addons/base/i18n/base.pot b/openerp/addons/base/i18n/base.pot index 5316b459bc9..69c948e8de4 100644 --- a/openerp/addons/base/i18n/base.pot +++ b/openerp/addons/base/i18n/base.pot @@ -14902,7 +14902,7 @@ msgstr "" #. module: base #: model:ir.model.fields,help:base.field_res_partner_company_type #: model:ir.model.fields,help:base.field_res_users_company_type -msgid "Technical field, used only to display a boolean using a radio button. As for Odoo v9 RadioButton cannot be used on boolean fields, this one serves as interface. Due to the old API limitations with interface function field, we implement it by hand instead of a true function field. When migrating to the new API the code should be simplified." +msgid "Technical field, used only to display a boolean using a radio button. As for Odoo v9 RadioButton cannot be used on boolean fields, this one serves as interface. Due to the old API limitations with interface function field, we implement it by hand instead of a true function field. When migrating to the new API the code should be simplified. Changing the company_type of a company contact into a company will not display this contact as a company contact but as a standalone company." msgstr "" #. module: base diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 2a5eca827c0..bb818375065 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -239,7 +239,9 @@ class res_partner(osv.Model, format_address): 'fields, this one serves as interface. Due to the old API ' 'limitations with interface function field, we implement it ' 'by hand instead of a true function field. When migrating to ' - 'the new API the code should be simplified.'), + 'the new API the code should be simplified. Changing the' + 'company_type of a company contact into a company will not display' + 'this contact as a company contact but as a standalone company.'), 'use_parent_address': fields.boolean('Use Company Address', help="Select this if you want to set company's address information for this contact"), 'company_id': fields.many2one('res.company', 'Company', select=1), 'color': fields.integer('Color Index'),