summaryrefslogtreecommitdiff
path: root/addons/sale/controllers
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/sale/controllers
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/sale/controllers')
-rw-r--r--addons/sale/controllers/__init__.py6
-rw-r--r--addons/sale/controllers/onboarding.py26
-rw-r--r--addons/sale/controllers/portal.py333
-rw-r--r--addons/sale/controllers/variant.py34
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))