diff --git a/addons/account/controllers/portal.py b/addons/account/controllers/portal.py index 35d32a79df4..e86e9f52746 100644 --- a/addons/account/controllers/portal.py +++ b/addons/account/controllers/portal.py @@ -2,10 +2,10 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import http, _ +from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager from odoo.exceptions import AccessError from odoo.http import request - -from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager +from odoo.tools import consteq class PortalAccount(CustomerPortal): @@ -23,6 +23,10 @@ class PortalAccount(CustomerPortal): values['invoice_count'] = invoice_count return values + # ------------------------------------------------------------ + # My Invoices + # ------------------------------------------------------------ + @http.route(['/my/invoices', '/my/invoices/page/'], type='http', auth="user", website=True) def portal_my_invoices(self, page=1, date_begin=None, date_end=None, sortby=None, **kw): values = self._prepare_portal_layout_values() @@ -74,15 +78,31 @@ class PortalAccount(CustomerPortal): }) return request.render("account.portal_my_invoices", values) - @http.route(['/my/invoices/'], type='http', auth="user", website=True) - def portal_my_invoices_report(self, invoice_id, **kw): + @http.route(['/my/invoices/'], type='http', auth="public", website=True) + def portal_my_invoice_detail(self, invoice_id, access_token=None, **kw): invoice = request.env['account.invoice'].browse(invoice_id) try: invoice.check_access_rights('read') invoice.check_access_rule('read') - # TDE FIXME: does not exist, or maybe, don't know except AccessError: - return request.redirect('/my') + if not access_token or not consteq(invoice.sudo().access_token, access_token): + return request.redirect('/my') + + values = { + 'page_name': 'invoice', + 'invoice': invoice.sudo(), + } + return request.render("account.portal_invoice_page", values) + + @http.route(['/my/invoices/pdf/'], type='http', auth="public", website=True) + def portal_my_invoice_report(self, invoice_id, access_token=None, **kw): + invoice = request.env['account.invoice'].browse(invoice_id) + try: + invoice.check_access_rights('read') + invoice.check_access_rule('read') + except AccessError: + if not access_token or not consteq(invoice.sudo().access_token, access_token): + return request.redirect('/my') # print report as sudo, since it require access to taxes, payment term, ... and portal # does not have those access rights. @@ -93,6 +113,10 @@ class PortalAccount(CustomerPortal): ] return request.make_response(pdf, headers=pdfhttpheaders) + # ------------------------------------------------------------ + # My Home + # ------------------------------------------------------------ + def details_form_validate(self, data): error, error_message = super(PortalAccount, self).details_form_validate(data) # prevent VAT/name change if invoices exist diff --git a/addons/account/data/mail_template_data.xml b/addons/account/data/mail_template_data.xml index 266ac35855b..949895e3c1c 100644 --- a/addons/account/data/mail_template_data.xml +++ b/addons/account/data/mail_template_data.xml @@ -20,7 +20,7 @@

