Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MIG] portal_addresses: Migration to 18.0 #1057

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions portal_addresses/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
##############################################################################
{
'name': 'Portal Addresses',
'version': "17.0.1.0.0",
'version': "18.0.1.0.0",
'category': 'Tools',
'complexity': 'easy',
'author': 'ADHOC SA, Odoo Community Association (OCA)',
Expand All @@ -35,6 +35,11 @@
'views/portal_templates.xml',
'views/templates.xml',
],
'installable': False,
'installable': True,
'auto_install': True,
'assets': {
'web.assets_frontend': [
'/portal_addresses/static/src/js/address.js'
]
}
}
233 changes: 139 additions & 94 deletions portal_addresses/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,106 +2,58 @@
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
import json
from odoo import http
from odoo.http import request
from werkzeug.exceptions import Forbidden
from odoo.addons.website_sale.controllers.main import WebsiteSale
from odoo.tools import clean_context


class WebsiteSalePortal(WebsiteSale):
@http.route(
'/portal/address', type='http', methods=['GET'], auth='public', website=True, sitemap=False
)
def shop_address(
self, partner_id=None, address_type='billing', use_delivery_as_billing=None, **query_params
):
""" Display the address form.

A partner and/or an address type can be given through the query string params to specify
which address to update or create, and its type.

@http.route(['/portal/address'], type='http', methods=['GET', 'POST'],
auth="public", website=True)
def portal_address(self, **kw):
Partner = request.env['res.partner'].with_context(show_address=1).sudo()
order = request.env['sale.order'].new({
:param str partner_id: The partner whose address to update with the address form, if any.
:param str address_type: The type of the address: 'billing' or 'delivery'.
:param str use_delivery_as_billing: Whether the provided address should be used as both the
delivery and the billing address. 'true' or 'false'.
:param dict query_params: The additional query string parameters forwarded to
`_prepare_address_form_values`.
:return: The rendered address form.
:rtype: str
"""
partner_id = partner_id and int(partner_id)
order_sudo = request.env['sale.order'].new({
'partner_id': request.env.user.partner_id.commercial_partner_id.id
})

mode = (False, False)
values, errors = {}, {}
partner_id = int(kw.get('partner_id', -1))

if partner_id > 0:
partner_type = request.env['res.partner'].browse(partner_id).type
values = Partner.browse(partner_id)
if partner_type == 'invoice':
mode = ('edit', 'billing')
else:
mode = ('edit', 'shipping')
elif partner_id == -1:
mode = ('new', kw.get('mode') or 'shipping')
else: # no mode - refresh without post?
return request.redirect('/portal/addresses')

# IF POSTED
if 'submitted' in kw:
pre_values = self.values_preprocess(kw)
errors, error_msg = self.checkout_form_validate(mode, kw, pre_values)
post, errors, error_msg = self.values_postprocess(order, mode, pre_values, errors, error_msg)
if errors:
errors['error_message'] = error_msg
values = kw
else:
partner_id = self._portal_address_form_save(mode, post, kw)
if isinstance(partner_id, Forbidden):
return partner_id
if mode[1] == 'billing':
order.partner_id = partner_id
elif mode[1] == 'shipping':
order.partner_shipping_id = partner_id

order.message_partner_ids = [(4, partner_id), (3, request.website.partner_id.id)]
if not errors:
return request.redirect(kw.get('callback') or '/portal/addresses')

render_values = {
'website_sale_order': order,
'partner_id': partner_id,
'mode': mode,
'checkout': values,
'error': errors,
'callback': kw.get('callback'),
'only_services': order and order.only_services,
'account_on_checkout': request.website.account_on_checkout,
'is_public_user': request.website.is_public_user()
}
# para evitar modulo puente con l10n_ar_website_sale lo hacemos asi
if request.env['ir.module.module'].sudo().search([
('name', '=', 'l10n_ar_website_sale'),
('state', '=', 'installed')], limit=1):
document_categories = request.env[
'l10n_latam.document.type'].sudo().search([])
afip_responsabilities = request.env[
'l10n_ar.afip.responsibility.type'].sudo().search([])
uid = request.session.uid or request.env.ref('base.public_user').id
Partner = request.env['res.users'].browse(uid).partner_id
Partner = Partner.with_context(show_address=1).sudo()
render_values.update({
'document_categories': document_categories,
'afip_responsabilities': afip_responsabilities,
'partner': Partner,
})
render_values.update(self._get_country_related_render_values(kw, render_values))
return request.render("portal_addresses.portal_address", render_values)

