Forbid to change quantity of invoice line from project

This commit is contained in:
Cédric Krier
2023-05-24 17:05:43 +02:00
parent 2721a6068a
commit a560998bc8
5 changed files with 145 additions and 1 deletions
+102 -1
View File
@@ -1,13 +1,114 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime as dt
from sql.aggregate import Sum
from trytond.i18n import gettext
from trytond.model import fields
from trytond.modules.account_invoice.exceptions import (
InvoiceLineValidationError)
from trytond.pool import Pool, PoolMeta
from trytond.tools import grouped_slice
from trytond.tools import grouped_slice, reduce_ids
from trytond.transaction import Transaction
class InvoiceLine(metaclass=PoolMeta):
__name__ = 'account.invoice.line'
project_invoice_works = fields.One2Many(
'project.work', 'invoice_line',
"Project Invoice Works", readonly=True)
project_invoice_progresses = fields.One2Many(
'project.work.invoiced_progress', 'invoice_line',
"Project Invoice Progresses", readonly=True)
project_invoice_timesheet_duration = fields.Function(
fields.TimeDelta("Project Invoice Timesheet Duration"),
'get_project_invoice_timesheet_duration')
@classmethod
def check_validate_project_invoice_quantity(cls, lines, field_names):
pool = Pool()
Lang = pool.get('ir.lang')
if field_names and not (field_names & {
'quantity', 'project_invoice_works'}):
return
for line in lines:
project_invoice_quantity = line.project_invoice_quantity
if project_invoice_quantity is None:
continue
if line.unit:
project_invoice_quantity = line.unit.round(
project_invoice_quantity)
if line.quantity != project_invoice_quantity:
lang = Lang.get()
if line.unit:
quantity = lang.format_number_symbol(
project_invoice_quantity, line.unit)
else:
quantity = lang.format_number(project_invoice_quantity)
raise InvoiceLineValidationError(gettext(
'project_invoice.msg_project_invoice_line_quantity',
invoice_line=line.rec_name,
quantity=quantity,
))
@property
def project_invoice_quantity(self):
quantity = None
for work in self.project_invoice_works:
if quantity is None:
quantity = 0
if work.price_list_hour:
quantity += work.effort_hours
else:
quantity += 1
for progress in self.project_invoice_progresses:
if quantity is None:
quantity = 0
work = progress.work
if work.price_list_hour:
quantity += progress.progress * work.effort_hours
else:
quantity += progress.progress
if self.project_invoice_timesheet_duration is not None:
if quantity is None:
quantity = 0
quantity += (
self.project_invoice_timesheet_duration.total_seconds()
/ 60 / 60)
return quantity
@classmethod
def get_project_invoice_timesheet_duration(cls, lines, name):
pool = Pool()
TimesheetLine = pool.get('timesheet.line')
cursor = Transaction().connection.cursor()
ts_line = TimesheetLine.__table__()
durations = dict.fromkeys(map(int, lines))
query = ts_line.select(
ts_line.invoice_line, Sum(ts_line.duration),
group_by=ts_line.invoice_line)
for sub_lines in grouped_slice(lines):
query.where = reduce_ids(
ts_line.invoice_line, map(int, sub_lines))
cursor.execute(*query)
for line_id, duration in cursor:
# SQLite uses float for SUM
if (duration is not None
and not isinstance(duration, dt.timedelta)):
duration = dt.timedelta(seconds=duration)
durations[line_id] = duration
return durations
@classmethod
def validate_fields(cls, lines, field_names):
super().validate_fields(lines, field_names)
cls.check_validate_project_invoice_quantity(lines, field_names)
@classmethod
def delete(cls, lines):
pool = Pool()
+3
View File
@@ -30,5 +30,8 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.message" id="msg_invoiced_work_delete">
<field name="text">You cannot delete work "%(work)s" because it has been invoiced.</field>
</record>
<record model="ir.message" id="msg_project_invoice_line_quantity">
<field name="text">The quantity of project invoice line "%(invoice_line)s" must be "%(quantity)s"</field>
</record>
</data>
</tryton>
@@ -192,3 +192,16 @@ Invoice again project::
Decimal('0.00')
>>> project.invoiced_amount
Decimal('170.00')
Try to change invoice line quantity::
>>> set_user(1)
>>> ProjectWork = Model.get('project.work')
>>> task = ProjectWork(task.id)
>>> task.invoice_line.quantity = 1
>>> task.invoice_line.save() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvoiceLineValidationError: ...
>>> task.invoice_line.quantity = 5
>>> task.invoice_line.save()
@@ -194,3 +194,17 @@ Invoice again project::
Decimal('0.00')
>>> project.invoiced_amount
Decimal('136.00')
Try to change invoice line quantity::
>>> set_user(1)
>>> ProjectWork = Model.get('project.work')
>>> task = ProjectWork(task.id)
>>> invoice_line = task.invoiced_progress[0].invoice_line
>>> invoice_line.quantity = 2.
>>> invoice_line.save() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvoiceLineValidationError: ...
>>> invoice_line.quantity = 2.5
>>> invoice_line.save()
@@ -225,3 +225,16 @@ Invoice again project::
>>> _, _, invoice = Invoice.find([], order=[('id', 'ASC')])
>>> invoice.total_amount
Decimal('80.00')
Try to change invoice line quantity::
>>> set_user(1)
>>> TimesheetLine = Model.get('timesheet.line')
>>> line = TimesheetLine(line.id)
>>> line.invoice_line.quantity = 5
>>> line.invoice_line.save() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvoiceLineValidationError: ...
>>> line.invoice_line.quantity = 4
>>> line.invoice_line.save()