Dear ${object.partner_id.name} % set access_action = object.with_context(force_website=True).get_access_action() % set is_online = access_action and access_action['type'] == 'ir.actions.act_url' -% set access_url = is_online and object.get_mail_url() or '/report/pdf/account.report_invoice/' + str(object.id) +% set access_url = object.get_mail_url() % if object.partner_id.parent_id: (${object.partner_id.parent_id.name}) diff --git a/addons/account/models/account_config_settings.py b/addons/account/models/account_config_settings.py index bcce4caab64..9f9ad09a298 100644 --- a/addons/account/models/account_config_settings.py +++ b/addons/account/models/account_config_settings.py @@ -38,6 +38,7 @@ class AccountConfigSettings(models.TransientModel): module_account_asset = fields.Boolean(string='Assets Management') module_account_deferred_revenue = fields.Boolean(string="Revenue Recognition") module_account_budget = fields.Boolean(string='Budget Management') + module_account_payment = fields.Boolean(string='Online Payment') module_account_reports = fields.Boolean("Dynamic Reports") module_account_reports_followup = fields.Boolean("Enable payment followup management") default_sale_tax_id = fields.Many2one('account.tax', string="Default Sale Tax", diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index cc96c01bbd5..de297e73aa5 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -2,6 +2,8 @@ import json import re +import uuid + from lxml import etree from datetime import datetime from dateutil.relativedelta import relativedelta @@ -38,10 +40,13 @@ MAGIC_COLUMNS = ('id', 'create_uid', 'create_date', 'write_uid', 'write_date') class AccountInvoice(models.Model): _name = "account.invoice" - _inherit = ['mail.thread', 'mail.activity.mixin'] + _inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin'] _description = "Invoice" _order = "date_invoice desc, number desc, id desc" + def _get_default_access_token(self): + return str(uuid.uuid4()) + @api.one @api.depends('invoice_line_ids.price_subtotal', 'tax_line_ids.amount', 'currency_id', 'company_id', 'date_invoice', 'type') def _compute_amount(self): @@ -231,6 +236,9 @@ class AccountInvoice(models.Model): ], readonly=True, index=True, change_default=True, default=lambda self: self._context.get('type', 'out_invoice'), track_visibility='always') + access_token = fields.Char( + 'Security Token', copy=False, + default=_get_default_access_token) refund_invoice_id = fields.Many2one('account.invoice', string="Invoice for which this invoice is the credit note") number = fields.Char(related='move_id.name', store=True, readonly=True, copy=False) @@ -353,6 +361,11 @@ class AccountInvoice(models.Model): ('number_uniq', 'unique(number, company_id, journal_id, type)', 'Invoice Number must be unique per Company!'), ] + def _compute_portal_url(self): + super(AccountInvoice, self)._compute_portal_url() + for order in self: + order.portal_url = '/my/invoices/%s' % (order.id) + def _no_existing_validated_invoice(self): self.ensure_one() if self.journal_id.refund_sequence: @@ -480,6 +493,27 @@ class AccountInvoice(models.Model): view_id = get_view_id('invoice_form', 'account.invoice.form').id return super(AccountInvoice, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu) + @api.model_cr_context + def _init_column(self, column_name): + """ Initialize the value of the given column for existing rows. + + Overridden here because we need to generate different access tokens + and by default _init_column calls the default method once and applies + it for every record. + """ + if column_name != 'access_token': + super(AccountInvoice, self)._init_column(column_name) + else: + query = """UPDATE %(table_name)s + SET %(column_name)s = md5(md5(random()::varchar || id::varchar) || clock_timestamp()::varchar)::uuid::varchar + WHERE %(column_name)s IS NULL + """ % {'table_name': self._name, 'column_name': column_name} + self.env.cr.execute(query) + + def _generate_access_token(self): + for invoice in self: + invoice.access_token = self._get_default_access_token() + @api.multi def invoice_print(self): """ Print the invoice and mark it as sent, so that we can see more @@ -692,7 +726,6 @@ class AccountInvoice(models.Model): raise UserError(_("Invoice must be in draft or open state in order to be cancelled.")) return self.action_cancel() - @api.multi def _notification_recipients(self, message, groups): groups = super(AccountInvoice, self)._notification_recipients(message, groups) @@ -715,24 +748,26 @@ class AccountInvoice(models.Model): try: record.check_access_rule('read') except exceptions.AccessError: - pass + if self.env.context.get('force_website'): + return { + 'type': 'ir.actions.act_url', + 'url': '/my/invoices/%s' % self.id, + 'target': 'self', + 'res_id': self.id, + } + else: + pass else: return { 'type': 'ir.actions.act_url', - 'url': '/my/invoices?', # No controller /my/invoices/, only a report pdf + 'url': '/my/invoices/%s?access_token=%s' % (self.id, self.access_token), 'target': 'self', 'res_id': self.id, } return super(AccountInvoice, self).get_access_action(access_uid) def get_mail_url(self): - self.ensure_one() - params = { - 'model': self._name, - 'res_id': self.id, - } - params.update(self.partner_id.signup_get_auth_param()[self.partner_id.id]) - return '/mail/view?' + url_encode(params) + return self.get_share_url() @api.multi def get_formview_id(self, access_uid=None): @@ -1367,6 +1402,7 @@ class AccountInvoiceLine(models.Model): ondelete='set null', index=True, oldname='uos_id') product_id = fields.Many2one('product.product', string='Product', ondelete='restrict', index=True) + product_image = fields.Binary('Product Image', related="product_id.image", store=False) account_id = fields.Many2one('account.account', string='Account', required=True, domain=[('deprecated', '=', False)], default=_default_account, diff --git a/addons/account/views/account_config_settings_views.xml b/addons/account/views/account_config_settings_views.xml index 21f3601d742..43a8cd0e900 100644 --- a/addons/account/views/account_config_settings_views.xml +++ b/addons/account/views/account_config_settings_views.xml @@ -225,6 +225,17 @@ +

+
+ +
+
+
+
diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml index f924ad4ef29..95b1a53463a 100644 --- a/addons/account/views/account_invoice_view.xml +++ b/addons/account/views/account_invoice_view.xml @@ -368,6 +368,8 @@
+
+