If we're to change the invoice policy of a product we'd provoke a chained computation of every sale line and sale order containing such product. This change goes along with the policy of applying such changes only to future orders, as stated here: https://github.com/odoo/odoo/pull/61135 closes odoo/odoo#64641 X-original-commit: 806da3d67a5cf9919c6c0f5042b3e00c69825faa Signed-off-by: Martin Trigaux (mat) <mat@odoo.com>
181 lines
7.6 KiB
Python
181 lines
7.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import models, fields, api, _
|
|
from odoo.exceptions import UserError
|
|
|
|
|
|
class SaleOrder(models.Model):
|
|
_inherit = 'sale.order'
|
|
|
|
carrier_id = fields.Many2one('delivery.carrier', string="Delivery Method", domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", help="Fill this field if you plan to invoice the shipping based on picking.")
|
|
delivery_message = fields.Char(readonly=True, copy=False)
|
|
delivery_rating_success = fields.Boolean(copy=False)
|
|
delivery_set = fields.Boolean(compute='_compute_delivery_state')
|
|
recompute_delivery_price = fields.Boolean('Delivery cost should be recomputed')
|
|
is_all_service = fields.Boolean("Service Product", compute="_compute_is_service_products")
|
|
|
|
@api.depends('order_line')
|
|
def _compute_is_service_products(self):
|
|
for so in self:
|
|
so.is_all_service = all(line.product_id.type == 'service' for line in so.order_line)
|
|
|
|
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('order_line')
|
|
def _compute_delivery_state(self):
|
|
for order in self:
|
|
order.delivery_set = any(line.is_delivery for line in order.order_line)
|
|
|
|
@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
|
|
|
|
def _remove_delivery_line(self):
|
|
self.env['sale.order.line'].search([('order_id', 'in', self.ids), ('is_delivery', '=', True)]).unlink()
|
|
|
|
def set_delivery_line(self, carrier, amount):
|
|
|
|
# Remove delivery products from the sales order
|
|
self._remove_delivery_line()
|
|
|
|
for order in self:
|
|
order.carrier_id = carrier.id
|
|
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
|
|
if self.env.context.get('carrier_recompute'):
|
|
name = _('Update shipping cost')
|
|
carrier = self.carrier_id
|
|
else:
|
|
name = _('Add a shipping method')
|
|
carrier = (
|
|
self.with_company(self.company_id).partner_shipping_id.property_delivery_carrier_id
|
|
or self.with_company(self.company_id).partner_shipping_id.commercial_partner_id.property_delivery_carrier_id
|
|
)
|
|
return {
|
|
'name': name,
|
|
'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': carrier.id,
|
|
}
|
|
}
|
|
|
|
def _create_delivery_line(self, carrier, price_unit):
|
|
SaleOrderLine = self.env['sale.order.line']
|
|
if self.partner_id:
|
|
# set delivery detail in the customer language
|
|
carrier = carrier.with_context(lang=self.partner_id.lang)
|
|
|
|
# Apply fiscal position
|
|
taxes = carrier.product_id.taxes_id.filtered(lambda t: t.company_id.id == self.company_id.id)
|
|
taxes_ids = taxes.ids
|
|
if self.partner_id and self.fiscal_position_id:
|
|
taxes_ids = self.fiscal_position_id.map_tax(taxes, carrier.product_id, self.partner_id).ids
|
|
|
|
# Create the sales order line
|
|
carrier_with_partner_lang = carrier.with_context(lang=self.partner_id.lang)
|
|
if carrier_with_partner_lang.product_id.description_sale:
|
|
so_description = '%s: %s' % (carrier_with_partner_lang.name,
|
|
carrier_with_partner_lang.product_id.description_sale)
|
|
else:
|
|
so_description = carrier_with_partner_lang.name
|
|
values = {
|
|
'order_id': self.id,
|
|
'name': so_description,
|
|
'product_uom_qty': 1,
|
|
'product_uom': carrier.product_id.uom_id.id,
|
|
'product_id': carrier.product_id.id,
|
|
'tax_id': [(6, 0, taxes_ids)],
|
|
'is_delivery': True,
|
|
}
|
|
if carrier.invoice_policy == 'real':
|
|
values['price_unit'] = 0
|
|
values['name'] += _(' (Estimated Cost: %s )', self._format_currency_amount(price_unit))
|
|
else:
|
|
values['price_unit'] = price_unit
|
|
if carrier.free_over and self.currency_id.is_zero(price_unit) :
|
|
values['name'] += '\n' + 'Free Shipping'
|
|
if self.order_line:
|
|
values['sequence'] = self.order_line[-1].sequence + 1
|
|
sol = SaleOrderLine.sudo().create(values)
|
|
return sol
|
|
|
|
def _format_currency_amount(self, amount):
|
|
pre = post = u''
|
|
if self.currency_id.position == 'before':
|
|
pre = u'{symbol}\N{NO-BREAK SPACE}'.format(symbol=self.currency_id.symbol or '')
|
|
else:
|
|
post = u'\N{NO-BREAK SPACE}{symbol}'.format(symbol=self.currency_id.symbol or '')
|
|
return u' {pre}{0}{post}'.format(amount, pre=pre, post=post)
|
|
|
|
@api.depends('order_line.is_delivery', 'order_line.is_downpayment')
|
|
def _get_invoice_status(self):
|
|
super()._get_invoice_status()
|
|
for order in self:
|
|
if order.invoice_status in ['no', 'invoiced']:
|
|
continue
|
|
order_lines = order.order_line.filtered(lambda x: not x.is_delivery and not x.is_downpayment and not x.display_type)
|
|
if all(line.product_id.invoice_policy == 'delivery' and line.invoice_status == 'no' for line in order_lines):
|
|
order.invoice_status = 'no'
|
|
|
|
def _get_estimated_weight(self):
|
|
self.ensure_one()
|
|
weight = 0.0
|
|
for order_line in self.order_line.filtered(lambda l: l.product_id.type in ['product', 'consu'] and not l.is_delivery and not l.display_type):
|
|
weight += order_line.product_qty * order_line.product_id.weight
|
|
return weight
|
|
|
|
|
|
class SaleOrderLine(models.Model):
|
|
_inherit = 'sale.order.line'
|
|
|
|
is_delivery = fields.Boolean(string="Is a Delivery", default=False)
|
|
product_qty = fields.Float(compute='_compute_product_qty', string='Product Qty', digits='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):
|
|
for line in self:
|
|
if not line.product_id or not line.product_uom or not line.product_uom_qty:
|
|
line.product_qty = 0.0
|
|
continue
|
|
line.product_qty = line.product_uom._compute_quantity(line.product_uom_qty, line.product_id.uom_id)
|
|
|
|
def unlink(self):
|
|
for line in self:
|
|
if line.is_delivery:
|
|
line.order_id.carrier_id = False
|
|
return super(SaleOrderLine, self).unlink()
|
|
|
|
def _is_delivery(self):
|
|
self.ensure_one()
|
|
return self.is_delivery
|
|
|
|
# override to allow deletion of delivery line in a confirmed order
|
|
def _check_line_unlink(self):
|
|
"""
|
|
Extend the allowed deletion policy of SO lines.
|
|
|
|
Lines that are delivery lines can be deleted from a confirmed order.
|
|
|
|
:rtype: recordset sale.order.line
|
|
:returns: set of lines that cannot be deleted
|
|
"""
|
|
|
|
undeletable_lines = super()._check_line_unlink()
|
|
return undeletable_lines.filtered(lambda line: not line.is_delivery)
|