diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 9fd3787f03b..5808a2a0eb1 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -892,7 +892,7 @@ class AccountBankStatementLine(models.Model): counterpart_moves = (counterpart_moves | move) # Create The payment - payment = False + payment = self.env['account.payment'] if abs(total)>0.00001: partner_id = self.partner_id and self.partner_id.id or False partner_type = False diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 6606450da4d..18696f6b195 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -707,7 +707,7 @@ class AccountInvoice(models.Model): total_currency = 0 for line in invoice_move_lines: if self.currency_id != company_currency: - currency = self.currency_id.with_context(date=self.date_invoice or fields.Date.context_today(self)) + currency = self.currency_id.with_context(date=self.date or self.date_invoice or fields.Date.context_today(self)) if not (line.get('currency_id') and line.get('amount_currency')): line['currency_id'] = currency.id line['amount_currency'] = currency.round(line['price']) @@ -841,7 +841,6 @@ class AccountInvoice(models.Model): if not inv.date_invoice: inv.with_context(ctx).write({'date_invoice': fields.Date.context_today(self)}) - date_invoice = inv.date_invoice company_currency = inv.company_id.currency_id # create move lines (one per invoice line + eventual taxes and analytic lines) @@ -854,9 +853,9 @@ class AccountInvoice(models.Model): name = inv.name or '/' if inv.payment_term_id: - totlines = inv.with_context(ctx).payment_term_id.with_context(currency_id=company_currency.id).compute(total, date_invoice)[0] + totlines = inv.with_context(ctx).payment_term_id.with_context(currency_id=company_currency.id).compute(total, inv.date_invoice)[0] res_amount_currency = total_currency - ctx['date'] = date_invoice + ctx['date'] = inv.date or inv.date_invoice for i, t in enumerate(totlines): if inv.currency_id != company_currency: amount_currency = company_currency.with_context(ctx).compute(t[1], inv.currency_id) @@ -896,7 +895,7 @@ class AccountInvoice(models.Model): journal = inv.journal_id.with_context(ctx) line = inv.finalize_invoice_move_lines(line) - date = inv.date or date_invoice + date = inv.date or inv.date_invoice move_vals = { 'ref': inv.reference, 'line_ids': line, @@ -1494,6 +1493,12 @@ class AccountPaymentTerm(models.Model): result.append((last_date, dist)) return result + @api.multi + def unlink(self): + property_recs = self.env['ir.property'].search([('value_reference', 'in', ['account.payment.term,%s'%payment_term.id for payment_term in self])]) + property_recs.unlink() + return super(AccountPaymentTerm, self).unlink() + class AccountPaymentTermLine(models.Model): _name = "account.payment.term.line" diff --git a/addons/account/models/company.py b/addons/account/models/company.py index 32b7b992137..3211571755b 100644 --- a/addons/account/models/company.py +++ b/addons/account/models/company.py @@ -57,10 +57,16 @@ Best Regards,''') if (date.month < last_month or (date.month == last_month and date.day <= last_day)): date = date.replace(month=last_month, day=last_day) else: - date = date.replace(month=last_month, day=last_day, year=date.year + 1) + if last_month == 2 and last_day == 29 and (date.year + 1) % 4 != 0: + date = date.replace(month=last_month, day=28, year=date.year + 1) + else: + date = date.replace(month=last_month, day=last_day, year=date.year + 1) date_to = date date_from = date + timedelta(days=1) - date_from = date_from.replace(year=date_from.year - 1) + if date_from.month == 2 and date_from.day == 29: + date_from = date_from.replace(day=28, year=date_from.year - 1) + else: + date_from = date_from.replace(year=date_from.year - 1) return {'date_from': date_from, 'date_to': date_to} def get_new_account_code(self, current_code, old_prefix, new_prefix, digits): diff --git a/addons/account/views/web_planner_data.xml b/addons/account/views/web_planner_data.xml index abd637d87d5..f040f3482b9 100644 --- a/addons/account/views/web_planner_data.xml +++ b/addons/account/views/web_planner_data.xml @@ -200,7 +200,7 @@

