diff --git a/portal_addresses/__manifest__.py b/portal_addresses/__manifest__.py index 8b5a3bb04..244d2a39d 100644 --- a/portal_addresses/__manifest__.py +++ b/portal_addresses/__manifest__.py @@ -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)', @@ -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' + ] + } } diff --git a/portal_addresses/controllers/main.py b/portal_addresses/controllers/main.py index a8f46b16d..349f9af78 100644 --- a/portal_addresses/controllers/main.py +++ b/portal_addresses/controllers/main.py @@ -4,104 +4,53 @@ ############################################################################## from odoo import http from odoo.http import request -from werkzeug.exceptions import Forbidden from odoo.addons.website_sale.controllers.main import WebsiteSale 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({ - 'partner_id': request.env.user.partner_id.commercial_partner_id.id - }) - - mode = (False, False) - values, errors = {}, {} - partner_id = int(kw.get('partner_id', -1)) + :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.website.sale_get_order() - 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') + # 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 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 + if partner_sudo: # If editing an existing partner. + use_delivery_as_billing = ( + order_sudo.partner_shipping_id == order_sudo.partner_invoice_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 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) - 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 @http.route(['/portal/addresses'], type='http', auth="public", website=True) @@ -110,8 +59,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), @@ -126,8 +73,8 @@ 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'): diff --git a/portal_addresses/static/src/js/address.js b/portal_addresses/static/src/js/address.js new file mode 100644 index 000000000..6d89d9768 --- /dev/null +++ b/portal_addresses/static/src/js/address.js @@ -0,0 +1,67 @@ +/** @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') ){ + debugger; + 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( + '/shop/address/submit', + new FormData(this.addressForm), + ) + if (result.successUrl) { + debugger; + 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); + } + } + } +}) diff --git a/portal_addresses/views/templates.xml b/portal_addresses/views/templates.xml index a6228e71a..a2cc2a260 100644 --- a/portal_addresses/views/templates.xml +++ b/portal_addresses/views/templates.xml @@ -1,13 +1,15 @@ @@ -18,16 +20,27 @@ -