[IMP] sale_timesheet: Invoicing wizard improvements

Purpose of this task is, the invoicing wizard is being able to
define a period with a start and an end date would allow a bit
more flexibility.

Currently, the invoicing wizard allows to invoice timesheets
up to a certain date.It is links all timesheets before or equals
this date in the sale order are invoiced.

In this commit, we add  another date field so that the user can
define a period with a start and an end date and rename the
'invoice timesheets up to' field into 'Timesheets Period'. Also,
use widget 'Daterange'  in both fields and update tooltips
accordingly this fields. By default we leave this two field empty.

After this commit behaviour of invoicing wizard:
 -If these fields are empty, include all timesheets without
  distinction in the invoice
 -If these fields are set, only include the timesheets that fall
  within this period in the invoice
 -If applicable, only include approved timesheets

TaskId:2252798
Closes: #52073
This commit is contained in:
Nidhi Patel
2020-08-05 12:56:25 +00:00
committed by Barad Mahendra
parent e249d2b434
commit 4cc8fe2a98
5 changed files with 56 additions and 40 deletions
+8 -6
View File
@@ -43,21 +43,23 @@ class AccountMove(models.Model):
}
}
def _link_timesheets_to_invoice(self, date=None):
""" Search timesheets and link this timesheets to the invoice
def _link_timesheets_to_invoice(self, start_date=None, end_date=None):
""" Search timesheets from given period and link this timesheets to the invoice
When we create an invoice from a sale order, we need to
link the timesheets in this sale order to the invoice.
Then, we can know which timesheets are invoiced in the sale order.
:param date: All timesheets on task before or equals this date
in the sale order are invoiced.
:param start_date: the start date of the period
:param end_date: the end date of the period
"""
for line in self.filtered(lambda i: i.move_type == 'out_invoice' and i.state == 'draft').invoice_line_ids:
sale_line_delivery = line.sale_line_ids.filtered(lambda sol: sol.product_id.invoice_policy == 'delivery' and sol.product_id.service_type == 'timesheet')
if sale_line_delivery:
domain = line._timesheet_domain_get_invoiced_lines(sale_line_delivery)
if date:
domain = expression.AND([domain, [('date', '<=', date)]])
if start_date:
domain = expression.AND([domain, [('date', '>=', start_date)]])
if end_date:
domain = expression.AND([domain, [('date', '<=', end_date)]])
timesheets = self.env['account.analytic.line'].sudo().search(domain)
timesheets.write({'timesheet_invoice_id': line.move_id.id})
+12 -7
View File
@@ -60,14 +60,15 @@ class SaleOrder(models.Model):
action = {'type': 'ir.actions.act_window_close'}
return action
def _create_invoices(self, grouped=False, final=False, date=None):
def _create_invoices(self, grouped=False, final=False, start_date=None, end_date=None):
""" Override the _create_invoice method in sale.order model in sale module
Add new parameter in this method, to invoice sale.order with a date. This date is used in sale_make_invoice_advance_inv into this module.
:param date
:param start_date: the start date of the period
:param end_date: the end date of the period
:return {account.move}: the invoices created
"""
moves = super(SaleOrder, self)._create_invoices(grouped, final)
moves._link_timesheets_to_invoice(date)
moves._link_timesheets_to_invoice(start_date, end_date)
return moves
class SaleOrderLine(models.Model):
@@ -115,22 +116,26 @@ class SaleOrderLine(models.Model):
project.write({'allow_timesheets': True})
return project
def _recompute_qty_to_invoice(self, date):
def _recompute_qty_to_invoice(self, start_date, end_date):
""" Recompute the qty_to_invoice field for product containing timesheets
Search the existed timesheets up the given date in parameter.
Search the existed timesheets between the given period in parameter.
Retrieve the unit_amount of this timesheet and then recompute
the qty_to_invoice for each current product.
:param date: date to search timesheets before this date.
:param start_date: the start date of the period
:param end_date: the end date of the period
"""
lines_by_timesheet = self.filtered(lambda sol: sol.product_id._is_delivered_timesheet())
domain = lines_by_timesheet._timesheet_compute_delivered_quantity_domain()
domain = expression.AND([domain, [
('date', '<=', date),
'|',
('timesheet_invoice_id', '=', False),
('timesheet_invoice_id.state', '=', 'cancel')]])
if start_date:
domain = expression.AND([domain, [('date', '>=', start_date)]])
if end_date:
domain = expression.AND([domain, [('date', '<=', end_date)]])
mapping = lines_by_timesheet.sudo()._get_delivered_quantity_by_analytic(domain)
for line in lines_by_timesheet:
@@ -467,22 +467,24 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
# invoice SO
wizard = self.env['sale.advance.payment.inv'].with_context(self.context).create({
'advance_payment_method': 'delivered',
'date_invoice_timesheet': today - timedelta(days=10)
'date_start_invoice_timesheet': today - timedelta(days=16),
'date_end_invoice_timesheet': today - timedelta(days=10)
})
self.assertTrue(wizard.invoicing_timesheet_enabled, 'The "date_invoice_timesheet" field should be visible in the wizard because a product in sale order has service_policy to "Timesheet on Task"')
self.assertTrue(wizard.invoicing_timesheet_enabled, 'The "date_start_invoice_timesheet" and "date_end_invoice_timesheet" field should be visible in the wizard because a product in sale order has service_policy to "Timesheet on Task"')
with self.assertRaises(UserError):
wizard.create_invoices()
self.assertFalse(sale_order.invoice_ids, 'Normally, no invoice will be created created because the timesheet logged is after the date defined in date_invoice_timesheet field')
self.assertFalse(sale_order.invoice_ids, 'Normally, no invoice will be created because the timesheet logged is after the period defined in date_start_invoice_timesheet and date_end_invoice_timesheet field')
wizard.write({
'date_invoice_timesheet': today - timedelta(days=6)
'date_start_invoice_timesheet': today - timedelta(days=10),
'date_end_invoice_timesheet': today - timedelta(days=6)
})
wizard.create_invoices()
self.assertTrue(sale_order.invoice_ids, 'One invoice should be created because the timesheet logged is before or equal to the date defined in wizard')
self.assertTrue(sale_order.invoice_ids, 'One invoice should be created because the timesheet logged is between the period defined in wizard')
invoice = sale_order.invoice_ids[0]
self.assertEqual(so_line_deliver_global_project.qty_invoiced, timesheet1.unit_amount)
@@ -491,17 +493,19 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
invoice.action_post()
wizard.write({
'date_invoice_timesheet': today - timedelta(days=4)
'date_start_invoice_timesheet': today - timedelta(days=16),
'date_end_invoice_timesheet': today - timedelta(days=4)
})
wizard.create_invoices()
self.assertEqual(len(sale_order.invoice_ids), 2)
invoice2 = sale_order.invoice_ids[-1]
self.assertEqual(so_line_deliver_global_project.qty_invoiced, timesheet1.unit_amount + timesheet3.unit_amount, "The last invoice done should have the quantity of the timesheet 3, because the date this timesheet is the only one before the 'date_invoice_timesheet' field in the wizard.")
self.assertEqual(so_line_deliver_global_project.qty_invoiced, timesheet1.unit_amount + timesheet3.unit_amount, "The last invoice done should have the quantity of the timesheet 3, because the date this timesheet is the only one before the 'date_end_invoice_timesheet' field in the wizard.")
wizard.write({
'date_invoice_timesheet': today
'date_start_invoice_timesheet': today - timedelta(days=4),
'date_end_invoice_timesheet': today
})
wizard.create_invoices()
@@ -565,7 +569,8 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
}
wizard = self.env['sale.advance.payment.inv'].with_context(self.context).create({
'advance_payment_method': 'delivered',
'date_invoice_timesheet': today
'date_start_invoice_timesheet': today - timedelta(days=4),
'date_end_invoice_timesheet': today
})
wizard.create_invoices()
@@ -16,18 +16,19 @@ class SaleAdvancePaymentInv(models.TransientModel):
product_ids = order_lines.mapped('product_id').filtered(lambda p: p._is_delivered_timesheet())
return bool(product_ids)
date_invoice_timesheet = fields.Date(
string='Invoice Timesheets Up To This Date',
default=fields.Date.context_today,
required=False,
help="Only timesheets not yet invoiced (and validated, if applicable) up to this date included will be invoiced. If no date is indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.")
date_start_invoice_timesheet = fields.Date(
string='Start Date',
help="Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.")
date_end_invoice_timesheet = fields.Date(
string='End Date',
help="Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.")
invoicing_timesheet_enabled = fields.Boolean(default=_default_invoicing_timesheet_enabled)
def create_invoices(self):
""" Override method from sale/wizard/sale_make_invoice_advance.py
When the user want to invoice the timesheets to the SO
up to a specific date then we need to recompute the
up to a specific period then we need to recompute the
qty_to_invoice for each product_id in sale.order.line,
before creating the invoice.
"""
@@ -36,10 +37,10 @@ class SaleAdvancePaymentInv(models.TransientModel):
)
if self.advance_payment_method == 'delivered' and self.invoicing_timesheet_enabled:
if self.date_invoice_timesheet:
sale_orders.mapped('order_line')._recompute_qty_to_invoice(self.date_invoice_timesheet)
if self.date_start_invoice_timesheet or self.date_end_invoice_timesheet:
sale_orders.mapped('order_line')._recompute_qty_to_invoice(self.date_start_invoice_timesheet, self.date_end_invoice_timesheet)
sale_orders._create_invoices(final=self.deduct_down_payments, date=self.date_invoice_timesheet)
sale_orders._create_invoices(final=self.deduct_down_payments, start_date=self.date_start_invoice_timesheet, end_date=self.date_end_invoice_timesheet)
if self._context.get('open_invoices', False):
return sale_orders.action_view_invoice()
@@ -6,16 +6,19 @@
<field name="inherit_id" ref="sale.view_sale_advance_payment_inv"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='deposit_taxes_id']" position="after">
<field name="date_invoice_timesheet" class="oe_inline" string="Invoice Timesheets up to" data-toggle="tooltip" data-placement="bottom"
title="Only timesheets not yet invoiced (and validated, if applicable) up to this date included will be invoiced. If no date is indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction."
attrs="{
'invisible': [ '|',
('invoicing_timesheet_enabled', '=', False),
('advance_payment_method', '!=', 'delivered'),
]
}"
/>
<field name="invoicing_timesheet_enabled" invisible="1"/>
<label for="date_start_invoice_timesheet" string="Timesheets Period" attrs="{'invisible': [ '|', ('invoicing_timesheet_enabled', '=', False), ('advance_payment_method', '!=', 'delivered')]}"/>
<div clas="o_row" attrs="{'invisible': [ '|',('invoicing_timesheet_enabled', '=', False), ('advance_payment_method', '!=', 'delivered')]}">
<field name="date_start_invoice_timesheet"
class="oe_inline" widget="daterange"
options="{'related_end_date': 'date_end_invoice_timesheet'}"
title="Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction."/>
<i class="fa fa-long-arrow-right mx-2" aria-label="Arrow icon" title="Arrow"/>
<field name="date_end_invoice_timesheet"
class="oe_inline" widget="daterange"
options="{'related_start_date': 'date_start_invoice_timesheet'}"
title="Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction."/>
</div>
</xpath>
</field>
</record>