diff --git a/addons/account_bank_statement_import/account_bank_statement_import.py b/addons/account_bank_statement_import/account_bank_statement_import.py index 07ab4c7dc6e..9056b7af916 100644 --- a/addons/account_bank_statement_import/account_bank_statement_import.py +++ b/addons/account_bank_statement_import/account_bank_statement_import.py @@ -223,7 +223,7 @@ class AccountBankStatementImport(models.TransientModel): else: ignored_statement_lines_import_ids.append(line_vals['unique_import_id']) if 'balance_start' in st_vals: - st_vals['balance_start'] += line_vals['amount'] + st_vals['balance_start'] += float(line_vals['amount']) if len(filtered_st_lines) > 0: # Remove values that won't be used to create records diff --git a/addons/hr_expense/data/hr_expense_data.xml b/addons/hr_expense/data/hr_expense_data.xml index b922a38d43c..100282658a8 100644 --- a/addons/hr_expense/data/hr_expense_data.xml +++ b/addons/hr_expense/data/hr_expense_data.xml @@ -44,7 +44,7 @@ Submitted hr.expense.sheet Expense report submitted, waiting approval - + Paid diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index 5c03e56c495..efc1433b629 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -30,7 +30,8 @@ class HrExpense(models.Model): company_id = fields.Many2one('res.company', string='Company', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.user.company_id) currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.user.company_id.currency_id) analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, oldname='analytic_account') - account_id = fields.Many2one('account.account', string='Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, default=lambda self: self.env['ir.property'].get('property_account_expense_categ_id', 'product.category')) + account_id = fields.Many2one('account.account', string='Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, default=lambda self: self.env['ir.property'].get('property_account_expense_categ_id', 'product.category'), + help="An expense account is expected") description = fields.Text() payment_mode = fields.Selection([("own_account", "Employee (to reimburse)"), ("company_account", "Company")], default='own_account', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, string="Payment By") attachment_number = fields.Integer(compute='_compute_attachment_number', string='Number of Attachments') @@ -418,10 +419,15 @@ class HrExpenseSheet(models.Model): @api.model def create(self, vals): + # Add the followers at creation, so they can be notified + if vals.get('employee_id'): + employee = self.env['hr.employee'].browse(vals['employee_id']) + users = self._get_users_to_subscribe(employee=employee) - self.env.user + vals['message_follower_ids'] = [] + for partner in users.mapped('partner_id'): + vals['message_follower_ids'] += self.env['mail.followers']._add_follower_command(self._name, [], {partner.id: None}, {})[0] sheet = super(HrExpenseSheet, self).create(vals) self.check_consistency() - if vals.get('employee_id'): - sheet._add_followers() return sheet @api.multi @@ -456,16 +462,20 @@ class HrExpenseSheet(models.Model): return 'hr_expense.mt_expense_paid' return super(HrExpenseSheet, self)._track_subtype(init_values) - def _add_followers(self): - user_ids = [] - employee = self.employee_id + def _get_users_to_subscribe(self, employee=False): + users = self.env['res.users'] + employee = employee or self.employee_id if employee.user_id: - user_ids.append(employee.user_id.id) + users |= employee.user_id if employee.parent_id: - user_ids.append(employee.parent_id.user_id.id) + users |= employee.parent_id.user_id if employee.department_id and employee.department_id.manager_id and employee.parent_id != employee.department_id.manager_id: - user_ids.append(employee.department_id.manager_id.user_id.id) - self.message_subscribe_users(user_ids=user_ids) + users |= employee.department_id.manager_id.user_id + return users + + def _add_followers(self): + users = self._get_users_to_subscribe() + self.message_subscribe_users(user_ids=users.ids) @api.onchange('employee_id') def _onchange_employee_id(self): diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml index afe006a9b43..1f85ee44f7e 100644 --- a/addons/hr_expense/views/hr_expense_views.xml +++ b/addons/hr_expense/views/hr_expense_views.xml @@ -60,7 +60,7 @@ - + diff --git a/addons/l10n_fr/data/account_chart_template_data.xml b/addons/l10n_fr/data/account_chart_template_data.xml index 9b961ff1e6a..6e2120f6c14 100644 --- a/addons/l10n_fr/data/account_chart_template_data.xml +++ b/addons/l10n_fr/data/account_chart_template_data.xml @@ -2906,6 +2906,7 @@ Coupons échus à l'encaissement 5111 + @@ -2913,6 +2914,7 @@ Chèques à encaisser 5112 + @@ -2920,6 +2922,7 @@ Effets à l'encaissement 5113 + @@ -2927,6 +2930,7 @@ Effets à l'escompte 5114 + diff --git a/addons/maintenance/views/maintenance_views.xml b/addons/maintenance/views/maintenance_views.xml index 3c1281a07a5..e61dfc9ba83 100644 --- a/addons/maintenance/views/maintenance_views.xml +++ b/addons/maintenance/views/maintenance_views.xml @@ -217,7 +217,7 @@ equipment.request.calendar maintenance.request - + diff --git a/addons/mass_mailing/models/mail_mail.py b/addons/mass_mailing/models/mail_mail.py index 3a69530db1a..5c4f68b5597 100644 --- a/addons/mass_mailing/models/mail_mail.py +++ b/addons/mass_mailing/models/mail_mail.py @@ -61,15 +61,14 @@ class MailMail(models.Model): self.ensure_one() body = super(MailMail, self).send_get_mail_body(partner=partner) - links_blacklist = ['/unsubscribe_from_list'] - if self.mailing_id and body and self.statistics_ids: for match in re.findall(URL_REGEX, self.body_html): - href = match[0] url = match[1] - if not [s for s in links_blacklist if s in href]: + parsed = urlparse.urlparse(url, scheme='http') + + if parsed.scheme.startswith('http') and parsed.path.startswith('/r/'): new_href = href.replace(url, url + '/m/' + str(self.statistics_ids[0].id)) body = body.replace(href, new_href) diff --git a/addons/mass_mailing/models/mail_thread.py b/addons/mass_mailing/models/mail_thread.py index 8e515a664f8..ee5fb45f6eb 100644 --- a/addons/mass_mailing/models/mail_thread.py +++ b/addons/mass_mailing/models/mail_thread.py @@ -39,3 +39,13 @@ class MailThread(models.AbstractModel): message_ids = [x.strip() for x in decode_smtp_header(message['References']).split()] self.env['mail.mail.statistics'].set_replied(mail_message_ids=message_ids) return super(MailThread, self).message_route_process(message, message_dict, routes) + + @api.multi + def message_post_with_template(self, template_id, **kwargs): + # avoid having message send through `message_post*` methods being implicitly considered as + # mass-mailing + no_massmail = self.with_context( + default_mass_mailing_name=False, + default_mass_mailing_id=False, + ) + return super(MailThread, no_massmail).message_post_with_template(template_id, **kwargs) diff --git a/addons/mrp_repair/models/mrp_repair.py b/addons/mrp_repair/models/mrp_repair.py index 866bde4858b..e8d362ae67c 100644 --- a/addons/mrp_repair/models/mrp_repair.py +++ b/addons/mrp_repair/models/mrp_repair.py @@ -226,11 +226,12 @@ class Repair(models.Model): @api.multi def action_repair_invoice_create(self): - self.action_invoice_create() - if self.invoice_method == 'b4repair': - self.action_repair_ready() - elif self.invoice_method == 'after_repair': - self.write({'state': 'done'}) + for repair in self: + repair.action_invoice_create() + if repair.invoice_method == 'b4repair': + repair.action_repair_ready() + elif repair.invoice_method == 'after_repair': + repair.write({'state': 'done'}) return True @api.multi diff --git a/addons/payment_authorize/models/payment.py b/addons/payment_authorize/models/payment.py index 56314adee26..7a3963acab2 100644 --- a/addons/payment_authorize/models/payment.py +++ b/addons/payment_authorize/models/payment.py @@ -256,14 +256,14 @@ class TxAuthorize(models.Model): def authorize_s2s_capture_transaction(self): self.ensure_one() transaction = AuthorizeAPI(self.acquirer_id) - tree = transaction.capture(self.acquirer_reference, self.amount) + tree = transaction.capture(self.acquirer_reference or '', self.amount) return self._authorize_s2s_validate_tree(tree) @api.multi def authorize_s2s_void_transaction(self): self.ensure_one() transaction = AuthorizeAPI(self.acquirer_id) - tree = transaction.void(self.acquirer_reference) + tree = transaction.void(self.acquirer_reference or '') return self._authorize_s2s_validate_tree(tree) @api.multi diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 21ed55c5a53..a2612f98638 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -625,7 +625,7 @@ class PosOrder(models.Model): return_picking = Picking.create(return_vals) return_picking.message_post(body=message) - for line in order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu']): + for line in order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu'] and not float_is_zero(l.qty, precision_digits=l.product_id.uom_id.rounding)): moves |= Move.create({ 'name': line.name, 'product_uom': line.product_id.uom_id.id, diff --git a/addons/sale/models/sale_analytic.py b/addons/sale/models/sale_analytic.py index ef58a8e44aa..5849845e660 100644 --- a/addons/sale/models/sale_analytic.py +++ b/addons/sale/models/sale_analytic.py @@ -10,27 +10,20 @@ class SaleOrderLine(models.Model): @api.multi def _compute_analytic(self, domain=None): lines = {} + force_so_lines = self.env.context.get("force_so_lines") if not domain: + if not self.ids and not force_so_lines: + return True # To filter on analyic lines linked to an expense expense_type_id = self.env.ref('account.data_account_type_expenses', raise_if_not_found=False) expense_type_id = expense_type_id and expense_type_id.id - domain = [ - ('so_line', 'in', self.ids), - '|', - ('amount', '<', 0), - '&', - ('amount', '=', 0), - '|', - ('move_id', '=', False), - ('move_id.account_id.user_type_id', '=', expense_type_id) - ] + domain = [('so_line', 'in', self.ids), ('amount', '<=', 0.0)] data = self.env['account.analytic.line'].read_group( domain, ['so_line', 'unit_amount', 'product_uom_id'], ['product_uom_id', 'so_line'], lazy=False ) # If the unlinked analytic line was the last one on the SO line, the qty was not updated. - force_so_lines = self.env.context.get("force_so_lines") if force_so_lines: for line in force_so_lines: lines.setdefault(line, 0.0) diff --git a/addons/sale/wizard/sale_make_invoice_advance.py b/addons/sale/wizard/sale_make_invoice_advance.py index 886d80d19bd..2b9caf659bb 100644 --- a/addons/sale/wizard/sale_make_invoice_advance.py +++ b/addons/sale/wizard/sale_make_invoice_advance.py @@ -113,6 +113,7 @@ class SaleAdvancePaymentInv(models.TransientModel): 'payment_term_id': order.payment_term_id.id, 'fiscal_position_id': order.fiscal_position_id.id or order.partner_id.property_account_position_id.id, 'team_id': order.team_id.id, + 'user_id': order.user_id.id, 'comment': order.note, }) invoice.compute_taxes() diff --git a/addons/sale_timesheet/models/sale_order.py b/addons/sale_timesheet/models/sale_order.py index 0254c3dbe0e..0db9b0c0632 100644 --- a/addons/sale_timesheet/models/sale_order.py +++ b/addons/sale_timesheet/models/sale_order.py @@ -140,20 +140,9 @@ class SaleOrderLine(models.Model): @api.multi def _compute_analytic(self, domain=None): - if not domain: + if not domain and self.ids: # To filter on analyic lines linked to an expense expense_type_id = self.env.ref('account.data_account_type_expenses', raise_if_not_found=False) expense_type_id = expense_type_id and expense_type_id.id - domain = [ - ('so_line', 'in', self.ids), - '|', - '|', - ('amount', '<', 0.0), - ('project_id', '!=', False), - '&', - ('amount', '=', 0), - '|', - ('move_id', '=', False), - ('move_id.account_id.user_type_id', '=', expense_type_id) - ] + domain = [('so_line', 'in', self.ids), '|', ('amount', '<=', 0.0), ('project_id', '!=', False)] return super(SaleOrderLine, self)._compute_analytic(domain=domain) diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index c3386476adb..a37fa6ff77b 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -364,6 +364,12 @@ class Product(models.Model): def action_view_routes(self): return self.mapped('product_tmpl_id').action_view_routes() + @api.multi + def write(self, values): + res = super(Product, self).write(values) + if 'active' in values and not values['active'] and self.mapped('orderpoint_ids').filtered(lambda r: r.active): + raise UserError(_('You still have some active reordering rules on this product. Please archive or delete them first.')) + return res class ProductTemplate(models.Model): _inherit = 'product.template' diff --git a/addons/website_quote/models/sale_quote.py b/addons/website_quote/models/sale_quote.py index 79743fdc814..19b944df129 100644 --- a/addons/website_quote/models/sale_quote.py +++ b/addons/website_quote/models/sale_quote.py @@ -24,7 +24,7 @@ class SaleQuoteTemplate(models.Model): ], 'Payment', help="Require immediate payment by the customer when validating the order from the website quote") mail_template_id = fields.Many2one('mail.template', 'Confirmation Mail', help="This e-mail template will be sent on confirmation. Leave empty to send nothing.", - domain=[('model', '=', 'sale.quote.template')]) + domain=[('model', '=', 'sale.order')]) @api.multi def open_template(self): diff --git a/addons/website_quote/static/src/js/website_quotation.js b/addons/website_quote/static/src/js/website_quotation.js index a6e1b701c5e..04705686f32 100644 --- a/addons/website_quote/static/src/js/website_quotation.js +++ b/addons/website_quote/static/src/js/website_quotation.js @@ -135,12 +135,18 @@ if(!$('.o_website_quote').length) { case "h1": var id = self.setElementId('quote_header_', el); var text = self.extractText($(el)); + if (!text) { + break; + } last_li = $("
  • ").append($('').text(text)).appendTo(self.$el); last_ul = false; break; case "h2": var id = self.setElementId('quote_', el); var text = self.extractText($(el)); + if (!text) { + break; + } if (last_li) { if (!last_ul) { last_ul = $("