Files
tryton/modules/project_revenue/work.py
T

213 lines
7.6 KiB
Python
Raw Normal View History

2014-12-02 12:55:45 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2015-09-24 16:08:08 +02:00
from collections import defaultdict
2021-12-11 17:58:43 +01:00
from decimal import Decimal
2015-09-22 19:27:26 +02:00
from sql.aggregate import Sum
2015-09-24 16:08:08 +02:00
from sql.operators import Concat
2013-09-20 17:34:12 +02:00
2012-09-24 22:18:08 +02:00
from trytond.model import fields
2021-08-21 09:09:56 +02:00
from trytond.modules.currency.fields import Monetary
2020-04-23 21:44:00 +02:00
from trytond.modules.product import price_digits, round_price
2021-12-11 17:58:43 +01:00
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.tools import grouped_slice, reduce_ids
from trytond.transaction import Transaction
2015-10-01 22:27:18 +02:00
2009-06-04 11:19:01 +02:00
2018-06-26 19:11:51 +02:00
class Work(metaclass=PoolMeta):
2012-09-24 22:18:08 +02:00
__name__ = 'project.work'
2009-06-04 11:19:01 +02:00
product = fields.Many2One('product.product', 'Product',
2013-05-27 17:55:27 +02:00
domain=[
('type', '=', 'service'),
],
context={
'company': Eval('company', -1),
},
depends={'company'})
2021-08-21 09:09:56 +02:00
list_price = Monetary(
"List Price", currency='currency', digits=price_digits)
revenue = fields.Function(Monetary(
"Revenue", currency='currency', digits='currency'),
'get_total')
cost = fields.Function(Monetary(
"Cost", currency='currency', digits='currency'),
'get_total')
2020-10-09 19:09:05 +02:00
currency = fields.Function(fields.Many2One('currency.currency',
'Currency'), 'on_change_with_currency')
2009-06-04 11:19:01 +02:00
2015-09-24 16:08:08 +02:00
@classmethod
def __setup__(cls):
pool = Pool()
super(Work, cls).__setup__()
try:
pool.get('purchase.line')
except KeyError:
pass
else:
# Add purchase lines if purchase is activated
2015-09-24 16:08:08 +02:00
cls.purchase_lines = fields.One2Many('purchase.line', 'work',
'Purchase Lines', domain=[
('purchase.company', '=', Eval('company', -1)),
2018-06-18 21:52:52 +02:00
('type', '=', 'line'),
])
2015-09-24 16:08:08 +02:00
2012-09-24 22:18:08 +02:00
@classmethod
2015-09-17 11:19:59 +02:00
def _get_cost(cls, works):
2013-09-20 17:34:12 +02:00
pool = Pool()
Line = pool.get('timesheet.line')
Work = pool.get('timesheet.work')
transaction = Transaction()
cursor = transaction.connection.cursor()
2013-09-20 17:34:12 +02:00
costs = defaultdict(Decimal)
2012-10-17 16:30:06 +02:00
2015-09-17 09:31:09 +02:00
table = cls.__table__()
work = Work.__table__()
2013-09-20 17:34:12 +02:00
line = Line.__table__()
2015-09-24 16:08:08 +02:00
# Timesheet cost
2015-09-17 09:31:09 +02:00
work_ids = [w.id for w in works]
for sub_ids in grouped_slice(work_ids):
red_sql = reduce_ids(table.id, sub_ids)
2015-09-17 11:19:59 +02:00
cursor.execute(*table.join(work,
2016-06-24 09:31:08 +02:00
condition=(
Concat(cls.__name__ + ',', table.id) == work.origin)
2015-09-17 09:31:09 +02:00
).join(line, condition=line.work == work.id
2015-10-01 22:27:18 +02:00
).select(table.id, Sum(line.cost_price * line.duration),
2013-09-20 17:34:12 +02:00
where=red_sql,
2015-10-01 22:27:18 +02:00
group_by=[table.id]))
for work_id, cost in cursor:
2015-09-22 19:27:26 +02:00
# SQLite stores timedelta as float
2015-10-01 22:27:18 +02:00
if not isinstance(cost, float):
cost = cost.total_seconds()
# Convert from seconds
cost /= 60 * 60
costs[work_id] += Decimal(str(cost))
2015-09-24 16:08:08 +02:00
# Purchase cost
if hasattr(cls, 'purchase_lines'):
2018-06-26 19:11:51 +02:00
for work_id, cost in cls._purchase_cost(works).items():
2015-09-24 16:08:08 +02:00
costs[work_id] += cost
2014-12-28 16:12:40 +01:00
for work in works:
costs[work.id] = work.company.currency.round(costs[work.id])
2013-09-20 17:34:12 +02:00
return costs
2009-06-04 11:19:01 +02:00
2015-09-24 16:08:08 +02:00
@classmethod
def _purchase_cost(cls, works):
'Compute direct purchase cost'
pool = Pool()
Currency = pool.get('currency.currency')
PurchaseLine = pool.get('purchase.line')
InvoiceLine = pool.get('account.invoice.line')
Invoice = pool.get('account.invoice')
Company = pool.get('company.company')
cursor = Transaction().connection.cursor()
2015-09-24 16:08:08 +02:00
table = cls.__table__()
purchase_line = PurchaseLine.__table__()
invoice_line = InvoiceLine.__table__()
invoice = Invoice.__table__()
company = Company.__table__()
amounts = defaultdict(Decimal)
work_ids = [w.id for w in works]
work2currency = {}
iline2work = {}
for sub_ids in grouped_slice(work_ids):
where = reduce_ids(table.id, sub_ids)
cursor.execute(*table.join(purchase_line,
condition=purchase_line.work == table.id
).join(invoice_line,
condition=invoice_line.origin == Concat(
'purchase.line,', purchase_line.id)
).join(invoice,
condition=invoice_line.invoice == invoice.id
).select(invoice_line.id, table.id,
where=where & ~invoice.state.in_(['draft', 'cancelled'])))
iline2work.update(cursor)
2015-09-24 16:08:08 +02:00
cursor.execute(*table.join(company,
condition=table.company == company.id
).select(table.id, company.currency,
where=where))
work2currency.update(cursor)
2015-09-24 16:08:08 +02:00
2018-06-26 19:11:51 +02:00
currencies = Currency.browse(set(work2currency.values()))
2015-09-24 16:08:08 +02:00
id2currency = {c.id: c for c in currencies}
2018-06-26 19:11:51 +02:00
invoice_lines = InvoiceLine.browse(list(iline2work.keys()))
2015-09-24 16:08:08 +02:00
for invoice_line in invoice_lines:
invoice = invoice_line.invoice
work_id = iline2work[invoice_line.id]
currency_id = work2currency[work_id]
currency = id2currency[currency_id]
if currency != invoice.currency:
with Transaction().set_context(date=invoice.currency_date):
amount = Currency.compute(invoice.currency,
invoice_line.amount, currency)
else:
amount = invoice_line.amount
amounts[work_id] += amount
return amounts
2012-09-24 22:18:08 +02:00
@classmethod
2015-09-17 11:19:59 +02:00
def _get_revenue(cls, works):
revenues = defaultdict(Decimal)
2017-12-14 09:48:13 +01:00
for work in works:
2020-03-28 13:06:21 +01:00
if not work.list_price:
continue
if work.price_list_hour:
revenue = work.company.currency.round(
2017-12-14 09:48:13 +01:00
work.list_price * Decimal(str(work.effort_hours)))
else:
2020-03-28 13:06:21 +01:00
revenue = work.list_price
revenues[work.id] = work.company.currency.round(revenue)
2017-12-14 09:48:13 +01:00
return revenues
2009-06-04 11:19:01 +02:00
2014-01-26 14:29:27 +01:00
@fields.depends('company')
2020-10-09 19:09:05 +02:00
def on_change_with_currency(self, name=None):
2023-03-26 09:49:00 +02:00
return self.company.currency if self.company else None
2020-10-09 19:09:05 +02:00
@fields.depends('product')
2012-09-24 22:18:08 +02:00
def on_change_product(self):
2011-07-03 13:19:08 +02:00
pool = Pool()
2012-09-24 22:18:08 +02:00
ModelData = pool.get('ir.model.data')
Uom = pool.get('product.uom')
2009-06-04 11:19:01 +02:00
2012-09-24 22:18:08 +02:00
if not self.product:
2014-10-29 20:40:07 +01:00
return
2009-06-04 11:19:01 +02:00
list_price = self.product.list_price_used
if list_price is not None:
if self.price_list_hour:
hour_uom = Uom(ModelData.get_id('product', 'uom_hour'))
list_price = Uom.compute_price(
self.product.default_uom, list_price, hour_uom)
list_price = round_price(list_price)
self.list_price = list_price
2020-03-28 13:06:21 +01:00
@property
def price_list_hour(self):
pool = Pool()
ModelData = pool.get('ir.model.data')
Category = pool.get('product.uom.category')
if not self.product:
return
time = Category(ModelData.get_id('product', 'uom_cat_time'))
return self.product.default_uom_category == time
2022-06-16 14:31:01 +02:00
@classmethod
def copy(cls, records, default=None):
if default is None:
default = {}
else:
default = default.copy()
if hasattr(cls, 'purchase_lines'):
# Do not copy purchase lines if purchase is activated
default.setdefault('purchase_lines', None)
return super().copy(records, default=default)