def _portal_address_form_save(self, mode, checkout, all_values):
Partner = request.env['res.partner']
if mode[0] == 'new':
partner_id = Partner.sudo().create(checkout).id
elif mode[0] == 'edit':
partner_id = int(all_values.get('partner_id', 0))
if partner_id:
# double check
partner = request.env.user.partner_id
shippings = Partner.sudo().search(
[("id", "child_of", partner.commercial_partner_id.ids)])
if partner_id not in shippings.mapped('id') and \
partner_id != partner.id:
return Forbidden()
Partner.browse(partner_id).sudo().write(checkout)
return partner_id
# Retrieve the partner whose address to update, if any, and its address type.
partner_sudo, address_type = self._prepare_address_update(
order_sudo, partner_id=partner_id, address_type=address_type
)

if partner_sudo: # If editing an existing partner.
use_delivery_as_billing = (
order_sudo.partner_shipping_id == order_sudo.partner_invoice_id
)

# Render the address form.
address_form_values = self._prepare_address_form_values(
order_sudo,
partner_sudo,
address_type=address_type,
use_delivery_as_billing=use_delivery_as_billing,
**query_params
)
return request.render('portal_addresses.portal_address', address_form_values)


@http.route(['/portal/addresses'],
type='http', auth="public", website=True)
Expand All @@ -110,8 +62,6 @@ def portal_addresses(self, **post):
order = request.env['sale.order'].new(
{'partner_id':
request.env.user.partner_id.commercial_partner_id.id})
order.pricelist_id = order.partner_id.property_product_pricelist \
and order.partner_id.property_product_pricelist.id or False
Partner = order.partner_id.with_context(show_address=1).sudo()
shippings = Partner.search(
[("id", "child_of", order.partner_id.commercial_partner_id.ids),
Expand All @@ -126,11 +76,106 @@ def portal_addresses(self, **post):
values = {
'order': order,
'website_sale_order': order,
'shippings': shippings,
'billings': billings
'delivery_addresses': shippings,
'billing_addresses': billings
}
# Avoid useless rendering if called in ajax
if post.get('xhr'):
return 'ok'
return request.render(
"portal_addresses.addresses", values)

@http.route(
'/portal/address/submit', type='http', methods=['POST'], auth='public', website=True,
sitemap=False
)
def shop_address_submit(
self, partner_id=None, address_type='billing', use_delivery_as_billing=None, callback=None,
required_fields=None, **form_data
):
""" Create or update an address.

If it succeeds, it returns the URL to redirect (client-side) to. If it fails (missing or
invalid information), it highlights the problematic form input with the appropriate error
message.

:param str partner_id: The partner whose address to update with the address form, if any.
:param str address_type: The type of the address: 'billing' or 'delivery'.
:param str use_delivery_as_billing: Whether the provided address should be used as both the
billing and the delivery address. 'true' or 'false'.
:param str callback: The URL to redirect to in case of successful address creation/update.
:param str required_fields: The additional required address values, as a comma-separated
list of `res.partner` fields.
:param dict form_data: The form data to process as address values.
:return: A JSON-encoded feedback, with either the success URL or an error message.
:rtype: str
"""
order_sudo = request.env['sale.order'].new({
'partner_id': request.env.user.partner_id.commercial_partner_id.id
})
use_delivery_as_billing = False
partner_sudo, address_type = self._prepare_address_update(
order_sudo, partner_id=partner_id and int(partner_id), address_type=address_type
)
# Parse form data into address values, and extract incompatible data as extra form data.
address_values, extra_form_data = self._parse_form_data(form_data)

is_main_address = order_sudo.partner_id.id == partner_sudo.id
# Validate the address values and highlights the problems in the form, if any.
invalid_fields, missing_fields, error_messages = self._validate_address_values(
address_values,
partner_sudo,
address_type,
use_delivery_as_billing,
required_fields,
is_main_address=is_main_address,
**extra_form_data,
)
if error_messages:
return json.dumps({
'invalid_fields': list(invalid_fields | missing_fields),
'messages': error_messages,
})

is_new_address = False
if not partner_sudo: # Creation of a new address.
is_new_address = True
self._complete_address_values(
address_values, address_type, use_delivery_as_billing, order_sudo
)
create_context = clean_context(request.env.context)
create_context.update({
'tracking_disable': True,
'no_vat_validation': True, # Already verified in _validate_address_values
})
partner_sudo = request.env['res.partner'].sudo().with_context(
create_context
).create(address_values)
elif not self._are_same_addresses(address_values, partner_sudo):
partner_sudo.write(address_values) # Keep the same partner if nothing changed.

partner_fnames = set()
if is_main_address: # Main address updated.
partner_fnames.add('partner_id') # Force the re-computation of partner-based fields.

if address_type == 'billing':
partner_fnames.add('partner_invoice_id')
if is_new_address and order_sudo.only_services:
# The delivery address is required to make the order.
partner_fnames.add('partner_shipping_id')
callback = callback or self._get_extra_billing_info_route(order_sudo)
elif address_type == 'delivery':
partner_fnames.add('partner_shipping_id')
if use_delivery_as_billing:
partner_fnames.add('partner_invoice_id')

if is_new_address or order_sudo.only_services:
callback = callback or '/shop/checkout?try_skip_step=true'
else:
callback = callback or '/shop/checkout'

self._handle_extra_form_data(extra_form_data, address_values)

return json.dumps({
'successUrl': callback,
})
65 changes: 65 additions & 0 deletions portal_addresses/static/src/js/address.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/** @odoo-module **/

import publicWidget from "@web/legacy/js/public/public_widget";

const websiteSaleAddress = publicWidget.registry.websiteSaleAddress;


websiteSaleAddress.include({

// @override
_onSaveAddress: async function (ev) {
if (!this.addressForm.reportValidity()) {
return
}

const submitButton = ev.currentTarget;
if (!ev.defaultPrevented && !submitButton.disabled) {
ev.preventDefault();
if(ev.currentTarget.closest('form').action.includes('portal/address') ){
submitButton.disabled = true;
const spinner = document.createElement('span');
spinner.classList.add('fa', 'fa-cog', 'fa-spin');
submitButton.appendChild(spinner);

const result = await this.http.post(
'/portal/address/submit',
new FormData(this.addressForm),
)
if (result.successUrl) {
window.location = '/portal/addresses';
} else {
// Highlight missing/invalid form values
document.querySelectorAll('.is-invalid').forEach(element => {
if (!result.invalid_fields.includes(element.name)) {
element.classList.remove('is-invalid');
}
})
result.invalid_fields.forEach(
fieldName => this.addressForm[fieldName].classList.add('is-invalid')
);

// Display the error messages
// NOTE: setCustomValidity is not used as we would have to reset the error msg on
// input update, which is not worth catching for the rare cases where the
// server-side validation will catch validation issues (now that required inputs
// are also handled client-side)
const newErrors = result.messages.map(message => {
const errorHeader = document.createElement('h5');
errorHeader.classList.add('text-danger');
errorHeader.appendChild(document.createTextNode(message));
return errorHeader;
});

this.errorsDiv.replaceChildren(...newErrors);

// Re-enable button and remove spinner
submitButton.disabled = false;
spinner.remove();
}
} else {
this._super(...arguments);
}
}
}
})
Loading