Permalink
Browse files

[REF] delivery: new delivery carrier wizard

On a sale order, the delivery carrier choice was done directly
on the form view. To get an estimated delivery price, the user need to
click 'add to order' to create a delivery fee line. To get the exact
delivery price on the sale order, the user needed to leave the sale
order without delivery line in order to get the exact price from the
picking at its validation.
As this flow was not so clear, this commit adds a new wizard to choose
the delivery carrier and check its commition fees. This add a line in
any case.

The wizard will be launched from a new button on the sale order

Task : 1908654
  • Loading branch information...
Whenrow committed Feb 8, 2019
1 parent 823b073 commit 2d489ca0f2369bba11b545e4781d74181aa2abde
@@ -28,6 +28,7 @@
'views/report_package_barcode.xml',
'views/res_config_settings_views.xml',
'wizard/choose_delivery_package_views.xml',
'wizard/choose_delivery_carrier_views.xml',
],
'demo': ['data/delivery_demo.xml'],
'installable': True,
@@ -10,51 +10,28 @@ class SaleOrder(models.Model):
_inherit = 'sale.order'

carrier_id = fields.Many2one('delivery.carrier', string="Delivery Method", help="Fill this field if you plan to invoice the shipping based on picking.")
delivery_price = fields.Float(string='Estimated Delivery Price', readonly=True, copy=False)
delivery_message = fields.Char(readonly=True, copy=False)
delivery_rating_success = fields.Boolean(copy=False)
delivery_rating_success = fields.Boolean(copy=False, default=True)
invoice_shipping_on_delivery = fields.Boolean(string="Invoice Shipping on Delivery", copy=False)
available_carrier_ids = fields.Many2many("delivery.carrier", compute="_compute_available_carrier", string="Available Carriers")
delivery_set = fields.Boolean(compute='_compute_delivery_state')
recompute_delivery_price = fields.Boolean('Delivery cost should be recomputed')

def _compute_amount_total_without_delivery(self):
self.ensure_one()
delivery_cost = sum([l.price_total for l in self.order_line if l.is_delivery])
return self.amount_total - delivery_cost

@api.depends('partner_id')
def _compute_available_carrier(self):
carriers = self.env['delivery.carrier'].search([])
for rec in self:
rec.available_carrier_ids = carriers.available_carriers(rec.partner_id) if rec.partner_id else carriers

def get_delivery_price(self):
for order in self.filtered(lambda o: o.state in ('draft', 'sent') and len(o.order_line) > 0):
# We do not want to recompute the shipping price of an already validated/done SO
# or on an SO that has no lines yet
order.delivery_rating_success = False
res = order.carrier_id.rate_shipment(order)
if res['success']:
order.delivery_rating_success = True
order.delivery_price = res['price']
order.delivery_message = res['warning_message']
else:
order.delivery_rating_success = False
order.delivery_price = 0.0
order.delivery_message = res['error_message']

@api.onchange('carrier_id')
def onchange_carrier_id(self):
if self.state in ('draft', 'sent'):
self.delivery_price = 0.0
self.delivery_rating_success = False
self.delivery_message = False

@api.onchange('partner_id')
def onchange_partner_id_carrier_id(self):
if self.partner_id:
self.carrier_id = self.partner_id.property_delivery_carrier_id.filtered('active')
@api.depends('order_line')
def _compute_delivery_state(self):
delivery_line = self.order_line.filtered('is_delivery')
if delivery_line:
self.delivery_set = True

# TODO onchange sol, clean delivery price
@api.onchange('order_line', 'partner_id')
def onchange_order_line(self):
delivery_line = self.order_line.filtered('is_delivery')
if delivery_line:
self.recompute_delivery_price = True

@api.multi
def _action_confirm(self):
@@ -68,24 +45,45 @@ def _remove_delivery_line(self):
self.env['sale.order.line'].search([('order_id', 'in', self.ids), ('is_delivery', '=', True)]).unlink()

