Skip to content

Commit

Permalink
[IMP] website_sale, base: use invoice address at checkout
Browse files Browse the repository at this point in the history
at checkout use the invoice address as billing address instead of
main address if the invoice one is defined. Add the functionality to
add new billings addresses at checkout.

task-3258835

Part-of: odoo#118428
  • Loading branch information
vchu-odoo authored and Feyensv committed Oct 6, 2023
1 parent 4015f90 commit d9c7ff6
Show file tree
Hide file tree
Showing 11 changed files with 288 additions and 136 deletions.
178 changes: 116 additions & 62 deletions addons/website_sale/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -959,12 +959,11 @@ def _get_cart_notification_information(self, order, line_ids):
# ------------------------------------------------------

def checkout_check_address(self, order):
billing_fields_required = self._get_mandatory_fields_billing(order.partner_id.country_id.id)
if not all(order.partner_id.read(billing_fields_required)[0].values()):
return request.redirect('/shop/address?partner_id=%d' % order.partner_id.id)
partner_invoice = order.partner_invoice_id
if not self._check_billing_partner_mandatory_fields(partner_invoice):
return request.redirect('/shop/address?partner_id=%d&is_invoice=1' % partner_invoice.id)

shipping_fields_required = self._get_mandatory_fields_shipping(order.partner_shipping_id.country_id.id)
if not all(order.partner_shipping_id.read(shipping_fields_required)[0].values()):
if not self._check_shipping_partner_mandatory_fields(order.partner_shipping_id):
return request.redirect('/shop/address?partner_id=%d' % order.partner_shipping_id.id)

def checkout_redirection(self, order):
Expand All @@ -987,29 +986,49 @@ def checkout_redirection(self, order):

