# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import binascii from odoo import fields, http, _ from odoo.exceptions import AccessError, MissingError from odoo.http import request from odoo.addons.payment.controllers.portal import PaymentProcessing from odoo.addons.portal.controllers.mail import _message_post_helper from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager, get_records_pager from odoo.osv import expression class CustomerPortal(CustomerPortal): def _prepare_home_portal_values(self, counters): values = super()._prepare_home_portal_values(counters) partner = request.env.user.partner_id SaleOrder = request.env['sale.order'] if 'quotation_count' in counters: values['quotation_count'] = SaleOrder.search_count([ ('message_partner_ids', 'child_of', [partner.commercial_partner_id.id]), ('state', 'in', ['sent', 'cancel']) ]) if SaleOrder.check_access_rights('read', raise_exception=False) else 0 if 'order_count' in counters: values['order_count'] = SaleOrder.search_count([ ('message_partner_ids', 'child_of', [partner.commercial_partner_id.id]), ('state', 'in', ['sale', 'done']) ]) if SaleOrder.check_access_rights('read', raise_exception=False) else 0 return values def _order_get_page_view_values(self, order, access_token, **kwargs): values = { 'sale_order': order, 'token': access_token, 'return_url': '/shop/payment/validate', 'bootstrap_formatting': True, 'partner_id': order.partner_id.id, 'report_type': 'html', 'action': order._get_portal_return_action(), } if order.company_id: values['res_company'] = order.company_id if order.has_to_be_paid(): domain = expression.AND([ ['&', ('state', 'in', ['enabled', 'test']), ('company_id', '=', order.company_id.id)], ['|', ('country_ids', '=', False), ('country_ids', 'in', [order.partner_id.country_id.id])] ]) acquirers = request.env['payment.acquirer'].sudo().search(domain) values['acquirers'] = acquirers.filtered(lambda acq: (acq.payment_flow == 'form' and acq.view_template_id) or (acq.payment_flow == 's2s' and acq.registration_view_template_id)) values['pms'] = request.env['payment.token'].search([('partner_id', '=', order.partner_id.id)]) values['acq_extra_fees'] = acquirers.get_acquirer_extra_fees(order.amount_total, order.currency_id, order.partner_id.country_id.id) if order.state in ('draft', 'sent', 'cancel'): history = request.session.get('my_quotations_history', []) else: history = request.session.get('my_orders_history', []) values.update(get_records_pager(history, order)) return values # # Quotations and Sales Orders # @http.route(['/my/quotes', '/my/quotes/page/'], type='http', auth="user", website=True) def portal_my_quotes(self, page=1, date_begin=None, date_end=None, sortby=None, **kw): values = self._prepare_portal_layout_values() partner = request.env.user.partner_id SaleOrder = request.env['sale.order'] domain = [ ('message_partner_ids', 'child_of', [partner.commercial_partner_id.id]), ('state', 'in', ['sent', 'cancel']) ] searchbar_sortings = { 'date': {'label': _('Order Date'), 'order': 'date_order desc'}, 'name': {'label': _('Reference'), 'order': 'name'}, 'stage': {'label': _('Stage'), 'order': 'state'}, } # default sortby order if not sortby: sortby = 'date' sort_order = searchbar_sortings[sortby]['order'] if date_begin and date_end: domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)] # count for pager quotation_count = SaleOrder.search_count(domain) # make pager pager = portal_pager( url="/my/quotes", url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby}, total=quotation_count, page=page, step=self._items_per_page ) # search the count to display, according to the pager data quotations = SaleOrder.search(domain, order=sort_order, limit=self._items_per_page, offset=pager['offset']) request.session['my_quotations_history'] = quotations.ids[:100] values.update({ 'date': date_begin, 'quotations': quotations.sudo(), 'page_name': 'quote', 'pager': pager, 'default_url': '/my/quotes', 'searchbar_sortings': searchbar_sortings, 'sortby': sortby, }) return request.render("sale.portal_my_quotations", values) @http.route(['/my/orders', '/my/orders/page/'], type='http', auth="user", website=True) def portal_my_orders(self, page=1, date_begin=None, date_end=None, sortby=None, **kw): values = self._prepare_portal_layout_values() partner = request.env.user.partner_id SaleOrder = request.env['sale.order'] domain = [ ('message_partner_ids', 'child_of', [partner.commercial_partner_id.id]), ('state', 'in', ['sale', 'done']) ] searchbar_sortings = { 'date': {'label': _('Order Date'), 'order': 'date_order desc'}, 'name': {'label': _('Reference'), 'order': 'name'}, 'stage': {'label': _('Stage'), 'order': 'state'}, } # default sortby order if not sortby: sortby = 'date' sort_order = searchbar_sortings[sortby]['order'] if date_begin and date_end: domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)] # count for pager order_count = SaleOrder.search_count(domain) # pager pager = portal_pager( url="/my/orders", url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby}, total=order_count, page=page, step=self._items_per_page ) # content according to pager orders = SaleOrder.search(domain, order=sort_order, limit=self._items_per_page, offset=pager['offset']) request.session['my_orders_history'] = orders.ids[:100] values.update({ 'date': date_begin, 'orders': orders.sudo(), 'page_name': 'order', 'pager': pager, 'default_url': '/my/orders', 'searchbar_sortings': searchbar_sortings, 'sortby': sortby, }) return request.render("sale.portal_my_orders", values) @http.route(['/my/orders/'], type='http', auth="public", website=True) def portal_order_page(self, order_id, report_type=None, access_token=None, message=False, download=False, **kw): try: order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token) except (AccessError, MissingError): return request.redirect('/my') if report_type in ('html', 'pdf', 'text'): return self._show_report(model=order_sudo, report_type=report_type, report_ref='sale.action_report_saleorder', download=download) # use sudo to allow accessing/viewing orders for public user # only if he knows the private token # Log only once a day if order_sudo: # store the date as a string in the session to allow serialization now = fields.Date.today().isoformat() session_obj_date = request.session.get('view_quote_%s' % order_sudo.id) if session_obj_date != now and request.env.user.share and access_token: request.session['view_quote_%s' % order_sudo.id] = now body = _('Quotation viewed by customer %s', order_sudo.partner_id.name) _message_post_helper( "sale.order", order_sudo.id, body, token=order_sudo.access_token, message_type="notification", subtype_xmlid="mail.mt_note", partner_ids=order_sudo.user_id.sudo().partner_id.ids, ) values = self._order_get_page_view_values(order_sudo, access_token, **kw) values['message'] = message return request.render('sale.sale_order_portal_template', values) @http.route(['/my/orders//accept'], type='json', auth="public", website=True) def portal_quote_accept(self, order_id, access_token=None, name=None, signature=None): # get from query string if not on json param access_token = access_token or request.httprequest.args.get('access_token') try: order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token) except (AccessError, MissingError): return {'error': _('Invalid order.')} if not order_sudo.has_to_be_signed(): return {'error': _('The order is not in a state requiring customer signature.')} if not signature: return {'error': _('Signature is missing.')} try: order_sudo.write({ 'signed_by': name, 'signed_on': fields.Datetime.now(), 'signature': signature, }) request.env.cr.commit() except (TypeError, binascii.Error) as e: return {'error': _('Invalid signature data.')} if not order_sudo.has_to_be_paid(): order_sudo.action_confirm() order_sudo._send_order_confirmation_mail() pdf = request.env.ref('sale.action_report_saleorder').sudo()._render_qweb_pdf([order_sudo.id])[0] _message_post_helper( 'sale.order', order_sudo.id, _('Order signed by %s') % (name,), attachments=[('%s.pdf' % order_sudo.name, pdf)], **({'token': access_token} if access_token else {})) query_string = '&message=sign_ok' if order_sudo.has_to_be_paid(True): query_string += '#allow_payment=yes' return { 'force_refresh': True, 'redirect_url': order_sudo.get_portal_url(query_string=query_string), } @http.route(['/my/orders//decline'], type='http', auth="public", methods=['POST'], website=True) def decline(self, order_id, access_token=None, **post): try: order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token) except (AccessError, MissingError): return request.redirect('/my') message = post.get('decline_message') query_string = False if order_sudo.has_to_be_signed() and message: order_sudo.action_cancel() _message_post_helper('sale.order', order_id, message, **{'token': access_token} if access_token else {}) else: query_string = "&message=cant_reject" return request.redirect(order_sudo.get_portal_url(query_string=query_string)) # note: website_sale code @http.route(['/my/orders//transaction/'], type='json', auth="public", website=True) def payment_transaction_token(self, acquirer_id, order_id, save_token=False, access_token=None, **kwargs): """ Json method that creates a payment.transaction, used to create a transaction when the user clicks on 'pay now' button. After having created the transaction, the event continues and the user is redirected to the acquirer website. :param int acquirer_id: id of a payment.acquirer record. If not set the user is redirected to the checkout page """ # Ensure a payment acquirer is selected if not acquirer_id: return False try: acquirer_id = int(acquirer_id) except: return False order = request.env['sale.order'].sudo().browse(order_id) if not order or not order.order_line or not order.has_to_be_paid(): return False # Create transaction vals = { 'acquirer_id': acquirer_id, 'type': order._get_payment_type(save_token), 'return_url': order.get_portal_url(), } transaction = order._create_payment_transaction(vals) PaymentProcessing.add_payment_transaction(transaction) return transaction.render_sale_button( order, submit_txt=_('Pay & Confirm'), render_values={ 'type': order._get_payment_type(save_token), 'alias_usage': _('If we store your payment information on our server, subscription payments will be made automatically.'), } ) @http.route('/my/orders//transaction/token', type='http', auth='public', website=True) def payment_token(self, order_id, pm_id=None, **kwargs): order = request.env['sale.order'].sudo().browse(order_id) if not order: return request.redirect("/my/orders") if not order.order_line or pm_id is None or not order.has_to_be_paid(): return request.redirect(order.get_portal_url()) # try to convert pm_id into an integer, if it doesn't work redirect the user to the quote try: pm_id = int(pm_id) except ValueError: return request.redirect(order.get_portal_url()) # Create transaction vals = { 'payment_token_id': pm_id, 'type': 'server2server', 'return_url': order.get_portal_url(), } tx = order._create_payment_transaction(vals) PaymentProcessing.add_payment_transaction(tx) return request.redirect('/payment/process')