1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
|
# -*- 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):
delivery_lines = self.env['sale.order.line'].search([('order_id', 'in', self.ids), ('is_delivery', '=', True)])
if not delivery_lines:
return
to_delete = delivery_lines.filtered(lambda x: x.qty_invoiced == 0)
if not to_delete:
raise UserError(
_('You can not update the shipping costs on an order where it was already invoiced!\n\nThe following delivery lines (product, invoiced quantity and price) have already been processed:\n\n')
+ '\n'.join(['- %s: %s x %s' % (line.product_id.with_context(display_default_code=False).display_name, line.qty_invoiced, line.price_unit) for line in delivery_lines])
)
to_delete.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
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)
|