@api.multi
def set_delivery_line(self):
def set_delivery_line(self, carrier, amount):

# Remove delivery products from the sales order
self._remove_delivery_line()

for order in self:
if order.state not in ('draft', 'sent'):
raise UserError(_('You can add delivery price only on unconfirmed quotations.'))
elif not order.carrier_id:
raise UserError(_('No carrier set for this order.'))
elif not order.delivery_rating_success:
raise UserError(_('Please use "Check price" in order to compute a shipping price for this quotation.'))
else:
price_unit = order.carrier_id.rate_shipment(order)['price']
# TODO check whether it is safe to use delivery_price here
order._create_delivery_line(order.carrier_id, price_unit)
order._create_delivery_line(carrier, amount)
return True

def action_open_delivery_wizard(self):
view_id = self.env.ref('delivery.choose_delivery_carrier_view_form').id
return {
'name': _('Add a shipping method'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'choose.delivery.carrier',
'view_id': view_id,
'views': [(view_id, 'form')],
'target': 'new',
'context': {
'default_order_id': self.id,
'default_carrier_id': self.partner_id.property_delivery_carrier_id.id,
}
}

def recompute_delivery_cost(self):
delivery_line = self.order_line.filtered('is_delivery')
res = self.carrier_id.rate_shipment(self)
if self.carrier_id.invoice_policy == 'real':
delivery_line.name = self.carrier_id.with_context(lang=self.partner_id.lang).name
delivery_line.name += _(' (Estimated Cost: %s )') % self._format_currency_amount(res['price'])
else:
delivery_line.price_unit = res['price']
self.recompute_delivery_price = False
# self.delivery_price = res['price']

def _create_delivery_line(self, carrier, price_unit, force_estimated=False):
SaleOrderLine = self.env['sale.order.line']
if self.partner_id:
@@ -143,6 +141,7 @@ class SaleOrderLine(models.Model):

is_delivery = fields.Boolean(string="Is a Delivery", default=False)
product_qty = fields.Float(compute='_compute_product_qty', string='Quantity', digits=dp.get_precision('Product Unit of Measure'))
recompute_delivery_price = fields.Boolean(related='order_id.recompute_delivery_price')

@api.depends('product_id', 'product_uom', 'product_uom_qty')
def _compute_product_qty(self):
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-

from odoo.tests import common
from odoo.tests import common, Form
from odoo.tools import float_compare


@@ -51,7 +51,6 @@ def test_00_delivery_cost(self):
'product_uom': self.product_uom_unit.id,
'price_unit': 750.00,
})],
'carrier_id': self.normal_delivery.id
})
# I add delivery cost in Sales order

@@ -76,13 +75,17 @@ def test_00_delivery_cost(self):
})

# I add delivery cost in Sales order
self.sale_normal_delivery_charges.get_delivery_price()
self.sale_normal_delivery_charges.set_delivery_line()
delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({
'default_order_id': self.sale_normal_delivery_charges.id,
'default_carrier_id': self.normal_delivery.id
}))
choose_delivery_carrier = delivery_wizard.save()
choose_delivery_carrier.button_confirm()

# I check sales order after added delivery cost

line = self.SaleOrderLine.search([('order_id', '=', self.sale_normal_delivery_charges.id),
('product_id', '=', self.sale_normal_delivery_charges.carrier_id.product_id.id)])
('product_id', '=', self.normal_delivery.product_id.id)])
self.assertEqual(len(line), 1, "Delivery cost is not Added")

self.assertEqual(float_compare(line.price_subtotal, 10.0, precision_digits=2), 0,
@@ -112,16 +115,19 @@ def test_00_delivery_cost(self):
'product_uom': self.product_uom_hour.id,
'price_unit': 38.25,
})],
'carrier_id': self.free_delivery.id
})

