[FIX] sale_expense: Fix price_unit used in mapping key

No rounding in the query used to map sale.order.line to
the hr.expense, models leads to some records not being able
to be linked together, because of floating point errors.

Adding a rounding to the key price_unit,
and not filtering on price_unit. Then, using the rounded string versions
of the price_unit in the comparisons adds a more reliable approach.

task-3705179

closes odoo/odoo#162245

X-original-commit: 0f4705be195ac66e1cdf5af39fd509035929556c
Signed-off-by: William André (wan) <wan@odoo.com>
Signed-off-by: Julien Alardot (jual) <jual@odoo.com>
This commit is contained in:
Julien Alardot (jual)
2024-04-18 07:28:30 +00:00
parent 0217b88e48
commit 442e032cce
2 changed files with 415 additions and 216 deletions
+56 -29
View File
@@ -1,4 +1,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import Counter
from psycopg2.extras import execute_values
from odoo import fields, models, _ from odoo import fields, models, _
@@ -15,42 +18,66 @@ class HrExpenseSheet(models.Model):
def _get_sale_order_lines(self): def _get_sale_order_lines(self):
""" """
This method is used to try to find the sale order lines created by expense sheets. This method is used to try to find the sale order lines created by expense sheets.
It is used to reset the quantities of the sale order lines when the expense sheet is reset.
:return: sale.order.line It uses several shared fields to try to find the sale order lines:
:rtype: recordset - order_id
- product_id
- product_uom_qty
- sale order line's price_unit (computed from the product_id, then rounded to the currency's rounding)
- name
""" """
expensed_amls = self.account_move_ids.line_ids.filtered(lambda aml: aml.expense_id.sale_order_id and aml.balance >= 0) # Get the product account move lines created by an expense
expensed_amls = self.account_move_ids.line_ids.filtered(lambda aml: aml.expense_id.sale_order_id and aml.balance >= 0 and not aml.tax_line_id)
if not expensed_amls: if not expensed_amls:
return self.env['sale.order.line'] return self.env['sale.order.line']
# Get the sale orders linked to the related expenses
aml_to_so_map = expensed_amls._sale_determine_order() aml_to_so_map = expensed_amls._sale_determine_order()
sale_order_ids = tuple(set(aml_to_so_map[aml.id].id for aml in expensed_amls))
aml_sol_unit_price_map = dict(expensed_amls.mapped(lambda aml: (aml.id, aml._sale_get_invoice_price(aml_to_so_map[aml.id]))))
product_ids = tuple(expensed_amls.product_id.ids)
quantities = tuple(expensed_amls.mapped('quantity'))
names = tuple(expensed_amls.mapped('name'))
self.env['sale.order.line'].flush_model(['order_id', 'product_id', 'product_uom_qty', 'price_unit', 'name']) self.env['sale.order.line'].flush_model(['order_id', 'product_id', 'product_uom_qty', 'price_unit', 'name'])
self.env['res.company'].flush_model(['currency_id'])
self.env['res.currency'].flush_model(['rounding'])
query = """ query = """
SELECT WITH aml(key_id, key_count, order_id, product_id, product_uom_qty, price_unit, name) AS (VALUES %s)
DISTINCT ON (sol.order_id, sol.product_id, sol.product_uom_qty, sol.price_unit, sol.name) SELECT ARRAY_AGG(sol.id ORDER BY sol.id), aml.key_count
sol.order_id, sol.product_id, sol.product_uom_qty, sol.price_unit, sol.name, sol.id FROM aml,
FROM sale_order_line AS sol sale_order_line AS sol
WHERE sol.is_expense = TRUE JOIN res_company AS company ON sol.company_id = company.id
AND sol.order_id IN %s JOIN res_currency AS company_currency ON company.currency_id = company_currency.id
AND sol.product_id IN %s LEFT JOIN res_currency AS currency ON sol.currency_id = currency.id
AND sol.product_uom_qty IN %s WHERE sol.is_expense = TRUE
AND sol.price_unit IN %s AND sol.order_id = aml.order_id
AND sol.name IN %s AND sol.product_id = aml.product_id
ORDER BY sol.order_id, sol.product_id, sol.product_uom_qty, sol.price_unit, sol.name AND sol.product_uom_qty = aml.product_uom_qty
AND sol.name = aml.name
AND ROUND(sol.price_unit::numeric, COALESCE(currency.rounding, company_currency.rounding)::int)
= ROUND(aml.price_unit::numeric, COALESCE(currency.rounding, company_currency.rounding)::int)
GROUP BY aml.key_id, aml.key_count
""" """
self.env.cr.execute(query, (sale_order_ids, product_ids, quantities, tuple(set(aml_sol_unit_price_map.values())), names))
potential_sols_map = { # Get the keys used to fetch the corresponding sale order lines, and the number of times they are used
(row['order_id'], row['product_id'], row['product_uom_qty'], row['price_unit'], row['name']): row['id'] # We need the occurrences count to filter out the sale order lines so that we keep exactly one per expense
for row in self.env.cr.dictfetchall() expense_keys_counter = Counter(expensed_amls.mapped(lambda aml: (
} aml.expense_id.sale_order_id.id,
expensed_amls_keys = set(expensed_amls.mapped( aml.product_id.id,
lambda aml: (aml.expense_id.sale_order_id.id, aml.product_id.id, aml.quantity, aml_sol_unit_price_map[aml.id], aml.name) aml.quantity,
)) aml.currency_id.round(aml._sale_get_invoice_price(aml_to_so_map[aml.id])),
return self.env['sale.order.line'].browse(sol_id for key, sol_id in potential_sols_map.items() if key in expensed_amls_keys) aml.name,
)))
expensed_amls_keys_and_count = tuple(
(key_id, key_count, *key) for key_id, (key, key_count) in enumerate(expense_keys_counter.items())
)
execute_values(
self.env.cr._obj,
query,
expensed_amls_keys_and_count,
)
# Filters out the sale order lines so that we only keep one per expense
sol_ids = []
for all_sol_ids_per_key, expense_count_per_key in self.env.cr.fetchall():
sol_ids += all_sol_ids_per_key[:expense_count_per_key]
return self.env['sale.order.line'].browse(sol_ids)
def _sale_expense_reset_sol_quantities(self): def _sale_expense_reset_sol_quantities(self):
sale_order_lines = self._get_sale_order_lines() sale_order_lines = self._get_sale_order_lines()
+359 -187
View File
@@ -8,239 +8,411 @@ from odoo.tests import tagged
@tagged('-at_install', 'post_install') @tagged('-at_install', 'post_install')
class TestReInvoice(TestExpenseCommon, TestSaleCommon): class TestReInvoice(TestExpenseCommon, TestSaleCommon):
"""
Test that expenses, when linked to a sale order and invoiced are correctly re-invoiced on the sale order.
It should cover the following rules:
- Lines are never grouped together (even if re-invoiced at sale price and with a re-invoice delivered policy)
- When posting the move of an expense, it creates the corresponding SOLs with the correct expense quantity
- The amount of analytic account linked do not impact the quantities on the SOLs
- The quantities ordered and delivered are reset to 0 when:
- the expense sheet move has been reset to draft
- the expense move is reversed
- the expense move has been reset to draft
- As it should be a one-to-one relation between model, we need to ensure that one expense only impacts one SOL
The test tries to cover all the possible combinations of expense and invoicing policies, as well as the different actions
"""
@classmethod @classmethod
def setUpClass(cls, chart_template_ref=None): def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref) super().setUpClass(chart_template_ref=chart_template_ref)
new_sale_tax, new_purchase_tax = cls.env['account.tax'].create([{
'name': 'Tax 12.499%',
'amount': 12.499,
'amount_type': 'percent',
'type_tax_use': tax_type,
'repartition_line_ids': [
Command.create({'document_type': 'invoice', 'repartition_type': 'base'}),
Command.create({
'document_type': 'invoice',
'repartition_type': 'tax',
'account_id': cls.company_data[f'default_account_tax_{tax_type}'].id
}),
Command.create({'document_type': 'refund', 'repartition_type': 'base'}),
Command.create({
'document_type': 'refund',
'repartition_type': 'tax',
'account_id': cls.company_data[f'default_account_tax_{tax_type}'].id
}),
],
} for tax_type in ('sale', 'purchase')])
cls.company_data.update({ cls.company_data.update({
'service_order_sales_price': cls.env['product.product'].with_company(cls.company_data['company']).create({ 'service_order_sales_price': cls.env['product.product'].with_company(cls.company_data['company']).create({
'name': 'service_order_sales_price', 'name': 'service_order_sales_price',
'categ_id': cls.company_data['product_category'].id, 'categ_id': cls.company_data['product_category'].id,
'standard_price': 235.0, 'standard_price': 0.,
'list_price': 280.0, 'list_price': 280.39,
'type': 'service', 'type': 'service',
'weight': 0.01, 'weight': 0.01,
'uom_id': cls.env.ref('uom.product_uom_unit').id, 'uom_id': cls.env.ref('uom.product_uom_unit').id,
'uom_po_id': cls.env.ref('uom.product_uom_unit').id, 'uom_po_id': cls.env.ref('uom.product_uom_unit').id,
'default_code': 'FURN_9999', 'default_code': 'FURN_99991',
'invoice_policy': 'order', 'invoice_policy': 'order',
'expense_policy': 'sales_price', 'expense_policy': 'sales_price',
'taxes_id': [Command.set([])], 'taxes_id': [Command.set([new_sale_tax.id])],
'supplier_taxes_id': [Command.set([])], 'supplier_taxes_id': [Command.set([new_purchase_tax.id])],
'can_be_expensed': True, 'can_be_expensed': True,
}), }),
'service_delivery_sales_price': cls.env['product.product'].with_company(cls.company_data['company']).create({ 'service_delivery_sales_price': cls.env['product.product'].with_company(cls.company_data['company']).create({
'name': 'service_order_sales_price', 'name': 'service_order_sales_price',
'categ_id': cls.company_data['product_category'].id, 'categ_id': cls.company_data['product_category'].id,
'standard_price': 235.0, 'standard_price': 0.,
'list_price': 280.0, 'list_price': 280.39,
'type': 'service', 'type': 'service',
'weight': 0.01, 'weight': 0.01,
'uom_id': cls.env.ref('uom.product_uom_unit').id, 'uom_id': cls.env.ref('uom.product_uom_unit').id,
'uom_po_id': cls.env.ref('uom.product_uom_unit').id, 'uom_po_id': cls.env.ref('uom.product_uom_unit').id,
'default_code': 'FURN_9999', 'default_code': 'FURN_99992',
'invoice_policy': 'order', 'invoice_policy': 'delivery',
'expense_policy': 'sales_price', 'expense_policy': 'sales_price',
'taxes_id': [Command.set([])], 'taxes_id': [Command.set([new_sale_tax.id])],
'supplier_taxes_id': [Command.set([])], 'supplier_taxes_id': [Command.set([new_purchase_tax.id])],
'can_be_expensed': True,
}),
'service_delivery_cost_price': cls.env['product.product'].with_company(cls.company_data['company']).create({
'name': 'service_delivery_cost_price',
'categ_id': cls.company_data['product_category'].id,
'standard_price': 235.28,
'list_price': 280.39,
'type': 'service',
'weight': 0.01,
'uom_id': cls.env.ref('uom.product_uom_unit').id,
'uom_po_id': cls.env.ref('uom.product_uom_unit').id,
'default_code': 'FURN_99993',
'invoice_policy': 'delivery',
'expense_policy': 'cost',
'taxes_id': [Command.set([new_sale_tax.id])],
'supplier_taxes_id': [Command.set([new_purchase_tax.id])],
'can_be_expensed': True,
}),
'service_order_cost_price': cls.env['product.product'].with_company(cls.company_data['company']).create({
'name': 'service_order_cost_price',
'categ_id': cls.company_data['product_category'].id,
'standard_price': 235.28,
'list_price': 280.39,
'type': 'service',
'weight': 0.01,
'uom_id': cls.env.ref('uom.product_uom_unit').id,
'uom_po_id': cls.env.ref('uom.product_uom_unit').id,
'default_code': 'FURN_99994',
'invoice_policy': 'order',
'expense_policy': 'cost',
'taxes_id': [Command.set([new_sale_tax.id])],
'supplier_taxes_id': [Command.set([new_purchase_tax.id])],
'can_be_expensed': True, 'can_be_expensed': True,
}), }),
}) })
def test_expenses_reinvoice(self):
"""
Test that expenses are re-invoiced correctly and that the quantity is updated when it has to.
- Lines are never grouped together (even if reinvoced at sale price and with a re-invoice delivered policy)
- When posting an expense, it creates the corresponding sol with the expense quantity
- The quantities ordered and delivered are reset to 0 when:
- the expense sheet is unposted
- the expense move is reversed
- the expense move is reset to draft
"""
# create SO line and confirm SO (with only one line) # create SO line and confirm SO (with only one line)
sale_order = self.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({ cls.expense_sale_order = cls.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({
'partner_id': self.partner_a.id, 'partner_id': cls.partner_a.id,
'partner_invoice_id': self.partner_a.id, 'partner_invoice_id': cls.partner_a.id,
'partner_shipping_id': self.partner_a.id, 'partner_shipping_id': cls.partner_a.id,
'order_line': [Command.create({ 'order_line': [Command.create({
'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price', 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price',
# Using the same name as one of the expense # Using the same name as one of the expense
'product_id': self.company_data['product_order_sales_price'].id, 'product_id': cls.company_data['product_order_sales_price'].id,
'product_uom_qty': 3.0, 'product_uom_qty': 3.0,
'price_unit': self.company_data['product_order_sales_price'].standard_price, 'price_unit': cls.company_data['product_order_sales_price'].standard_price,
})], })],
}) })
sale_order.action_confirm() cls.expense_sale_order.action_confirm()
expense_sheet = self.env['hr.expense.sheet'].create({ # Create an expense sheet with 6 expenses, covering all the expense & invoicing policies combinaisons
cls.sale_expenses = cls.env['hr.expense'].create([
{
# exp_order_sale_1
'name': 'expense_1 invoicing=order, expense=sales_price',
'date': '2016-01-01',
'product_id': cls.company_data['service_order_sales_price'].id,
'total_amount': 100.34,
'analytic_distribution': {cls.analytic_account_1.id: 100},
'employee_id': cls.expense_employee.id,
'sale_order_id': cls.expense_sale_order.id,
},
{
# exp_order_sale_2
'name': 'expense_2 invoicing=order, expense=sales_price',
'date': '2016-01-02',
'product_id': cls.company_data['service_order_sales_price'].id,
'total_amount': 100.21,
'employee_id': cls.expense_employee.id,
'sale_order_id': cls.expense_sale_order.id,
},
{
# exp_deliv_sale_3
'name': 'expense_3 invoicing=delivery, expense=sales_price',
'date': '2016-01-03',
'product_id': cls.company_data['service_delivery_sales_price'].id,
'total_amount': 10012.49,
'analytic_distribution': {cls.analytic_account_1.id: 100},
'employee_id': cls.expense_employee.id,
'sale_order_id': cls.expense_sale_order.id,
},
{
# exp_deliv_sale_4
'name': 'expense_4 invoicing=delivery, expense=sales_price',
'date': '2016-01-03',
'product_id': cls.company_data['service_delivery_sales_price'].id,
'analytic_distribution': {cls.analytic_account_1.id: 100},
'total_amount': 10012.49,
'employee_id': cls.expense_employee.id,
'sale_order_id': cls.expense_sale_order.id,
},
{
# exp_deliv_cost_5
'name': 'expense_5 invoicing=delivery, expense=cost',
'date': '2016-01-03',
'product_id': cls.company_data['service_delivery_cost_price'].id,
'quantity': 5,
'employee_id': cls.expense_employee.id,
'sale_order_id': cls.expense_sale_order.id,
},
{
# exp_order_cost_6
'name': 'expense_6 invoicing=order, expense=cost',
'date': '2016-01-03',
'product_id': cls.company_data['service_order_cost_price'].id,
'quantity': 6,
'employee_id': cls.expense_employee.id,
'sale_order_id': cls.expense_sale_order.id,
},
]).sorted()
cls.sale_expense_sheet = cls.env['hr.expense.sheet'].create({
'name': 'Reset expense test', 'name': 'Reset expense test',
'employee_id': self.expense_employee.id, 'employee_id': cls.expense_employee.id,
'journal_id': self.company_data['default_journal_purchase'].id, 'journal_id': cls.company_data['default_journal_purchase'].id,
'accounting_date': '2017-01-01', 'accounting_date': '2017-01-01',
'expense_line_ids': [ 'expense_line_ids': [Command.set(cls.sale_expenses.ids)],
Command.create({
'name': 'expense_1 invoicing=order, expense=sales_price',
'date': '2016-01-01',
'product_id': self.company_data['service_order_sales_price'].id,
'quantity': 1,
'analytic_distribution': {self.analytic_account_1.id: 100},
'employee_id': self.expense_employee.id,
'sale_order_id': sale_order.id,
}),
Command.create({
'name': 'expense_2 invoicing=order, expense=sales_price',
'date': '2016-01-02',
'product_id': self.company_data['service_order_sales_price'].id,
'quantity': 2,
'employee_id': self.expense_employee.id,
'sale_order_id': sale_order.id,
}),
Command.create({
'name': 'expense_3 invoicing=delivery, expense=sales_price',
'date': '2016-01-03',
'product_id': self.company_data['service_delivery_sales_price'].id,
'quantity': 3,
'analytic_distribution': {self.analytic_account_1.id: 100},
'employee_id': self.expense_employee.id,
'sale_order_id': sale_order.id,
}),
Command.create({
'name': 'expense_4 invoicing=delivery, expense=sales_price',
'date': '2016-01-03',
'product_id': self.company_data['service_delivery_sales_price'].id,
'quantity': 4,
'analytic_distribution': {self.analytic_account_1.id: 100},
'employee_id': self.expense_employee.id,
'sale_order_id': sale_order.id,
}),
],
}) })
cls.sale_expense_sheet._do_approve()
expense_sheet.action_approve_expense_sheets() def test_expenses_reinvoice_case_1_create_moves(self):
expense_sheet.action_sheet_move_create() """
CASE 1: Creation of the expense sheets moves. The sale order lines are created.
"""
# pylint: disable=bad-whitespace
self.sale_expense_sheet.action_sheet_move_create()
# We also need to test "duplicates" aka very similar expenses self.assertRecordValues(self.expense_sale_order.order_line, [
expense_sheet_copy = self.env['hr.expense.sheet'].create({ # [0] Line not created from a re-invoiced, should never be changed
'name': 'Reset expense test copy', {'qty_delivered': 0.0, 'product_uom_qty': 3.0, 'is_expense': False, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
'employee_id': self.expense_employee.id, # [1-6] SHEET 1 Lines: created with the correct quantities and linked to the expense
'journal_id': self.company_data['default_journal_purchase'].id, {'qty_delivered': 6.0, 'product_uom_qty': 6.0, 'is_expense': True, 'name': 'expense_employee: expense_6 invoicing=order, expense=cost'}, # noqa: E272
'accounting_date': '2017-01-01', {'qty_delivered': 5.0, 'product_uom_qty': 5.0, 'is_expense': True, 'name': 'expense_employee: expense_5 invoicing=delivery, expense=cost'}, # noqa: E272
'expense_line_ids': [ {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'is_expense': True, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'}, # noqa: E272
Command.create({ {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'is_expense': True, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'}, # noqa: E272
'name': 'expense_3 invoicing=delivery, expense=sales_price copy', {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'is_expense': True, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'}, # noqa: E272
'date': '2016-01-03', {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'is_expense': True, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'}, # noqa: E272
'product_id': self.company_data['service_delivery_sales_price'].id, ])
'quantity': 3,
'analytic_distribution': {self.analytic_account_1.id: 100}, def test_expenses_reinvoice_case_2_reset_sheet_to_draft(self):
'employee_id': self.expense_employee.id, """
'sale_order_id': sale_order.id, CASE 2: Reset to draft of the expense sheet, the quantities of the corresponding SOL are set to 0
}), """
Command.create({ # CASE 1 steps
'name': 'expense_4 invoicing=delivery, expense=sales_price copy', self.sale_expense_sheet.action_sheet_move_create()
'date': '2016-01-03',
'product_id': self.company_data['service_delivery_sales_price'].id, # CASE 2 steps
'quantity': 4, self.sale_expense_sheet.action_reset_expense_sheets()
'analytic_distribution': {self.analytic_account_1.id: 100},
'employee_id': self.expense_employee.id, self.assertRecordValues(self.expense_sale_order.order_line, [
'sale_order_id': sale_order.id, # [0] Line not created from a re-invoiced, should never be changed
}), {'qty_delivered': 0.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
], # [1-6] SHEET Lines: quantities are reset to 0 and expenses are unlinked
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_6 invoicing=order, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_5 invoicing=delivery, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
])
def test_expenses_reinvoice_case_3_recreate_move_after_reset(self):
"""
CASE 3: Re-Approve and Re-Post the expense sheet after a reset, creating new SOLs with the correct quantities
"""
# CASE 1 steps
self.sale_expense_sheet.action_sheet_move_create()
# CASE 2 steps
self.sale_expense_sheet.action_reset_expense_sheets()
# CASE 3 steps
self.sale_expense_sheet._do_approve()
self.sale_expense_sheet.action_sheet_move_create()
self.assertRecordValues(self.expense_sale_order.order_line, [
# [0] Line not created from a re-invoiced, should never be changed
{'qty_delivered': 0.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
# [1-6] SHEET CASE 2 Lines: no change
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_6 invoicing=order, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_5 invoicing=delivery, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
# [7-12] SHEET CASE 3 Lines: created with the correct quantities and linked to the expense
{'qty_delivered': 6.0, 'product_uom_qty': 6.0, 'name': 'expense_employee: expense_6 invoicing=order, expense=cost'},
{'qty_delivered': 5.0, 'product_uom_qty': 5.0, 'name': 'expense_employee: expense_5 invoicing=delivery, expense=cost'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
])
def test_expenses_reinvoice_case_4_reset_sheet_move_to_draft(self):
"""
CASE 4: Reset to draft of the expense sheet's move, the quantities of the corresponding SOL are set to 0
"""
# CASE 1 steps
self.sale_expense_sheet.action_sheet_move_create()
# CASE 4 steps
self.sale_expense_sheet.account_move_ids.button_draft()
self.assertRecordValues(self.expense_sale_order.order_line, [
# [0] Line not created from a re-invoiced, should never be changed
{'qty_delivered': 0.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
# [1-6] SHEET Lines: quantities are reset to 0 and expenses are unlinked
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_6 invoicing=order, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_5 invoicing=delivery, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
])
def test_expenses_reinvoice_case_5_repost_sheet_move_after_reset_to_draft(self):
"""
CASE 5: Re-Post the expense sheet's move, creating new SOLs with the correct quantities
"""
# CASE 1 steps
self.sale_expense_sheet.action_sheet_move_create()
# CASE 4 steps
self.sale_expense_sheet.account_move_ids.button_draft()
# CASE 5 steps
self.sale_expense_sheet.account_move_ids.action_post()
self.assertRecordValues(self.expense_sale_order.order_line, [
# [0] Line not created from a re-invoiced, should never be changed
{'qty_delivered': 0.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
# [1-6] SHEET CASE 4 Lines: no change
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_6 invoicing=order, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_5 invoicing=delivery, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
# [7-12] SHEET CASE 5 Lines: created with the correct quantities and linked to the expense
{'qty_delivered': 6.0, 'product_uom_qty': 6.0, 'name': 'expense_employee: expense_6 invoicing=order, expense=cost'},
{'qty_delivered': 5.0, 'product_uom_qty': 5.0, 'name': 'expense_employee: expense_5 invoicing=delivery, expense=cost'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
])
def test_expenses_reinvoice_case_6_reverse_expense_move(self):
"""
CASE 6: Reverse the expense sheet's move, the quantities of the corresponding SOL are reset to 0
"""
# CASE 1 steps
self.sale_expense_sheet.action_sheet_move_create()
# CASE 6 steps
self.sale_expense_sheet.account_move_ids._reverse_moves()
self.assertRecordValues(self.expense_sale_order.order_line, [
# [0] Line not created from a re-invoiced, should never be changed
{'qty_delivered': 0.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
# [1-6] SHEET Lines: quantities are reset to 0 and expenses are unlinked
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_6 invoicing=order, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_5 invoicing=delivery, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
])
def test_expenses_reinvoice_case_7_ensure_one2one_relationship(self):
"""
CASE 7: Test that two exact same sols are not reset to 0 when the expense of one of them is resetting the quantities to 0
"""
original_expenses = self.sale_expenses
self.sale_expense_sheet.write({
'expense_line_ids': [Command.link(expense.copy().id) for expense in original_expenses], # Duplicates of the expenses IN the reset sheet
'accounting_date': '2017-01-01', # To avoid "duplicate vendor reference raised" in the move
}) })
self.sale_expense_sheet._do_approve()
action_data = expense_sheet_copy.action_approve_expense_sheets() self.sale_expense_sheet.action_sheet_move_create()
duplicate_wizard = self.env['hr.expense.approve.duplicate'].with_context(action_data['context']).create({}) sheet_2 = self.sale_expense_sheet.copy({
duplicate_wizard.action_approve() 'expense_line_ids': [Command.set([expense.copy().id for expense in original_expenses])], # Duplicates of the expenses OUTSIDE the reset sheet
expense_sheet_copy.action_sheet_move_create() 'accounting_date': '2017-01-02',
#pylint: disable=bad-whitespace })
self.assertRecordValues(sale_order.order_line, [ sheet_2._do_approve()
{'qty_delivered': 0.0, 'product_uom_qty': 3.0, 'is_expense': False, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'}, sheet_2.action_sheet_move_create()
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'is_expense': True, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'}, self.assertRecordValues(self.expense_sale_order.order_line, [
{'qty_delivered': 2.0, 'product_uom_qty': 2.0, 'is_expense': True, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'}, # [0] Line not created from a re-invoiced, should never be changed
{'qty_delivered': 3.0, 'product_uom_qty': 3.0, 'is_expense': True, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 4.0, 'product_uom_qty': 4.0, 'is_expense': True, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 3.0, 'product_uom_qty': 3.0, 'is_expense': True, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price copy'},
{'qty_delivered': 4.0, 'product_uom_qty': 4.0, 'is_expense': True, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price copy'},
])
expense_sheet.action_reset_expense_sheets() # Lines [0-4] quantities are set to 0
self.assertRecordValues(sale_order.order_line, [
{'qty_delivered': 0.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'}, {'qty_delivered': 0.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'}, # [1-12] SHEET 1 Lines: Created with the correct quantities
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'}, {'qty_delivered': 6.0, 'product_uom_qty': 6.0, 'name': 'expense_employee: expense_6 invoicing=order, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'}, {'qty_delivered': 5.0, 'product_uom_qty': 5.0, 'name': 'expense_employee: expense_5 invoicing=delivery, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'}, {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 3.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price copy'}, {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 4.0, 'product_uom_qty': 4.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price copy'}, {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
])
expense_sheet.action_approve_expense_sheets()
expense_sheet.action_sheet_move_create() # Lines [1-4] are still at 0 but new lines are created (stable limitation)
self.assertRecordValues(sale_order.order_line, [
{'qty_delivered': 0.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 3.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price copy'},
{'qty_delivered': 4.0, 'product_uom_qty': 4.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price copy'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'}, {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
{'qty_delivered': 2.0, 'product_uom_qty': 2.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'}, {'qty_delivered': 6.0, 'product_uom_qty': 6.0, 'name': 'expense_employee: expense_6 invoicing=order, expense=cost'},
{'qty_delivered': 3.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'}, {'qty_delivered': 5.0, 'product_uom_qty': 5.0, 'name': 'expense_employee: expense_5 invoicing=delivery, expense=cost'},
{'qty_delivered': 4.0, 'product_uom_qty': 4.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'}, {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
]) {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
expense_sheet.account_move_ids.button_draft() # Lines [7-11] quantities are set to 0 {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
self.assertRecordValues(sale_order.order_line, [ # [13-18] SHEET 2 Lines: Created with the correct quantities
{'qty_delivered': 0.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'}, {'qty_delivered': 6.0, 'product_uom_qty': 6.0, 'name': 'expense_employee: expense_6 invoicing=order, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'}, {'qty_delivered': 5.0, 'product_uom_qty': 5.0, 'name': 'expense_employee: expense_5 invoicing=delivery, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'}, {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'}, {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'}, {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 3.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price copy'},
{'qty_delivered': 4.0, 'product_uom_qty': 4.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price copy'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
])
expense_sheet.account_move_ids.action_post() # Lines [7-11] are still at 0 but new lines are created (stable limitation)
self.assertRecordValues(sale_order.order_line, [
{'qty_delivered': 0.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 3.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price copy'},
{'qty_delivered': 4.0, 'product_uom_qty': 4.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price copy'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'}, {'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
{'qty_delivered': 2.0, 'product_uom_qty': 2.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 3.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 4.0, 'product_uom_qty': 4.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
]) ])
expense_sheet.account_move_ids._reverse_moves() # Lines [7-11] quantities are set to 0 self.sale_expense_sheet.account_move_ids.button_draft()
self.assertRecordValues(sale_order.order_line, [
{'qty_delivered': 0.0, 'product_uom_qty': 3.0, 'is_expense': False, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'}, self.assertRecordValues(self.expense_sale_order.order_line, [
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'is_expense': True, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'}, # [0] Line not created from a re-invoiced, should never be changed
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'is_expense': True, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'}, {'qty_delivered': 0.0, 'product_uom_qty': 3.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'is_expense': True, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'}, # [1-12] SHEET 1 Lines: quantities are reset to 0 and expenses are unlinked (because they are the oldest)
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'is_expense': True, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'}, {'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_6 invoicing=order, expense=cost'},
{'qty_delivered': 3.0, 'product_uom_qty': 3.0, 'is_expense': True, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price copy'}, {'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_5 invoicing=delivery, expense=cost'},
{'qty_delivered': 4.0, 'product_uom_qty': 4.0, 'is_expense': True, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price copy'}, {'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'is_expense': True, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'}, {'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'is_expense': True, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'}, {'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'is_expense': True, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'}, {'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'is_expense': True, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'}, {'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_6 invoicing=order, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'is_expense': True, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'}, {'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_5 invoicing=delivery, expense=cost'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'is_expense': True, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'}, {'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'is_expense': True, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'}, {'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'is_expense': True, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'}, {'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 0.0, 'product_uom_qty': 0.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
# [13-18] SHEET 2 Lines: Not caught by the reset
{'qty_delivered': 6.0, 'product_uom_qty': 6.0, 'name': 'expense_employee: expense_6 invoicing=order, expense=cost'},
{'qty_delivered': 5.0, 'product_uom_qty': 5.0, 'name': 'expense_employee: expense_5 invoicing=delivery, expense=cost'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_4 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_3 invoicing=delivery, expense=sales_price'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_2 invoicing=order, expense=sales_price'},
{'qty_delivered': 1.0, 'product_uom_qty': 1.0, 'name': 'expense_employee: expense_1 invoicing=order, expense=sales_price'},
]) ])
def test_expenses_reinvoice_analytic_distribution(self): def test_expenses_reinvoice_analytic_distribution(self):