diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/sale/controllers | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/sale/controllers')
| -rw-r--r-- | addons/sale/controllers/__init__.py | 6 | ||||
| -rw-r--r-- | addons/sale/controllers/onboarding.py | 26 | ||||
| -rw-r--r-- | addons/sale/controllers/portal.py | 333 | ||||
| -rw-r--r-- | addons/sale/controllers/variant.py | 34 |
4 files changed, 399 insertions, 0 deletions
diff --git a/addons/sale/controllers/__init__.py b/addons/sale/controllers/__init__.py new file mode 100644 index 00000000..a35a8802 --- /dev/null +++ b/addons/sale/controllers/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import onboarding +from . import portal +from . import variant diff --git a/addons/sale/controllers/onboarding.py b/addons/sale/controllers/onboarding.py new file mode 100644 index 00000000..8b54c0d3 --- /dev/null +++ b/addons/sale/controllers/onboarding.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import http +from odoo.http import request + + +class OnboardingController(http.Controller): + + @http.route('/sales/sale_quotation_onboarding_panel', auth='user', type='json') + def sale_quotation_onboarding(self): + """ Returns the `banner` for the sale onboarding panel. + It can be empty if the user has closed it or if he doesn't have + the permission to see it. """ + + company = request.env.company + if not request.env.is_admin() or \ + company.sale_quotation_onboarding_state == 'closed': + return {} + + return { + 'html': request.env.ref('sale.sale_quotation_onboarding_panel')._render({ + 'company': company, + 'state': company.get_and_update_sale_quotation_onboarding_state() + }) + } diff --git a/addons/sale/controllers/portal.py b/addons/sale/controllers/portal.py new file mode 100644 index 00000000..746cfc1c --- /dev/null +++ b/addons/sale/controllers/portal.py @@ -0,0 +1,333 @@ +# -*- 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/<int: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/<int: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/<int:order_id>'], 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/<int:order_id>/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/<int:order_id>/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/<int:order_id>/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/<int:order_id>/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') diff --git a/addons/sale/controllers/variant.py b/addons/sale/controllers/variant.py new file mode 100644 index 00000000..3afdc19b --- /dev/null +++ b/addons/sale/controllers/variant.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import http +from odoo.http import request + + +class VariantController(http.Controller): + @http.route(['/sale/get_combination_info'], type='json', auth="user", methods=['POST']) + def get_combination_info(self, product_template_id, product_id, combination, add_qty, pricelist_id, **kw): + combination = request.env['product.template.attribute.value'].browse(combination) + pricelist = self._get_pricelist(pricelist_id) + ProductTemplate = request.env['product.template'] + if 'context' in kw: + ProductTemplate = ProductTemplate.with_context(**kw.get('context')) + product_template = ProductTemplate.browse(int(product_template_id)) + res = product_template._get_combination_info(combination, int(product_id or 0), int(add_qty or 1), pricelist) + if 'parent_combination' in kw: + parent_combination = request.env['product.template.attribute.value'].browse(kw.get('parent_combination')) + if not combination.exists() and product_id: + product = request.env['product.product'].browse(int(product_id)) + if product.exists(): + combination = product.product_template_attribute_value_ids + res.update({ + 'is_combination_possible': product_template._is_combination_possible(combination=combination, parent_combination=parent_combination), + }) + return res + + @http.route(['/sale/create_product_variant'], type='json', auth="user", methods=['POST']) + def create_product_variant(self, product_template_id, product_template_attribute_value_ids, **kwargs): + return request.env['product.template'].browse(int(product_template_id)).create_product_variant(product_template_attribute_value_ids) + + def _get_pricelist(self, pricelist_id, pricelist_fallback=False): + return request.env['product.pricelist'].browse(int(pricelist_id or 0)) |
