[REV] sale: refund of bill re-invoiced
This reverts commit 33d95edd4832c4bd68bb2da2c4585e7b26db8a3c. Following of the next steps https://www.odoo.com/web#id=3338017&cids=1&model=project.task&view_type=form Initial opw-2992106 closes odoo/odoo#122998 Forward-port-of: #122431 X-original-commit: 9b50f49edaf1bee5cab0f42f37f92f4752654789 Signed-off-by: William André (wan) <wan@odoo.com> Signed-off-by: Yolann Sabaux (yosa) <yosa@odoo.com>
This commit is contained in:
@@ -48,16 +48,13 @@ class AccountMoveLine(models.Model):
|
||||
|
||||
def _sale_can_be_reinvoice(self):
|
||||
""" determine if the generated analytic line should be reinvoiced or not.
|
||||
For Vendor Bill flow, if the product has a 'reinvoice policy' and is a cost, then we will find the SO on which reinvoice the AAL
|
||||
if it is refund, we will update the quantity of the SO line
|
||||
For Vendor Bill flow, if the product has a 'erinvoice policy' and is a cost, then we will find the SO on which reinvoice the AAL
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.sale_line_ids:
|
||||
return False
|
||||
is_refund = self.move_id.move_type in ('out_refund', 'in_refund')
|
||||
return self.product_id.expense_policy not in [False, 'no'] and (
|
||||
(self.currency_id.compare_amounts(self.balance, 0.0) == -1 and is_refund)
|
||||
or (self.currency_id.compare_amounts(self.balance, 0.0) == 1 and not is_refund))
|
||||
uom_precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure')
|
||||
return float_compare(self.credit or 0.0, self.debit or 0.0, precision_digits=uom_precision_digits) != 1 and self.product_id.expense_policy not in [False, 'no']
|
||||
|
||||
def _sale_create_reinvoice_sale_line(self):
|
||||
|
||||
@@ -93,8 +90,7 @@ class AccountMoveLine(models.Model):
|
||||
|
||||
# find the existing sale.line or keep its creation values to process this in batch
|
||||
sale_line = None
|
||||
if (move_line.product_id.expense_policy == 'sales_price' and move_line.product_id.invoice_policy == 'delivery') \
|
||||
or move_line.move_id.move_type in ('out_refund', 'in_refund'): # for those case only, we can try to reuse one
|
||||
if move_line.product_id.expense_policy == 'sales_price' and move_line.product_id.invoice_policy == 'delivery': # for those case only, we can try to reuse one
|
||||
map_entry_key = (sale_order.id, move_line.product_id.id, price) # cache entry to limit the call to search
|
||||
sale_line = existing_sale_line_cache.get(map_entry_key)
|
||||
if sale_line: # already search, so reuse it. sale_line can be sale.order.line record or index of a "to create values" in `sale_line_values_to_create`
|
||||
|
||||
@@ -674,7 +674,7 @@ class SaleOrderLine(models.Model):
|
||||
"""
|
||||
# compute for analytic lines
|
||||
lines_by_analytic = self.filtered(lambda sol: sol.qty_delivered_method == 'analytic')
|
||||
mapping = lines_by_analytic._get_delivered_quantity_by_analytic([])
|
||||
mapping = lines_by_analytic._get_delivered_quantity_by_analytic([('amount', '<=', 0.0)])
|
||||
for so_line in lines_by_analytic:
|
||||
so_line.qty_delivered = mapping.get(so_line.id or so_line._origin.id, 0.0)
|
||||
|
||||
@@ -705,20 +705,29 @@ class SaleOrderLine(models.Model):
|
||||
|
||||
# group analytic lines by product uom and so line
|
||||
domain = expression.AND([[('so_line', 'in', self.ids)], additional_domain])
|
||||
analytic_lines = self.env['account.analytic.line'].search(domain)
|
||||
for line in analytic_lines:
|
||||
if not line.product_uom_id:
|
||||
data = self.env['account.analytic.line'].read_group(
|
||||
domain,
|
||||
['so_line', 'unit_amount', 'product_uom_id'], ['product_uom_id', 'so_line'], lazy=False
|
||||
)
|
||||
|
||||
# convert uom and sum all unit_amount of analytic lines to get the delivered qty of SO lines
|
||||
# browse so lines and product uoms here to make them share the same prefetch
|
||||
lines = self.browse([item['so_line'][0] for item in data])
|
||||
lines_map = {line.id: line for line in lines}
|
||||
product_uom_ids = [item['product_uom_id'][0] for item in data if item['product_uom_id']]
|
||||
product_uom_map = {uom.id: uom for uom in self.env['uom.uom'].browse(product_uom_ids)}
|
||||
for item in data:
|
||||
if not item['product_uom_id']:
|
||||
continue
|
||||
result.setdefault(line.so_line.id, 0.0)
|
||||
if line.so_line.product_uom.category_id == line.product_uom_id.category_id:
|
||||
qty = line.product_uom_id._compute_quantity(line.unit_amount, line.so_line.product_uom, rounding_method='HALF-UP')
|
||||
so_line_id = item['so_line'][0]
|
||||
so_line = lines_map[so_line_id]
|
||||
result.setdefault(so_line_id, 0.0)
|
||||
uom = product_uom_map.get(item['product_uom_id'][0])
|
||||
if so_line.product_uom.category_id == uom.category_id:
|
||||
qty = uom._compute_quantity(item['unit_amount'], so_line.product_uom, rounding_method='HALF-UP')
|
||||
else:
|
||||
qty = line.unit_amount
|
||||
|
||||
# if greater than 0 -> refund
|
||||
sign = line.amount and -line.amount / abs(line.amount) or 1
|
||||
|
||||
result[line.so_line.id] += sign * qty
|
||||
qty = item['unit_amount']
|
||||
result[so_line_id] += qty
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -40,17 +40,6 @@ class TestReInvoice(TestSaleCommon):
|
||||
mail_create_nolog=True,
|
||||
)
|
||||
|
||||
def _create_sol(self, sale_order, product):
|
||||
return self.env['sale.order.line'].create({
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 1,
|
||||
'qty_delivered': 0,
|
||||
'product_uom': product.uom_id.id,
|
||||
'price_unit': product.list_price,
|
||||
'order_id': sale_order.id,
|
||||
})
|
||||
|
||||
def test_at_cost(self):
|
||||
# Required for `analytic_account_id` to be visible in the view
|
||||
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
|
||||
@@ -289,57 +278,3 @@ class TestReInvoice(TestSaleCommon):
|
||||
self.assertFalse(so_line4, "No re-invoicing should have created a new sale line with product #2")
|
||||
self.assertEqual(so_line1.qty_delivered, 1, "No re-invoicing should have impacted exising SO line 1")
|
||||
self.assertEqual(so_line2.qty_delivered, 1, "No re-invoicing should have impacted exising SO line 2")
|
||||
|
||||
def test_refund_delivered_reinvoiced(self):
|
||||
"""
|
||||
Tests that when we refund a re-invoiced expense, the Quantity Delivered on the Sale Order Line is updated
|
||||
- (1) We create a Sale Order
|
||||
- (2) We create a bill to be re-invoiced
|
||||
- (3) We create a partial credit note
|
||||
-> The sale order lines created in (2) should be updated during (3) with the correct delivered quantity.
|
||||
"""
|
||||
# create the setup
|
||||
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
|
||||
product_1 = self.company_data['product_order_cost']
|
||||
product_2 = self.env['product.product'].create({
|
||||
'name': 'Great Product',
|
||||
'standard_price': 50.0,
|
||||
'list_price': 100.0,
|
||||
'type': 'consu',
|
||||
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
'uom_po_id': self.env.ref('uom.product_uom_unit').id,
|
||||
'invoice_policy': 'order',
|
||||
'expense_policy': 'cost',
|
||||
})
|
||||
sol_1 = self._create_sol(self.sale_order, product_1)
|
||||
sol_1.analytic_distribution = {self.analytic_account.id: 100}
|
||||
sol_2 = self._create_sol(self.sale_order, product_2)
|
||||
sol_2.analytic_distribution = {self.analytic_account.id: 100}
|
||||
self.sale_order.action_confirm()
|
||||
|
||||
# create the bill to be re-invoiced
|
||||
bill_form = Form(self.AccountMove.with_context(default_move_type='in_invoice'))
|
||||
bill_form.partner_id = self.partner_b
|
||||
with bill_form.invoice_line_ids.new() as line_form:
|
||||
line_form.product_id = product_1
|
||||
line_form.quantity = 20.0
|
||||
line_form.analytic_distribution = {self.analytic_account.id: 100}
|
||||
with bill_form.invoice_line_ids.new() as line_form:
|
||||
line_form.product_id = product_2
|
||||
line_form.quantity = 20.0
|
||||
line_form.analytic_distribution = {self.analytic_account.id: 100}
|
||||
bill = bill_form.save()
|
||||
bill.action_post()
|
||||
|
||||
self.assertRecordValues(self.sale_order.order_line[-2:], [{'qty_delivered': 20}, {'qty_delivered': 20}])
|
||||
|
||||
# create partial credit note
|
||||
rbill_form = Form(bill._reverse_moves([{'invoice_date': '2023-03-31'}]))
|
||||
with rbill_form.invoice_line_ids.edit(0) as line_form:
|
||||
line_form.quantity = 10
|
||||
with rbill_form.invoice_line_ids.edit(1) as line_form:
|
||||
line_form.quantity = 5
|
||||
rbill = rbill_form.save()
|
||||
rbill.action_post()
|
||||
|
||||
self.assertRecordValues(self.sale_order.order_line[-2:], [{'qty_delivered': 10}, {'qty_delivered': 15}])
|
||||
|
||||
Reference in New Issue
Block a user