def checkout_values(self, **kw):
order = request.website.sale_get_order(force_create=True)
shippings = []
bill_partners = []
ship_partners = []
if order.partner_id != request.website.user_id.sudo().partner_id:
Partner = order.partner_id.with_context(show_address=1).sudo()
shippings = Partner.search([
("id", "child_of", order.partner_id.commercial_partner_id.ids),
'|', ("type", "in", ["delivery", "other"]), ("id", "=", order.partner_id.commercial_partner_id.id)
commercial_partner = order.partner_id.commercial_partner_id
bill_partners = Partner.search([
("id", "child_of", commercial_partner.ids),
'|', ("type", "in", ["invoice", "other"]), ("id", "=", commercial_partner.id)
], order='id desc')
if shippings:
if kw.get('partner_id') or 'use_billing' in kw:
if 'use_billing' in kw:
partner_id = order.partner_id.id
else:
partner_id = int(kw.get('partner_id'))
if partner_id in shippings.mapped('id'):
order.partner_shipping_id = partner_id
ship_partners = Partner.search([
("id", "child_of", commercial_partner.ids),
'|', ("type", "in", ["delivery", "other"]), ("id", "=", commercial_partner.id)
], order='id desc')

# do not show commercial_partner_id if its mandatory fields are not complete to children
# as children can not edit (fill) the commercial_partner_id
if commercial_partner != order.partner_id:
if not self._check_billing_partner_mandatory_fields(commercial_partner):
bill_partners = bill_partners.filtered(lambda p: p.id != commercial_partner.id)

if not self._check_shipping_partner_mandatory_fields(commercial_partner):
ship_partners = ship_partners.filtered(lambda p: p.id != commercial_partner.id)

partner_id = int(kw.get('partner_id', 0))
if partner_id:
if 'is_invoice' in kw and bill_partners and partner_id in bill_partners.mapped('id'):
order.partner_invoice_id = partner_id
elif ship_partners and partner_id in ship_partners.mapped('id'):
order.partner_shipping_id = partner_id

values = {
'order': order,
'shippings': shippings,
'shippings': ship_partners,
'billings': bill_partners,
'only_services': order and order.only_services or False
}
return values

def _check_billing_partner_mandatory_fields(self, partner_id):
''' return True if all mandatory fields for billing address are complete '''
billing_fields_required = self._get_mandatory_fields_billing(partner_id.country_id.id)
return all(partner_id.read(billing_fields_required)[0].values())

def _get_mandatory_fields_billing(self, country_id=False):
req = ["name", "email", "street", "city", "country_id"]
if country_id:
Expand All @@ -1020,6 +1039,11 @@ def _get_mandatory_fields_billing(self, country_id=False):
req += ['zip']
return req

def _check_shipping_partner_mandatory_fields(self, partner_id):
''' return True if all mandatory fields for shipping address are complete '''
shipping_fields_required = self._get_mandatory_fields_shipping(partner_id.country_id.id)
return all(partner_id.read(shipping_fields_required)[0].values())

def _get_mandatory_fields_shipping(self, country_id=False):
req = ["name", "street", "city", "country_id"]
if country_id:
Expand All @@ -1037,13 +1061,16 @@ def checkout_form_validate(self, mode, all_form_values, data):
error = dict()
error_message = []

if data.get('partner_id'):
if mode == ('edit', 'billing') and data.get('partner_id'):
partner_su = request.env['res.partner'].sudo().browse(int(data['partner_id'])).exists()
name_change = partner_su and 'name' in data and data['name'] != partner_su.name
email_change = partner_su and 'email' in data and data['email'] != partner_su.email
name_change = partner_su and partner_su.name and 'name' in data and data['name'] != partner_su.name
email_change = partner_su and 'email' in data and data['email'] != partner_su.email and partner_su.email
invoices = request.env['account.move'].sudo().search(
[('partner_id', '=', partner_su.id)], limit=1
) if partner_su else False

# Prevent changing the billing partner name if invoices have been issued.
if mode[1] == 'billing' and name_change and not partner_su.can_edit_vat():
if name_change and not partner_su.can_edit_vat() and invoices:
error['name'] = 'error'
error_message.append(_(
"Changing your name is not allowed once documents have been issued for your"
Expand Down Expand Up @@ -1151,20 +1178,23 @@ def values_postprocess(self, order, mode, values, errors, error_msg):
if request.website.specific_user_account:
new_values['website_id'] = request.website.id

commercial_partner_id = order.partner_id.commercial_partner_id
if mode[0] == 'new':
lang = request.lang.code if request.lang.code in request.website.mapped('language_ids.code') else None
if lang:
new_values['lang'] = lang
new_values['company_id'] = request.website.company_id.id
new_values['team_id'] = request.website.salesteam_id and request.website.salesteam_id.id
new_values['user_id'] = request.website.salesperson_id.id

lang = request.lang.code if request.lang.code in request.website.mapped('language_ids.code') else None
if lang:
new_values['lang'] = lang
if mode == ('edit', 'billing') and order.partner_id.type == 'contact':
new_values['type'] = 'other'
if mode[1] == 'shipping':
new_values['parent_id'] = order.partner_id.commercial_partner_id.id
new_values['type'] = 'delivery'

# define type of the new partner
if mode[1] == 'billing':
new_values['type'] = 'other' if 'use_same' in values else 'invoice'
# for public user avoid linking to default archived 'Public user' partner
if commercial_partner_id.active:
new_values['parent_id'] = commercial_partner_id.id
elif mode[1] == 'shipping':
new_values['type'] = 'delivery'
new_values['parent_id'] = commercial_partner_id.id
return new_values, errors, error_msg

@http.route(['/shop/address'], type='http', methods=['GET', 'POST'], auth="public", website=True, sitemap=False)
Expand All @@ -1176,35 +1206,36 @@ def address(self, **kw):
if redirection:
return redirection

mode = (False, False)
can_edit_vat = False
values, errors = {}, {}

partner_id = int(kw.get('partner_id', -1))

# IF PUBLIC ORDER
if order.partner_id.id == request.website.user_id.sudo().partner_id.id:
if order._is_public_order():
mode = ('new', 'billing')
can_edit_vat = True
# IF ORDER LINKED TO A PARTNER
else:
else: # IF ORDER LINKED TO A PARTNER
if partner_id > 0:
if partner_id == order.partner_id.id:
mode = ('edit', 'billing')
can_edit_vat = order.partner_id.can_edit_vat()
partners = Partner.search(
[('id', 'child_of', order.partner_id.commercial_partner_id.ids)]
)
if 'is_invoice' in kw:
bill_partners_ids = partners.filtered(lambda p: p.type != 'delivery').ids
if partner_id in bill_partners_ids or order.partner_id.id == partner_id:
mode = ('edit', 'billing')
can_edit_vat = order.partner_id.can_edit_vat()
else:
return Forbidden()
else:
shippings = Partner.search([('id', 'child_of', order.partner_id.commercial_partner_id.ids)])
if order.partner_id.commercial_partner_id.id == partner_id:
mode = ('new', 'shipping')
partner_id = -1
elif partner_id in shippings.mapped('id'):
ship_partners_ids = partners.filtered(lambda p: p.type != 'invoice').ids
if partner_id in ship_partners_ids:
mode = ('edit', 'shipping')
else:
return Forbidden()
if mode and partner_id != -1:
values = Partner.browse(partner_id)
elif partner_id == -1:
mode = ('new', 'shipping')
mode = ('new', 'billing') if 'is_invoice' in kw else ('new', 'shipping')
else: # no mode - refresh without post?
return request.redirect('/shop/checkout')

Expand All @@ -1218,29 +1249,53 @@ def address(self, **kw):
errors['error_message'] = error_msg
values = kw
else:
update_mode, address_mode = mode
partner_id = self._checkout_form_save(mode, post, kw)
# We need to validate _checkout_form_save return, because when partner_id not in shippings
# it returns Forbidden() instead the partner_id
if isinstance(partner_id, Forbidden):
return partner_id

fpos_before = order.fiscal_position_id
if mode[1] == 'billing':
order.partner_id = partner_id
# This is the *only* thing that the front end user will see/edit anyway when choosing billing address
order.partner_invoice_id = partner_id
if not kw.get('use_same'):
kw['callback'] = kw.get('callback') or \
(not order.only_services and (mode[0] == 'edit' and '/shop/checkout' or '/shop/address'))
# We need to update the pricelist(by the one selected by the customer), because onchange_partner reset it
# We only need to update the pricelist when it is not redirected to /confirm_order
if kw.get('callback', False) != '/shop/confirm_order':
request.website.sale_get_order(update_pricelist=True)
elif mode[1] == 'shipping':
order.partner_shipping_id = partner_id
update_values = {}
if update_mode == 'new': # New address
if order._is_public_order():
update_values['partner_id'] = partner_id

if address_mode == 'billing':
update_values['partner_invoice_id'] = partner_id
if kw.get('use_same'):
update_values['partner_shipping_id'] = partner_id
elif not kw.get('callback') and not order.only_services:
# Now that the billing is set, if shipping is necessary
# request the user to fill the shipping address
kw['callback'] = '/shop/address'
elif address_mode == 'shipping':
update_values['partner_shipping_id'] = partner_id
elif update_mode == 'edit': # Updating an existing address
if order.partner_id.id == partner_id:
# Editing the main partner of the SO --> also trigger a partner update to
# recompute fpos & any partner-related fields
update_values['partner_id'] = partner_id

if address_mode == 'billing':
update_values['partner_invoice_id'] = partner_id
if not kw.get('callback') and not order.only_services:
kw['callback'] = '/shop/checkout'
elif address_mode == 'shipping':
update_values['partner_shipping_id'] = partner_id

order.write(update_values)

if order.fiscal_position_id != fpos_before:
# Recompute taxes on fpos change
# TODO recompute all prices too to correctly manage price_include taxes ?
order._recompute_taxes()

if 'partner_id' in update_values:
# Force recomputation of pricelist on main customer address update
request.website.sale_get_order(update_pricelist=True)

# TDE FIXME: don't ever do this
# -> TDE: you are the guy that did what we should never do in commit e6f038a
order.message_partner_ids = [(4, partner_id), (3, request.website.partner_id.id)]
Expand Down Expand Up @@ -1438,8 +1493,7 @@ def _get_country_related_render_values(self, kw, render_values):
order = render_values['website_sale_order']

def_country_id = order.partner_id.country_id
# IF PUBLIC ORDER
if order.partner_id.id == request.website.user_id.sudo().partner_id.id:
if order._is_public_order():
if request.geoip.country_code:
def_country_id = request.env['res.country'].search([('code', '=', request.geoip.country_code)], limit=1)
else:
Expand All @@ -1463,7 +1517,7 @@ def checkout(self, **post):
if redirection:
return redirection

if order.partner_id.id == request.website.user_id.sudo().partner_id.id:
if order._is_public_order():
return request.redirect('/shop/address')

redirection = self.checkout_check_address(order)
Expand Down
6 changes: 6 additions & 0 deletions addons/website_sale/models/sale_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,3 +622,9 @@ def _is_carrier_available(carrier):
]).available_carriers(
self.partner_shipping_id
).filtered(_is_carrier_available)

#=== TOOLING ===#

def _is_public_order(self):
self.ensure_one()
return self.partner_id.id == request.website.user_id.sudo().partner_id.id
12 changes: 7 additions & 5 deletions addons/website_sale/models/website.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,6 @@ def sale_get_order(self, force_create=False, update_pricelist=False):
# change the partner, and trigger the computes (fpos)
sale_order_sudo.write({
'partner_id': partner_sudo.id,
'partner_invoice_id': partner_sudo.id,
'payment_term_id': self.sale_get_payment_term(partner_sudo),
# Must be specified to ensure it is not recomputed when it shouldn't
'pricelist_id': pricelist_id,
Expand All @@ -418,16 +417,19 @@ def sale_get_order(self, force_create=False, update_pricelist=False):

def _prepare_sale_order_values(self, partner_sudo):
self.ensure_one()
addr = partner_sudo.address_get(['delivery'])
addr = partner_sudo.address_get(['delivery', 'invoice'])
if not request.website.is_public_user():
# FIXME VFE why not use last_website_so_id field ?
last_sale_order = self.env['sale.order'].sudo().search(
[('partner_id', '=', partner_sudo.id)],
limit=1,
order="date_order desc, id desc",
)
if last_sale_order and last_sale_order.partner_shipping_id.active: # first = me
addr['delivery'] = last_sale_order.partner_shipping_id.id
if last_sale_order:
if last_sale_order.partner_shipping_id.active: # first = me
addr['delivery'] = last_sale_order.partner_shipping_id.id
if last_sale_order.partner_invoice_id.active:
addr['invoice'] = last_sale_order.partner_invoice_id.id

affiliate_id = request.session.get('affiliate_id')
salesperson_user_sudo = self.env['res.users'].sudo().browse(affiliate_id).exists()
Expand All @@ -439,7 +441,7 @@ def _prepare_sale_order_values(self, partner_sudo):

'fiscal_position_id': self.fiscal_position_id.id,
'partner_id': partner_sudo.id,
'partner_invoice_id': partner_sudo.id,
'partner_invoice_id': addr['invoice'],
'partner_shipping_id': addr['delivery'],

'pricelist_id': self.pricelist_id.id,
Expand Down
25 changes: 20 additions & 5 deletions addons/website_sale/static/src/js/website_sale.js
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ publicWidget.registry.WebsiteSaleLayout = publicWidget.Widget.extend({
publicWidget.registry.websiteSaleCart = publicWidget.Widget.extend({
selector: '.oe_website_sale .oe_cart',
events: {
'click .js_change_billing': '_onClickChangeBilling',
'click .js_change_shipping': '_onClickChangeShipping',
'click .js_edit_address': '_onClickEditAddress',
'click .js_delete_product': '_onClickDeleteProduct',
Expand All @@ -805,19 +806,33 @@ publicWidget.registry.websiteSaleCart = publicWidget.Widget.extend({
// Handlers
//--------------------------------------------------------------------------

/**
* @private
* @param {Event} ev
*/
_onClickChangeBilling: function (ev) {
this._onClickChangeAddress(ev, 'all_billing', 'js_change_billing');
},
/**
* @private
* @param {Event} ev
*/
_onClickChangeShipping: function (ev) {
var $old = $('.all_shipping').find('.card.border.border-primary');
$old.find('.btn-ship').toggle();
$old.addClass('js_change_shipping');
this._onClickChangeAddress(ev, 'all_shipping', 'js_change_shipping');
},
/**
* @private
* @param {Event} ev
*/
_onClickChangeAddress: function (ev, rowAddrClass, cardClass) {
var $old = $(`.${rowAddrClass}`).find('.card.border.border-primary');
$old.find('.btn-addr').toggle();
$old.addClass(cardClass);
$old.removeClass('border border-primary');

var $new = $(ev.currentTarget).parent('div.one_kanban').find('.card');
$new.find('.btn-ship').toggle();
$new.removeClass('js_change_shipping');
$new.find('.btn-addr').toggle();
$new.removeClass(cardClass);
$new.addClass('border border-primary');

var $form = $(ev.currentTarget).parent('div.one_kanban').find('form.d-none');
Expand Down
2 changes: 1 addition & 1 deletion addons/website_sale/static/src/scss/website_sale.scss
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ tr#empty {
display: none;
}

.js_change_shipping {
.js_change_shipping, .js_change_billing {
cursor: pointer;
}

Expand Down
Loading

0 comments on commit d9c7ff6

Please sign in to comment.