# I add free delivery cost in Sales order
self.delivery_sale_order_cost.get_delivery_price()
self.delivery_sale_order_cost.set_delivery_line()
delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({
'default_order_id': self.delivery_sale_order_cost.id,
'default_carrier_id': self.free_delivery.id
}))
choose_delivery_carrier = delivery_wizard.save()
choose_delivery_carrier.button_confirm()

# I check sales order after adding delivery cost
line = self.SaleOrderLine.search([('order_id', '=', self.delivery_sale_order_cost.id),
('product_id', '=', self.delivery_sale_order_cost.carrier_id.product_id.id)])
('product_id', '=', self.free_delivery.product_id.id)])

self.assertEqual(len(line), 1, "Delivery cost is not Added")
self.assertEqual(float_compare(line.price_subtotal, 0, precision_digits=2), 0,
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-

from odoo.addons.account.tests.account_test_classes import AccountingTestCase
from odoo.tests import tagged
from odoo.tests import tagged, Form


@tagged('post_install', '-at_install')
@@ -38,12 +38,15 @@ def test_01_delivery_stock_move(self):
'product_uom': self.product_uom_kgm.id,
'price_unit': 750.00,
})],
'carrier_id': self.normal_delivery.id
})

# I add delivery cost in Sales order
self.sale_prepaid.get_delivery_price()
self.sale_prepaid.set_delivery_line()
delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({
'default_order_id': self.sale_prepaid.id,
'default_carrier_id': self.normal_delivery.id,
}))
choose_delivery_carrier = delivery_wizard.save()
choose_delivery_carrier.button_confirm()

# I confirm the SO.
self.sale_prepaid.action_confirm()
@@ -292,27 +292,50 @@
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='payment_term_id']" position="after">
<field name="id" invisible="1"/>
<field name="available_carrier_ids" invisible="1"/>
<label for="carrier_id"/>
<div name='carrier_selection'>
<div>
<field name="carrier_id" domain="[('id', 'in', available_carrier_ids)]" context="{'order_id': id}" class="oe_inline" options="{'no_create': True, 'no_open': True}" attrs="{'readonly':[('state','not in',('draft','sent'))]}"/> <i class="fa fa-check text-success" role="img" aria-label="Rating OK" title="Rating OK" attrs="{'invisible':['|','|',('carrier_id','=',False),('state','not in',('draft','sent')),('delivery_rating_success','=',False)]}"></i>
</div>
<div>
<field name='delivery_price' widget='monetary' class="oe_inline" options="{'currency_field': 'currency_id'}" nolabel="1" attrs="{'invisible': [('carrier_id','=', False)]}" force_save="1"/>
<button name="get_delivery_price" type="object" class="oe_inline oe_link" attrs="{'invisible':['|',('carrier_id','=',False),('state','not in',('draft','sent'))]}"><i class="fa fa-arrow-right"/> Get rate</button>
<button name="set_delivery_line" type="object" class="oe_inline oe_link" attrs="{'invisible':['|','|',('carrier_id','=',False),('state','not in',('draft','sent')),('delivery_rating_success','=',False)]}"><i class="fa fa-arrow-right"/> Add to order</button>
<field name='delivery_rating_success' invisible="1" force_save="1"/>
</div>
<div class="alert alert-info" role="status" attrs="{'invisible': ['|',('carrier_id','=', False),('delivery_message','=',False)]}">
<div class="alert alert-info" role="status" attrs="{'invisible': [('delivery_message','=',False)]}">
<field name='delivery_message' force_save="1"/>
</div>
</div>
</xpath>
<xpath expr="//field[@name='product_uom_qty']" position="after">
<field name="product_qty" invisible="1"/>
</xpath>
<xpath expr="//field[@name='partner_id']" position='after'>
<field name="delivery_set" invisible="1"/>
<field name="recompute_delivery_price" invisible="1"/>
</xpath>
<xpath expr="//group[@name='note_group']" position="before">
<div class="oe_left" style="margin-left: 10px">
<button
string="Add a shipping method"
name="action_open_delivery_wizard"
type="object"
attrs="{'invisible': ['|', '|', ('order_line', '=', []), ('delivery_set', '=', True), ('state', 'not in', ('draft', 'sent'))]}"
/>
<button
string="Recompute shipping cost"
name="recompute_delivery_cost"
type="object"
class="text-warning btn-secondary"
attrs="{'invisible': ['|', '|', ('recompute_delivery_price', '=', False), ('delivery_set', '=', False), ('state', 'not in', ('draft', 'sent'))]}"
/>
<button
string="Recompute shipping cost"
name="recompute_delivery_cost"
type="object"
attrs="{'invisible': ['|', '|', ('recompute_delivery_price', '=', True), ('delivery_set', '=', False), ('state', 'not in', ('draft', 'sent'))]}"
/>
</div>
</xpath>
<xpath expr="//field[@name='order_line']/form/group/group/field[@name='price_unit']" position="before">
<field name="recompute_delivery_price" invisible="1"/>
<field name="is_delivery" invisible="1"/>
</xpath>
<xpath expr="//field[@name='order_line']/tree/field[@name='price_unit']" position="before">
<field name="recompute_delivery_price" invisible="1"/>
<field name="is_delivery" invisible="1"/>
</xpath>
<xpath expr="//field[@name='order_line']/tree" position="attributes">
<attribute name="decoration-warning">recompute_delivery_price and is_delivery</attribute>
</xpath>
</data>
</field>
</record>
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import choose_delivery_package
from . import choose_delivery_carrier
@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import fields, models, api, _
from odoo.exceptions import UserError


