Files
tradon/modules/project_invoice/invoice.py
2026-03-14 09:42:12 +00:00

118 lines
4.4 KiB
Python

# 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, 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 copy(cls, lines, default=None):
default = default.copy() if default is not None else {}
default.setdefault('project_invoice_works')
default.setdefault('project_invoice_progresses')
return super().copy(lines, default=default)