[FIX] sale_*, delivery: more consistent ignore of cancelled SO lines
Sales Order lines have a cancelled state, but this state is not always considered when looping over lines. This check is done in some places already and this patch's aim is to do it in the remaining places. - Cancel the procurement of a sale line in sale.order.line instead of sale.order, so a line canceled individually with sale_order_line.button_cancel will properly cancel it procurement. - Sale report: uses the state of lines instead of Sales order, so canceled lines of not-canceled orders are correctly represented in the analysis. - test: do not create invoices lines for canceled sale lines - test: creation of moves with canceled lines - test: check if lines are still canceled when sale order is done Closes #6036
This commit is contained in:
parent
a7cb326e5a
commit
3a314780c0
|
@ -211,6 +211,8 @@ class delivery_grid(osv.osv):
|
|||
total_delivery = 0.0
|
||||
product_uom_obj = self.pool.get('product.uom')
|
||||
for line in order.order_line:
|
||||
if line.state == 'cancel':
|
||||
continue
|
||||
if line.is_delivery:
|
||||
total_delivery += line.price_subtotal + self.pool['sale.order']._amount_line_tax(cr, uid, line, context=context)
|
||||
if not line.product_id or line.is_delivery:
|
||||
|
|
|
@ -105,6 +105,8 @@ class sale_order_line(osv.osv):
|
|||
context = dict(context or {})
|
||||
registration_obj = self.pool.get('event.registration')
|
||||
for order_line in self.browse(cr, uid, ids, context=context):
|
||||
if order_line.state == 'cancel':
|
||||
continue
|
||||
if order_line.event_id:
|
||||
dic = {
|
||||
'name': order_line.order_id.partner_invoice_id.name,
|
||||
|
|
|
@ -86,6 +86,7 @@ The Dashboard for the Sales Manager will include
|
|||
'test/cancel_order.yml',
|
||||
'test/delete_order.yml',
|
||||
'test/edi_sale_order.yml',
|
||||
'test/canceled_lines_order.yml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
|
|
|
@ -72,7 +72,7 @@ class sale_report(osv.osv):
|
|||
s.user_id as user_id,
|
||||
s.company_id as company_id,
|
||||
extract(epoch from avg(date_trunc('day',s.date_confirm)-date_trunc('day',s.create_date)))/(24*60*60)::decimal(16,2) as delay,
|
||||
s.state,
|
||||
l.state,
|
||||
t.categ_id as categ_id,
|
||||
s.pricelist_id as pricelist_id,
|
||||
s.project_id as analytic_account_id,
|
||||
|
@ -102,7 +102,7 @@ class sale_report(osv.osv):
|
|||
s.partner_id,
|
||||
s.user_id,
|
||||
s.company_id,
|
||||
s.state,
|
||||
l.state,
|
||||
s.pricelist_id,
|
||||
s.project_id,
|
||||
s.section_id
|
||||
|
|
|
@ -501,6 +501,8 @@ class sale_order(osv.osv):
|
|||
|
||||
def test_no_product(self, cr, uid, order, context):
|
||||
for line in order.order_line:
|
||||
if line.state == 'cancel':
|
||||
continue
|
||||
if line.product_id and (line.product_id.type<>'service'):
|
||||
return False
|
||||
return True
|
||||
|
@ -584,7 +586,6 @@ class sale_order(osv.osv):
|
|||
context = {}
|
||||
sale_order_line_obj = self.pool.get('sale.order.line')
|
||||
account_invoice_obj = self.pool.get('account.invoice')
|
||||
procurement_obj = self.pool.get('procurement.order')
|
||||
for sale in self.browse(cr, uid, ids, context=context):
|
||||
for inv in sale.invoice_ids:
|
||||
if inv.state not in ('draft', 'cancel'):
|
||||
|
@ -592,9 +593,8 @@ class sale_order(osv.osv):
|
|||
_('Cannot cancel this sales order!'),
|
||||
_('First cancel all invoices attached to this sales order.'))
|
||||
inv.signal_workflow('invoice_cancel')
|
||||
procurement_obj.cancel(cr, uid, sum([l.procurement_ids.ids for l in sale.order_line],[]))
|
||||
sale_order_line_obj.write(cr, uid, [l.id for l in sale.order_line],
|
||||
{'state': 'cancel'})
|
||||
line_ids = [l.id for l in sale.order_line if l.state != 'cancel']
|
||||
sale_order_line_obj.button_cancel(cr, uid, line_ids, context=context)
|
||||
self.write(cr, uid, ids, {'state': 'cancel'})
|
||||
return True
|
||||
|
||||
|
@ -606,14 +606,14 @@ class sale_order(osv.osv):
|
|||
def action_wait(self, cr, uid, ids, context=None):
|
||||
context = context or {}
|
||||
for o in self.browse(cr, uid, ids):
|
||||
if not o.order_line:
|
||||
if not any(line.state != 'cancel' for line in o.order_line):
|
||||
raise osv.except_osv(_('Error!'),_('You cannot confirm a sales order which has no line.'))
|
||||
noprod = self.test_no_product(cr, uid, o, context)
|
||||
if (o.order_policy == 'manual') or noprod:
|
||||
self.write(cr, uid, [o.id], {'state': 'manual', 'date_confirm': fields.date.context_today(self, cr, uid, context=context)})
|
||||
else:
|
||||
self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': fields.date.context_today(self, cr, uid, context=context)})
|
||||
self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line])
|
||||
self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line if x.state != 'cancel'])
|
||||
return True
|
||||
|
||||
def action_quotation_send(self, cr, uid, ids, context=None):
|
||||
|
@ -652,7 +652,7 @@ class sale_order(osv.osv):
|
|||
|
||||
def action_done(self, cr, uid, ids, context=None):
|
||||
for order in self.browse(cr, uid, ids, context=context):
|
||||
self.pool.get('sale.order.line').write(cr, uid, [line.id for line in order.order_line], {'state': 'done'}, context=context)
|
||||
self.pool.get('sale.order.line').write(cr, uid, [line.id for line in order.order_line if line.state != 'cancel'], {'state': 'done'}, context=context)
|
||||
return self.write(cr, uid, ids, {'state': 'done'}, context=context)
|
||||
|
||||
def _prepare_order_line_procurement(self, cr, uid, order, line, group_id=False, context=None):
|
||||
|
@ -685,7 +685,7 @@ class sale_order(osv.osv):
|
|||
sale_line_obj = self.pool.get('sale.order.line')
|
||||
res = []
|
||||
for order in self.browse(cr, uid, ids, context=context):
|
||||
res.append(sale_line_obj.need_procurement(cr, uid, [line.id for line in order.order_line], context=context))
|
||||
res.append(sale_line_obj.need_procurement(cr, uid, [line.id for line in order.order_line if line.state != 'cancel'], context=context))
|
||||
return any(res)
|
||||
|
||||
def action_ignore_delivery_exception(self, cr, uid, ids, context=None):
|
||||
|
@ -712,6 +712,8 @@ class sale_order(osv.osv):
|
|||
order.write({'procurement_group_id': group_id})
|
||||
|
||||
for line in order.order_line:
|
||||
if line.state == 'cancel':
|
||||
continue
|
||||
#Try to fix exception procurement (possible when after a shipping exception the user choose to recreate)
|
||||
if line.procurement_ids:
|
||||
#first check them to see if they are in exception or not (one of the related moves is cancelled)
|
||||
|
@ -793,6 +795,8 @@ class sale_order(osv.osv):
|
|||
def test_procurements_done(self, cr, uid, ids, context=None):
|
||||
for sale in self.browse(cr, uid, ids, context=context):
|
||||
for line in sale.order_line:
|
||||
if line.state == 'cancel':
|
||||
continue
|
||||
if not all([x.state == 'done' for x in line.procurement_ids]):
|
||||
return False
|
||||
return True
|
||||
|
@ -800,6 +804,8 @@ class sale_order(osv.osv):
|
|||
def test_procurements_except(self, cr, uid, ids, context=None):
|
||||
for sale in self.browse(cr, uid, ids, context=context):
|
||||
for line in sale.order_line:
|
||||
if line.state == 'cancel':
|
||||
continue
|
||||
if any([x.state == 'cancel' for x in line.procurement_ids]):
|
||||
return True
|
||||
return False
|
||||
|
@ -995,9 +1001,12 @@ class sale_order_line(osv.osv):
|
|||
return create_ids
|
||||
|
||||
def button_cancel(self, cr, uid, ids, context=None):
|
||||
for line in self.browse(cr, uid, ids, context=context):
|
||||
lines = self.browse(cr, uid, ids, context=context)
|
||||
for line in lines:
|
||||
if line.invoiced:
|
||||
raise osv.except_osv(_('Invalid Action!'), _('You cannot cancel a sales order line that has already been invoiced.'))
|
||||
procurement_obj = self.pool['procurement.order']
|
||||
procurement_obj.cancel(cr, uid, sum([l.procurement_ids.ids for l in lines], []), context=context)
|
||||
return self.write(cr, uid, ids, {'state': 'cancel'})
|
||||
|
||||
def button_confirm(self, cr, uid, ids, context=None):
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
-
|
||||
I create a draft Sale Order with 2 lines but 1 canceled in order to check if the canceled lines are not considered in the logic
|
||||
-
|
||||
!record {model: sale.order, id: sale_order_cl_2}:
|
||||
partner_id: base.res_partner_15
|
||||
partner_invoice_id: base.res_partner_address_25
|
||||
partner_shipping_id: base.res_partner_address_25
|
||||
pricelist_id: product.list0
|
||||
order_policy: manual
|
||||
-
|
||||
!record {model: sale.order.line, id: sale_order_cl_2_line_1}:
|
||||
order_id: sale_order_cl_2
|
||||
product_id: product.product_product_27
|
||||
product_uom_qty: 1
|
||||
product_uom: 1
|
||||
price_unit: 3645
|
||||
name: 'Laptop Customized'
|
||||
-
|
||||
!record {model: sale.order.line, id: sale_order_cl_2_line_2}:
|
||||
order_id: sale_order_cl_2
|
||||
product_id: product.product_product_12
|
||||
product_uom_qty: 1
|
||||
product_uom: 1
|
||||
price_unit: 12.50
|
||||
name: 'Mouse, Wireless'
|
||||
-
|
||||
I cancel the first line
|
||||
-
|
||||
!python {model: sale.order.line, id: sale_order_cl_2_line_1}: |
|
||||
self.button_cancel()
|
||||
-
|
||||
I confirm the sale order
|
||||
-
|
||||
!workflow {model: sale.order, action: order_confirm, ref: sale_order_cl_2}
|
||||
-
|
||||
Invoice the whole sale order
|
||||
-
|
||||
!python {model: sale.advance.payment.inv}: |
|
||||
ctx = context.copy()
|
||||
ctx.update({"active_model": 'sale.order',
|
||||
"active_ids": [ref("sale_order_cl_2")],
|
||||
"active_id":ref("sale_order_cl_2")})
|
||||
pay_id = self.create(cr, uid, {'advance_payment_method': 'all'})
|
||||
self.create_invoices(cr, uid, [pay_id], context=ctx)
|
||||
-
|
||||
I check the invoice
|
||||
-
|
||||
!python {model: sale.order, id: sale_order_cl_2}: |
|
||||
invoice = self.invoice_ids
|
||||
assert len(invoice.invoice_line) == 1, "Only 1 line should be invoiced because the other one is canceled, got %d" % len(invoice.invoice_line)
|
||||
-
|
||||
I set the sale to done
|
||||
-
|
||||
!python {model: sale.order, id: sale_order_cl_2}: |
|
||||
self.action_done()
|
||||
-
|
||||
And check if the canceled line is still canceled
|
||||
-
|
||||
!assert {model: sale.order.line, id: sale_order_cl_2_line_1, string: The canceled line should still be canceled}:
|
||||
- state == 'cancel'
|
|
@ -102,7 +102,7 @@ class sale_order_line_make_invoice(osv.osv_memory):
|
|||
sales_order_obj.message_post(cr, uid, [order.id], body=_("Invoice created"), context=context)
|
||||
data_sale = sales_order_obj.browse(cr, uid, order.id, context=context)
|
||||
for line in data_sale.order_line:
|
||||
if not line.invoiced:
|
||||
if not line.invoiced and line.state != 'cancel':
|
||||
flag = False
|
||||
break
|
||||
if flag:
|
||||
|
|
|
@ -71,6 +71,8 @@ class sale_order(osv.osv):
|
|||
for sale in self.browse(cr, uid, ids, context=context):
|
||||
result[sale.id] = 0.0
|
||||
for line in sale.order_line:
|
||||
if line.state == 'cancel':
|
||||
continue
|
||||
result[sale.id] += line.margin or 0.0
|
||||
return result
|
||||
|
||||
|
|
|
@ -61,6 +61,8 @@ class sale_order_dates(osv.osv):
|
|||
dates_list = []
|
||||
order_datetime = datetime.strptime(order.date_order, DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
for line in order.order_line:
|
||||
if line.state == 'cancel':
|
||||
continue
|
||||
dt = order_datetime + timedelta(days=line.delay or 0.0)
|
||||
dt_s = dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
dates_list.append(dt_s)
|
||||
|
|
|
@ -62,6 +62,7 @@ You can choose flexible invoicing methods:
|
|||
'test/picking_order_policy.yml',
|
||||
'test/prepaid_order_policy.yml',
|
||||
'test/sale_order_onchange.yml',
|
||||
'test/sale_order_canceled_line.yml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
|
|
|
@ -182,6 +182,8 @@ class sale_order(osv.osv):
|
|||
def has_stockable_products(self, cr, uid, ids, *args):
|
||||
for order in self.browse(cr, uid, ids):
|
||||
for order_line in order.order_line:
|
||||
if order_line.state == 'cancel':
|
||||
continue
|
||||
if order_line.product_id and order_line.product_id.type in ('product', 'consu'):
|
||||
return True
|
||||
return False
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
-
|
||||
I create a draft Sale Order with 2 lines but 1 canceled in order to check if the canceled lines are not considered in the logic
|
||||
-
|
||||
!record {model: sale.order, id: sale_order_cl_3}:
|
||||
partner_id: base.res_partner_15
|
||||
partner_invoice_id: base.res_partner_address_25
|
||||
partner_shipping_id: base.res_partner_address_25
|
||||
pricelist_id: product.list0
|
||||
order_policy: manual
|
||||
-
|
||||
!record {model: sale.order.line, id: sale_order_cl_3_line_1}:
|
||||
order_id: sale_order_cl_3
|
||||
product_id: product.product_product_27
|
||||
product_uom_qty: 1
|
||||
product_uom: 1
|
||||
price_unit: 3645
|
||||
name: 'Laptop Customized'
|
||||
-
|
||||
!record {model: sale.order.line, id: sale_order_cl_3_line_2}:
|
||||
order_id: sale_order_cl_3
|
||||
product_id: product.product_product_12
|
||||
product_uom_qty: 1
|
||||
product_uom: 1
|
||||
price_unit: 12.50
|
||||
name: 'Mouse, Wireless'
|
||||
-
|
||||
I cancel the first line
|
||||
-
|
||||
!python {model: sale.order.line, id: sale_order_cl_3_line_1}: |
|
||||
self.button_cancel()
|
||||
-
|
||||
I confirm the sale order
|
||||
-
|
||||
!workflow {model: sale.order, action: order_confirm, ref: sale_order_cl_3}
|
||||
-
|
||||
I check that no procurement has been generated for the canceled line
|
||||
-
|
||||
!assert {model: sale.order.line, id: sale_order_cl_3_line_1, string: The canceled line should not have a procurement}:
|
||||
- not procurement_ids
|
||||
-
|
||||
I check that we have only 1 stock move, for the not canceled line
|
||||
-
|
||||
!python {model: sale.order, id: sale_order_cl_3}: |
|
||||
moves = self.picking_ids.mapped('move_lines')
|
||||
assert len(moves) == 1, "We should have 1 move, got %s" % len(moves)
|
Loading…
Reference in New Issue