class ChooseDeliveryCarrier(models.TransientModel):
_name = 'choose.delivery.carrier'
_description = 'Delivery Carrier Selection Wizard'

order_id = fields.Many2one('sale.order', required=True, ondelete="cascade")
partner_id = fields.Many2one('res.partner', related='order_id.partner_id', required=True)
carrier_id = fields.Many2one(
'delivery.carrier',
string="Shipping Method",
help="Choose the method to deliver your goods",
required=True,
)
delivery_type = fields.Selection(related='carrier_id.delivery_type')
delivery_price = fields.Float(string='Cost', readonly=True)
currency_id = fields.Many2one('res.currency', related='order_id.currency_id')
available_carrier_ids = fields.Many2many("delivery.carrier", compute='_compute_available_carrier', string="Available Carriers")
invoicing_message = fields.Text(compute='_compute_invoicing_message')

@api.onchange('carrier_id')
def _onchange_carrier_id(self):
if self.delivery_type in ('fixed', 'base_on_rule'):
vals = self.carrier_id.rate_shipment(self.order_id)
if vals.get('success'):
if vals['warning_message']:
self.order_id.delivery_message = vals['warning_message']
else:
self.delivery_price = vals['price']
else:
return {'error': vals['error_message']}
else:
self.delivery_price = 0

@api.depends('carrier_id')
def _compute_invoicing_message(self):
self.ensure_one()
if self.carrier_id.invoice_policy == 'real':
self.invoicing_message = _('The shipping price will be set once the delivery is done.')
else:
self.invoicing_message = ""

@api.depends('partner_id')
def _compute_available_carrier(self):
carriers = self.env['delivery.carrier'].search([])
for rec in self:
rec.available_carrier_ids = carriers.available_carriers(rec.partner_id) if rec.partner_id else carriers

def update_price(self):
vals = self.carrier_id.rate_shipment(self.order_id)
if vals.get('success'):
if vals['warning_message']:
self.order_id.delivery_message = vals['warning_message']
self.delivery_price = vals['price']
else:
raise UserError(vals['error_message'])
return {
'name': _('Add a shipping method'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'choose.delivery.carrier',
'res_id': self.id,
'target': 'new',
}

def button_confirm(self):
self.order_id.carrier_id = self.carrier_id
self.order_id.set_delivery_line(self.carrier_id, self.delivery_price)
Oops, something went wrong.

0 comments on commit 2d489ca

Please sign in to comment.