diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 4c6853b4221..09283dae105 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -419,9 +419,6 @@ class AccountMove(models.Model): journal_id=journal_id, auto=auto) reversed_moves |= reversed_move - #unreconcile all lines reversed - aml = ac_move.line_ids.filtered(lambda x: x.account_id.reconcile or x.account_id.internal_type == 'liquidity') - aml.remove_move_reconcile() #reconcile together the reconcilable (or the liquidity aml) and their newly created counterpart for account in set([x.account_id for x in aml]): to_rec = aml.filtered(lambda y: y.account_id == account) @@ -438,6 +435,18 @@ class AccountMove(models.Model): def open_reconcile_view(self): return self.line_ids.open_reconcile_view() + # FIXME: Clarify me and change me in master + @api.multi + def action_duplicate(self): + self.ensure_one() + action = self.env.ref('account.action_move_journal_line').read()[0] + action['target'] = 'inline' + action['context'] = dict(self.env.context) + action['context']['view_no_maturity'] = False + action['views'] = [(self.env.ref('account.view_move_form').id, 'form')] + action['res_id'] = self.copy().id + return action + @api.model def _run_reverses_entries(self): ''' This method is called from a cron job. ''' @@ -627,7 +636,7 @@ class AccountMoveLine(models.Model): index=True, store=True, copy=False) # related is required blocked = fields.Boolean(string='No Follow-up', default=False, help="You can check this box to mark this journal item as a litigation with the associated partner") - date_maturity = fields.Date(string='Due date', index=True, required=True, + date_maturity = fields.Date(string='Due date', index=True, required=True, copy=False, help="This field is used for payable and receivable journal entries. You can put the limit date for the payment of this line.") date = fields.Date(related='move_id.date', string='Date', index=True, store=True, copy=False, readonly=False) # related is required analytic_line_ids = fields.One2many('account.analytic.line', 'move_id', string='Analytic lines', oldname="analytic_lines") diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py index f2485b379e6..85ec5f9bb66 100644 --- a/addons/account/models/partner.py +++ b/addons/account/models/partner.py @@ -204,7 +204,7 @@ class ResPartner(models.Model): @api.multi def _credit_debit_get(self): - tables, where_clause, where_params = self.env['account.move.line']._query_get() + tables, where_clause, where_params = self.env['account.move.line'].with_context(company_id=self.env.user.company_id.id)._query_get() where_params = [tuple(self.ids)] + where_params if where_clause: where_clause = 'AND ' + where_clause diff --git a/addons/account/static/src/js/account_payment_field.js b/addons/account/static/src/js/account_payment_field.js index a20a681b0e6..c4dec36dda0 100644 --- a/addons/account/static/src/js/account_payment_field.js +++ b/addons/account/static/src/js/account_payment_field.js @@ -162,4 +162,8 @@ var ShowPaymentLineWidget = AbstractField.extend({ field_registry.add('payment', ShowPaymentLineWidget); +return { + ShowPaymentLineWidget: ShowPaymentLineWidget +}; + }); diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index 92436671b63..7e78b448a85 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -1556,10 +1556,11 @@ account.move.form account.move -
+
diff --git a/addons/bus/models/res_partner.py b/addons/bus/models/res_partner.py index b0b4c23d74f..a476e0c08dc 100644 --- a/addons/bus/models/res_partner.py +++ b/addons/bus/models/res_partner.py @@ -15,13 +15,14 @@ class ResPartner(models.Model): self.env.cr.execute(""" SELECT U.partner_id as id, - CASE WHEN age(now() AT TIME ZONE 'UTC', B.last_poll) > interval %s THEN 'offline' - WHEN age(now() AT TIME ZONE 'UTC', B.last_presence) > interval %s THEN 'away' + CASE WHEN age(now() AT TIME ZONE 'UTC', max(B.last_poll)) > interval %s THEN 'offline' + WHEN age(now() AT TIME ZONE 'UTC', max(B.last_presence)) > interval %s THEN 'away' ELSE 'online' END as status FROM bus_presence B JOIN res_users U ON B.user_id = U.id WHERE U.partner_id IN %s AND U.active = 't' + GROUP BY U.partner_id """, ("%s seconds" % DISCONNECTION_TIMER, "%s seconds" % AWAY_TIMER, tuple(self.ids))) res = dict(((status['id'], status['status']) for status in self.env.cr.dictfetchall())) for partner in self: diff --git a/addons/delivery/models/delivery_carrier.py b/addons/delivery/models/delivery_carrier.py index b5b0fcb0644..ad0d4f3cb30 100644 --- a/addons/delivery/models/delivery_carrier.py +++ b/addons/delivery/models/delivery_carrier.py @@ -70,7 +70,7 @@ class DeliveryCarrier(models.Model): def install_more_provider(self): return { 'name': 'New Providers', - 'view_mode': 'kanban', + 'view_mode': 'kanban,form', 'res_model': 'ir.module.module', 'domain': [['name', 'ilike', 'delivery_']], 'type': 'ir.actions.act_window', diff --git a/addons/fleet/models/fleet_vehicle.py b/addons/fleet/models/fleet_vehicle.py index 091595bc937..6be09a1da8a 100644 --- a/addons/fleet/models/fleet_vehicle.py +++ b/addons/fleet/models/fleet_vehicle.py @@ -59,6 +59,7 @@ class FleetVehicle(models.Model): fuel_type = fields.Selection([ ('gasoline', 'Gasoline'), ('diesel', 'Diesel'), + ('lpg', 'LPG'), ('electric', 'Electric'), ('hybrid', 'Hybrid') ], 'Fuel Type', help='Fuel Used by the vehicle') diff --git a/addons/l10n_be_hr_payroll_fleet/models/fleet.py b/addons/l10n_be_hr_payroll_fleet/models/fleet.py index c72f10117ba..2a89a67de88 100644 --- a/addons/l10n_be_hr_payroll_fleet/models/fleet.py +++ b/addons/l10n_be_hr_payroll_fleet/models/fleet.py @@ -45,15 +45,21 @@ class FleetVehicle(models.Model): elif contract.cost_frequency == "yearly": car.total_cost += contract.cost_generated / 12.0 - def _get_co2_fee(self, co2): - return max((((co2 * 9.0) - 600.0) * 1.2488) / 12.0, 0.0) + def _get_co2_fee(self, co2, fuel_type): + fuel_coefficient = {'diesel': 600, 'gasoline': 768, 'lpg': 990, 'electric': 0, 'hybrid': 600} + co2_fee = 0 + if fuel_type and fuel_type != 'electric': + if not co2: + co2 = 165 if fuel_type in ['diesel', 'hybrid'] else 182 + co2_fee = (((co2 * 9.0) - fuel_coefficient.get(fuel_type, 0)) * 144.97 / 114.08) / 12.0 + return max(co2_fee, 26.47) - @api.depends('co2') + @api.depends('co2', 'fuel_type') def _compute_co2_fee(self): for car in self: - car.co2_fee = self._get_co2_fee(car.co2) + car.co2_fee = self._get_co2_fee(car.co2, car.fuel_type) - @api.depends('fuel_type', 'car_value', 'acquisition_date') + @api.depends('fuel_type', 'car_value', 'acquisition_date', 'co2') def _compute_car_atn(self): for car in self: car.atn = car._get_car_atn(car.acquisition_date, car.car_value, car.fuel_type, car.co2) @@ -111,15 +117,18 @@ class FleetVehicle(models.Model): atn = 0.0 else: if fuel_type in ['diesel', 'hybrid']: - reference = 87.0 + reference = 88.0 else: - reference = 105.0 + reference = 107.0 + + if not co2: + co2 = 195 if fuel_type in ['diesel', 'hybrid'] else 205 if co2 <= reference: atn = car_value * max(0.04, (0.055 - 0.001 * (reference - co2))) * magic_coeff else: atn = car_value * min(0.18, (0.055 + 0.001 * (co2 - reference))) * magic_coeff - return max(1280, atn) / 12.0 + return max(1310, atn) / 12.0 @api.onchange('model_id') def _onchange_model_id(self): @@ -140,7 +149,7 @@ class FleetVehicleModel(models.Model): default_recurring_cost_amount_depreciated = fields.Float(string="Cost (Depreciated)", help="Default recurring cost amount that should be applied to a new car from this model") default_co2 = fields.Float(string="CO2 emissions") - default_fuel_type = fields.Selection([('gasoline', 'Gasoline'), ('diesel', 'Diesel'), ('electric', 'Electric'), ('hybrid', 'Hybrid')], 'Fuel Type', help='Fuel Used by the vehicle') + default_fuel_type = fields.Selection([('gasoline', 'Gasoline'), ('diesel', 'Diesel'), ('lpg', 'LPG'), ('electric', 'Electric'), ('hybrid', 'Hybrid')], 'Fuel Type', help='Fuel Used by the vehicle') default_car_value = fields.Float(string="Catalog Value (VAT Incl.)") can_be_requested = fields.Boolean(string="Can be requested", help="Can be requested on a contract as a new car") default_atn = fields.Float(compute='_compute_atn', string="ATN") @@ -158,7 +167,7 @@ class FleetVehicleModel(models.Model): for model in self: model.default_total_depreciated_cost = model.co2_fee + model.default_recurring_cost_amount_depreciated - @api.depends('default_co2') + @api.depends('default_co2', 'default_fuel_type') def _compute_co2_fee(self): for model in self: - model.co2_fee = self.env['fleet.vehicle']._get_co2_fee(model.default_co2) + model.co2_fee = self.env['fleet.vehicle']._get_co2_fee(model.default_co2, model.default_fuel_type) diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js index d32a9327068..7f3ca1cb202 100644 --- a/addons/mail/static/src/js/chatter.js +++ b/addons/mail/static/src/js/chatter.js @@ -266,6 +266,7 @@ var Chatter = Widget.extend({ recordName: this.recordName, defaultBody: oldComposer && oldComposer.$input && oldComposer.$input.val(), defaultMentionSelections: oldComposer && oldComposer.getMentionListenerSelections(), + attachmentIds: (oldComposer && oldComposer.get('attachment_ids')) || [], }); this._composer.on('input_focused', this, function () { this._composer.mentionSetPrefetchedPartners(this._mentionSuggestions || []); diff --git a/addons/mail/static/src/js/composers/basic_composer.js b/addons/mail/static/src/js/composers/basic_composer.js index 8a533cd4515..0e25b3cdccf 100644 --- a/addons/mail/static/src/js/composers/basic_composer.js +++ b/addons/mail/static/src/js/composers/basic_composer.js @@ -56,7 +56,7 @@ var BasicComposer = Widget.extend({ // Attachments this._attachmentDataSet = new data.DataSetSearch(this, 'ir.attachment', this.context); this.fileuploadID = _.uniqueId('o_chat_fileupload'); - this.set('attachment_ids', []); + this.set('attachment_ids', options.attachmentIds || []); // Mention this._mentionManager = new MentionManager(this); @@ -118,6 +118,7 @@ var BasicComposer = Widget.extend({ }); // Attachments + this._renderAttachments(); $(window).on(this.fileuploadID, this._onAttachmentLoaded.bind(this)); this.on('change:attachment_ids', this, this._renderAttachments); diff --git a/addons/rating/models/mail_message.py b/addons/rating/models/mail_message.py index f6b3553b7f4..aade3b9ade7 100644 --- a/addons/rating/models/mail_message.py +++ b/addons/rating/models/mail_message.py @@ -8,7 +8,7 @@ class MailMessage(models.Model): _inherit = 'mail.message' rating_ids = fields.One2many('rating.rating', 'message_id', string='Related ratings') - rating_value = fields.Float("Rating Value", compute='_compute_rating_value', store=False) + rating_value = fields.Float("Rating Value", compute='_compute_rating_value', store=False, search='_search_rating_value') @api.multi @api.depends('rating_ids', 'rating_ids.rating') @@ -17,3 +17,7 @@ class MailMessage(models.Model): mapping = dict((r.message_id.id, r.rating) for r in ratings) for message in self: message.rating_value = mapping.get(message.id, 0.0) + + @api.multi + def _search_rating_value(self, operator, operand): + return [('rating_ids.rating', operator, operand)] diff --git a/addons/sale/models/analytic.py b/addons/sale/models/analytic.py index 8fb938582a0..a60dd11b023 100644 --- a/addons/sale/models/analytic.py +++ b/addons/sale/models/analytic.py @@ -19,7 +19,7 @@ class AccountAnalyticLine(models.Model): @api.model def create(self, values): result = super(AccountAnalyticLine, self).create(values) - if 'so_line' not in values and not result.so_line and result.product_id and result.product_id.expense_policy != 'no' and result.amount <= 0: # allow to force a False value for so_line + if 'so_line' not in values and not result.so_line and result.product_id and result.product_id.expense_policy not in [False, 'no'] and result.amount <= 0: # allow to force a False value for so_line result.sudo()._sale_determine_order_line() return result @@ -28,7 +28,8 @@ class AccountAnalyticLine(models.Model): result = super(AccountAnalyticLine, self).write(values) if 'so_line' not in values: # allow to force a False value for so_line # only take the AAL from expense or vendor bill, meaning having a negative amount - self.sudo().filtered(lambda aal: not aal.so_line and aal.product_id and aal.product_id.expense_policy != 'no' and aal.amount <= 0)._sale_determine_order_line() + self.sudo().filtered(lambda aal: not aal.so_line and aal.product_id and aal.product_id.expense_policy not in [False, 'no'] and aal.amount <= 0)._sale_determine_order_line() + return result # ---------------------------------------------------------- # Vendor Bill / Expense : determine the Sale Order to reinvoice diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 7e3e917b5a4..dd4bd9cf57e 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -47,8 +47,8 @@ class SaleOrder(models.Model): amount_untaxed += line.price_subtotal amount_tax += line.price_tax order.update({ - 'amount_untaxed': order.pricelist_id.currency_id.round(amount_untaxed), - 'amount_tax': order.pricelist_id.currency_id.round(amount_tax), + 'amount_untaxed': amount_untaxed, + 'amount_tax': amount_tax, 'amount_total': amount_untaxed + amount_tax, }) @@ -66,22 +66,31 @@ class SaleOrder(models.Model): for possible refunds created directly from existing invoices. This is necessary since such a refund is not directly linked to the SO. """ + # Ignore the status of the deposit product + deposit_product_id = self.env['sale.advance.payment.inv']._default_product_id() + line_invoice_status_all = [(d['order_id'][0], d['invoice_status']) for d in self.env['sale.order.line'].read_group([('order_id', 'in', self.ids), ('product_id', '!=', deposit_product_id.id)], ['order_id', 'invoice_status'], ['order_id', 'invoice_status'], lazy=False)] for order in self: invoice_ids = order.order_line.mapped('invoice_lines').mapped('invoice_id').filtered(lambda r: r.type in ['out_invoice', 'out_refund']) # Search for invoices which have been 'cancelled' (filter_refund = 'modify' in # 'account.invoice.refund') # use like as origin may contains multiple references (e.g. 'SO01, SO02') - refunds = invoice_ids.search([('origin', 'like', order.name), ('company_id', '=', order.company_id.id)]).filtered(lambda r: r.type in ['out_invoice', 'out_refund']) + refunds = invoice_ids.search([('origin', 'like', order.name), ('company_id', '=', order.company_id.id), ('type', 'in', ('out_invoice', 'out_refund'))]) invoice_ids |= refunds.filtered(lambda r: order.name in [origin.strip() for origin in r.origin.split(',')]) - # Search for refunds as well - refund_ids = self.env['account.invoice'].browse() - if invoice_ids: - for inv in invoice_ids: - refund_ids += refund_ids.search([('type', '=', 'out_refund'), ('origin', '=', inv.number), ('origin', '!=', False), ('journal_id', '=', inv.journal_id.id)]) - # Ignore the status of the deposit product - deposit_product_id = self.env['sale.advance.payment.inv']._default_product_id() - line_invoice_status = [line.invoice_status for line in order.order_line if line.product_id != deposit_product_id] + # Search for refunds as well + domain_inv = expression.OR([ + ['&', ('origin', '=', inv.number), ('journal_id', '=', inv.journal_id.id)] + for inv in invoice_ids if inv.number + ]) + if domain_inv: + refund_ids = self.env['account.invoice'].search(expression.AND([ + ['&', ('type', '=', 'out_refund'), ('origin', '!=', False)], + domain_inv + ])) + else: + refund_ids = self.env['account.invoice'].browse() + + line_invoice_status = [d[1] for d in line_invoice_status_all if d[0] == order.id] if order.state not in ('sale', 'done'): invoice_status = 'no' @@ -662,7 +671,7 @@ class SaleOrder(models.Model): # create an analytic account if at least an expense product for order in self: - if any([expense_policy != 'no' for expense_policy in order.order_line.mapped('product_id.expense_policy')]): + if any([expense_policy not in [False, 'no'] for expense_policy in order.order_line.mapped('product_id.expense_policy')]): if not order.analytic_account_id: order._create_analytic_account() @@ -1026,7 +1035,7 @@ class SaleOrderLine(models.Model): msg = _("Extra line with %s ") % (line.product_id.display_name,) line.order_id.message_post(body=msg) # create an analytic account if at least an expense product - if line.product_id.expense_policy != 'no' and not self.order_id.analytic_account_id: + if line.product_id.expense_policy not in [False, 'no'] and not self.order_id.analytic_account_id: self.order_id._create_analytic_account() return line diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml index 7e101fe63b1..72ede8b2e55 100644 --- a/addons/sale/views/sale_views.xml +++ b/addons/sale/views/sale_views.xml @@ -314,6 +314,7 @@ +