Files
tryton/modules/account_asset/asset.py
T

1195 lines
42 KiB
Python
Raw Normal View History

2012-11-15 19:33:08 +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.
import calendar
2021-12-11 17:58:41 +01:00
import datetime
2012-11-15 19:33:08 +01:00
from decimal import Decimal
2015-02-03 17:20:11 +01:00
from itertools import groupby
2012-11-15 19:33:08 +01:00
2021-12-11 17:58:41 +01:00
from dateutil import relativedelta, rrule
from sql.functions import CharLength
2021-12-11 17:58:41 +01:00
2018-12-29 14:20:28 +01:00
from trytond.i18n import gettext
from trytond.model import Index, ModelSQL, ModelView, Unique, Workflow, fields
2018-12-29 14:20:28 +01:00
from trytond.model.exceptions import AccessError
2015-02-03 17:20:11 +01:00
from trytond.modules.company import CompanyReport
2021-08-21 09:09:55 +02:00
from trytond.modules.currency.fields import Monetary
2021-12-11 17:58:41 +01:00
from trytond.pool import Pool
from trytond.pyson import Bool, Eval, If
from trytond.tools import cached_property, grouped_slice
from trytond.transaction import Transaction, check_access, without_check_access
2021-12-11 17:58:41 +01:00
from trytond.wizard import (
Button, StateReport, StateTransition, StateView, Wizard)
2012-11-15 19:33:08 +01:00
2021-02-21 19:19:26 +01:00
from .exceptions import PrintDepreciationTableError
2012-11-15 19:33:08 +01:00
def date2datetime(date):
return datetime.datetime.combine(date, datetime.time())
2019-11-16 14:49:18 +01:00
February = 2
def normalized_delta(start, end):
"Returns timedelta using fixed 365 days per year"
assert start <= end
delta = end - start
correction = 0
if start.year == end.year:
if (calendar.isleap(start.year)
and (start.month < 2
or (start.month == 2 and start.day < 29))
and end.month > 2):
2019-11-16 14:49:18 +01:00
correction -= 1
else:
if calendar.isleap(start.year) and start.month <= February:
correction -= 1
if calendar.isleap(end.year) and end.month > February:
correction -= 1
correction -= calendar.leapdays(start.year + 1, end.year)
return delta + datetime.timedelta(days=correction)
2012-11-15 19:33:08 +01:00
class Asset(Workflow, ModelSQL, ModelView):
'Asset'
__name__ = 'account.asset'
_rec_name = 'number'
number = fields.Char("Number", readonly=True)
2012-11-15 19:33:08 +01:00
product = fields.Many2One('product.product', 'Product', required=True,
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
},
2020-04-25 09:17:50 +02:00
context={
'company': Eval('company', None),
},
depends={'company'},
2012-11-15 19:33:08 +01:00
domain=[
('type', '=', 'assets'),
('depreciable', '=', True),
])
supplier_invoice_line = fields.Many2One('account.invoice.line',
'Supplier Invoice Line',
domain=[
2020-03-01 12:33:51 +01:00
If(~Eval('product', None),
('product', '=', -1),
('product', '=', Eval('product', -1)),
),
2016-03-20 22:58:04 +01:00
('invoice.type', '=', 'in'),
2014-12-03 16:13:09 +01:00
['OR',
('company', '=', Eval('company', -1)),
('invoice.company', '=', Eval('company', -1)),
],
2012-11-15 19:33:08 +01:00
],
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
})
2012-11-15 19:33:08 +01:00
customer_invoice_line = fields.Function(fields.Many2One(
'account.invoice.line', 'Customer Invoice Line'),
'get_customer_invoice_line')
account_journal = fields.Many2One('account.journal', 'Journal',
states={
'readonly': Eval('state') != 'draft',
},
domain=[('type', '=', 'asset')],
required=True)
company = fields.Many2One('company.company', 'Company',
states={
'readonly': Eval('state') != 'draft',
},
required=True)
2020-10-09 19:09:05 +02:00
currency = fields.Function(fields.Many2One('currency.currency',
'Currency'), 'on_change_with_currency')
2021-08-30 00:27:13 +02:00
quantity = fields.Float(
"Quantity", digits='unit',
2012-11-15 19:33:08 +01:00
states={
'readonly': (Bool(Eval('supplier_invoice_line', 1))
| Eval('lines', [0])
| (Eval('state') != 'draft')),
})
2012-11-15 19:33:08 +01:00
unit = fields.Many2One('product.uom', 'Unit',
states={
'readonly': (Bool(Eval('product'))
| (Eval('state') != 'draft')),
})
2021-08-21 09:09:55 +02:00
value = Monetary(
"Value", currency='currency', digits='currency',
2012-11-15 19:33:08 +01:00
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
},
2019-10-08 18:47:27 +02:00
required=True,
help="The value of the asset when purchased.")
2021-08-21 09:09:55 +02:00
depreciated_amount = Monetary(
"Depreciated Amount", currency='currency', digits='currency',
2019-10-08 18:47:27 +02:00
domain=[
('depreciated_amount', '<=', Eval('value')),
],
2012-11-15 19:33:08 +01:00
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
},
2019-10-08 18:47:27 +02:00
required=True,
help="The amount already depreciated at the start date.")
2021-08-21 09:09:55 +02:00
depreciating_value = fields.Function(Monetary(
"Depreciating Value", currency='currency', digits='currency',
2019-10-08 18:47:27 +02:00
help="The value of the asset at the start date."),
'on_change_with_depreciating_value')
2021-08-21 09:09:55 +02:00
residual_value = Monetary(
"Residual Value", currency='currency', digits='currency',
required=True,
2019-10-08 18:47:27 +02:00
domain=[
('residual_value', '<=', Eval('depreciating_value')),
],
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
})
2012-11-15 19:33:08 +01:00
purchase_date = fields.Date('Purchase Date', states={
'readonly': (Bool(Eval('supplier_invoice_line', 1))
| Eval('lines', [0])
| (Eval('state') != 'draft')),
},
required=True)
2012-11-15 19:33:08 +01:00
start_date = fields.Date('Start Date', states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
},
required=True,
domain=[('start_date', '<=', Eval('end_date', None))])
2012-11-15 19:33:08 +01:00
end_date = fields.Date('End Date',
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
},
required=True,
domain=[('end_date', '>=', Eval('start_date', None))])
2012-11-15 19:33:08 +01:00
depreciation_method = fields.Selection([
('linear', 'Linear'),
], 'Depreciation Method',
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
},
required=True)
2012-11-15 19:33:08 +01:00
frequency = fields.Selection([
('monthly', 'Monthly'),
('yearly', 'Yearly'),
], 'Frequency',
required=True,
states={
'readonly': (Eval('lines', [0]) | (Eval('state') != 'draft')),
})
2012-11-15 19:33:08 +01:00
state = fields.Selection([
('draft', 'Draft'),
('running', 'Running'),
('closed', 'Closed'),
2022-02-15 00:08:46 +01:00
], "State", readonly=True, sort=False)
2012-11-15 19:33:08 +01:00
lines = fields.One2Many('account.asset.line', 'asset', 'Lines',
readonly=True)
2014-12-03 16:13:09 +01:00
move = fields.Many2One('account.move', 'Account Move', readonly=True,
domain=[
('company', '=', Eval('company', -1)),
])
2012-11-15 19:33:08 +01:00
update_moves = fields.Many2Many('account.asset-update-account.move',
'asset', 'move', 'Update Moves', readonly=True,
2014-12-03 16:13:09 +01:00
domain=[
('company', '=', Eval('company', -1)),
],
2012-11-15 19:33:08 +01:00
states={
'invisible': ~Eval('update_moves'),
})
2012-11-15 19:33:08 +01:00
comment = fields.Text('Comment')
2021-10-04 18:53:55 +02:00
revisions = fields.One2Many(
'account.asset.revision', 'asset', "Revisions", readonly=True)
2012-11-15 19:33:08 +01:00
@classmethod
def __setup__(cls):
cls.number.search_unaccented = False
2012-11-15 19:33:08 +01:00
super(Asset, cls).__setup__()
table = cls.__table__()
2012-11-15 19:33:08 +01:00
cls._sql_constraints = [
('invoice_line_uniq', Unique(table, table.supplier_invoice_line),
2018-12-29 14:20:28 +01:00
'account_asset.msg_asset_invoice_line_unique'),
2012-11-15 19:33:08 +01:00
]
cls._sql_indexes.add(
Index(
table,
(table.state, Index.Equality()),
where=table.state.in_(['draft', 'running'])))
2012-11-15 19:33:08 +01:00
cls._transitions |= set((
('draft', 'running'),
('running', 'closed'),
('running', 'draft'),
2012-11-15 19:33:08 +01:00
))
cls._buttons.update({
'draft': {
'invisible': (Eval('lines', [])
| (Eval('state') != 'running')),
'depends': ['state'],
},
2012-11-15 19:33:08 +01:00
'run': {
'invisible': Eval('state') != 'draft',
2018-02-19 11:36:24 +01:00
'depends': ['state'],
2012-11-15 19:33:08 +01:00
},
'close': {
'invisible': Eval('state') != 'running',
2018-02-19 11:36:24 +01:00
'depends': ['state'],
2012-11-15 19:33:08 +01:00
},
'create_lines': {
'invisible': ~Eval('state').in_(['draft', 'running']),
2018-02-19 11:36:24 +01:00
'depends': ['state'],
2012-11-15 19:33:08 +01:00
},
'clear_lines': {
'invisible': (~Eval('lines', [0])
| ~Eval('state').in_(['draft', 'running'])),
2018-02-19 11:36:24 +01:00
'depends': ['state'],
2012-11-15 19:33:08 +01:00
},
'update': {
'invisible': Eval('state') != 'running',
2018-02-19 11:36:24 +01:00
'depends': ['state'],
2012-11-15 19:33:08 +01:00
},
})
@classmethod
def order_number(cls, tables):
table, _ = tables[None]
return [CharLength(table.number), table.number]
2012-11-15 19:33:08 +01:00
@staticmethod
def default_state():
return 'draft'
2020-01-29 22:24:40 +01:00
@classmethod
def default_frequency(cls, **pattern):
pool = Pool()
Configuration = pool.get('account.configuration')
return Configuration(1).get_multivalue('asset_frequency', **pattern)
2012-11-15 19:33:08 +01:00
@staticmethod
def default_depreciation_method():
return 'linear'
2019-10-08 18:47:27 +02:00
@classmethod
def default_depreciated_amount(cls):
return Decimal(0)
@classmethod
def default_residual_value(cls):
return Decimal(0)
2012-11-15 19:33:08 +01:00
@staticmethod
def default_start_date():
return Pool().get('ir.date').today()
@staticmethod
def default_company():
return Transaction().context.get('company')
2021-04-03 14:27:09 +02:00
@fields.depends('company')
def on_change_company(self):
self.frequency = self.default_frequency(
company=self.company.id if self.company else None)
2012-11-15 19:33:08 +01:00
@staticmethod
def default_account_journal():
Journal = Pool().get('account.journal')
journals = Journal.search([
('type', '=', 'asset'),
])
if len(journals) == 1:
return journals[0].id
return None
2019-10-08 18:47:27 +02:00
@fields.depends('value', 'depreciated_amount')
def on_change_with_depreciating_value(self, name=None):
if self.value is not None and self.depreciated_amount is not None:
return self.value - self.depreciated_amount
else:
return Decimal(0)
2014-01-26 14:28:47 +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
2014-01-26 14:28:47 +01:00
@fields.depends('supplier_invoice_line', 'unit')
2012-11-15 19:33:08 +01:00
def on_change_supplier_invoice_line(self):
pool = Pool()
Currency = pool.get('currency.currency')
Unit = Pool().get('product.uom')
if not self.supplier_invoice_line:
2014-10-29 20:39:04 +01:00
self.quantity = None
self.value = None
self.start_date = self.default_start_date()
return
2012-11-15 19:33:08 +01:00
invoice_line = self.supplier_invoice_line
invoice = invoice_line.invoice
if invoice.company.currency != invoice.currency:
with Transaction().set_context(date=invoice.currency_date):
2014-10-29 20:39:04 +01:00
self.value = Currency.compute(
invoice.currency, invoice_line.amount,
invoice.company.currency)
2012-11-15 19:33:08 +01:00
else:
2014-10-29 20:39:04 +01:00
self.value = invoice_line.amount
if invoice.invoice_date:
2014-10-29 20:39:04 +01:00
self.purchase_date = invoice.invoice_date
self.start_date = invoice.invoice_date
if invoice_line.product.depreciation_duration:
duration = relativedelta.relativedelta(
2017-03-27 16:55:48 +02:00
months=invoice_line.product.depreciation_duration,
days=-1)
2014-10-29 20:39:04 +01:00
self.end_date = self.start_date + duration
2012-11-15 19:33:08 +01:00
if not self.unit:
2014-10-29 20:39:04 +01:00
self.quantity = invoice_line.quantity
else:
self.quantity = Unit.compute_qty(invoice_line.unit,
invoice_line.quantity, self.unit)
2012-11-15 19:33:08 +01:00
2014-01-26 14:28:47 +01:00
@fields.depends('product')
2012-11-15 19:33:08 +01:00
def on_change_with_unit(self):
2023-03-26 09:49:00 +02:00
return self.product.default_uom if self.product else None
2012-11-15 19:33:08 +01:00
2014-01-26 14:28:47 +01:00
@fields.depends('end_date', 'product', 'start_date')
2012-11-15 19:33:08 +01:00
def on_change_with_end_date(self):
if (all(getattr(self, k, None) for k in ('product', 'start_date'))
and not self.end_date):
2012-11-15 19:33:08 +01:00
if self.product.depreciation_duration:
duration = relativedelta.relativedelta(
2013-02-12 17:38:43 +01:00
months=int(self.product.depreciation_duration), days=-1)
2012-11-15 19:33:08 +01:00
return self.start_date + duration
return self.end_date
@classmethod
def get_customer_invoice_line(cls, assets, name):
InvoiceLine = Pool().get('account.invoice.line')
invoice_lines = InvoiceLine.search([
('asset', 'in', [a.id for a in assets]),
])
result = dict((a.id, None) for a in assets)
result.update(dict((l.asset.id, l.id) for l in invoice_lines))
return result
def get_depreciated_amount(self):
2019-10-08 18:47:27 +02:00
lines = [line.depreciation for line in self.lines
2012-11-15 19:33:08 +01:00
if line.move and line.move.state == 'posted']
2019-10-08 18:47:27 +02:00
return sum(lines, Decimal(0))
2012-11-15 19:33:08 +01:00
def compute_move_dates(self):
"""
Returns all the remaining dates at which asset depreciation movement
will be issued.
"""
pool = Pool()
Config = pool.get('account.configuration')
config = Config(1)
2012-11-15 19:33:08 +01:00
start_date = max([self.start_date] + [l.date for l in self.lines])
delta = relativedelta.relativedelta(self.end_date, start_date)
# dateutil >= 2.0 has replace __nonzero__ by __bool__ which doesn't
# work in Python < 3
if delta == relativedelta.relativedelta():
if not self.lines:
return [self.end_date]
else:
return []
2012-11-15 19:33:08 +01:00
if self.frequency == 'monthly':
rule = rrule.rrule(rrule.MONTHLY, dtstart=self.start_date,
2021-04-03 14:27:09 +02:00
bymonthday=int(config.get_multivalue(
'asset_bymonthday', company=self.company.id)))
2012-11-15 19:33:08 +01:00
elif self.frequency == 'yearly':
rule = rrule.rrule(rrule.YEARLY, dtstart=self.start_date,
2021-04-03 14:27:09 +02:00
bymonth=int(config.get_multivalue(
'asset_bymonth', company=self.company.id)),
bymonthday=int(config.get_multivalue(
'asset_bymonthday', company=self.company.id)))
2012-11-15 19:33:08 +01:00
dates = [d.date()
for d in rule.between(date2datetime(start_date),
date2datetime(self.end_date))]
dates.append(self.end_date)
return dates
2019-10-08 18:47:27 +02:00
def compute_depreciation(self, amount, date, dates):
2012-11-15 19:33:08 +01:00
"""
Returns the depreciation amount for an asset on a certain date.
"""
if self.depreciation_method == 'linear':
start_date = max([self.start_date
- relativedelta.relativedelta(days=1)]
+ [l.date for l in self.lines])
2019-11-16 14:49:18 +01:00
first_delta = normalized_delta(start_date, dates[0])
if len(dates) > 1:
2019-11-16 14:49:18 +01:00
last_delta = normalized_delta(dates[-2], dates[-1])
else:
last_delta = first_delta
2012-11-15 19:33:08 +01:00
if self.frequency == 'monthly':
_, first_ndays = calendar.monthrange(
dates[0].year, dates[0].month)
2019-11-16 14:49:18 +01:00
if (calendar.isleap(dates[0].year)
and dates[0].month == February):
first_ndays -= 1
2012-11-15 19:33:08 +01:00
_, last_ndays = calendar.monthrange(
dates[-1].year, dates[-1].month)
2019-11-16 14:49:18 +01:00
if (calendar.isleap(dates[-1].year)
and dates[-1].month == February):
last_ndays -= 1
2012-11-15 19:33:08 +01:00
elif self.frequency == 'yearly':
2019-11-16 14:49:18 +01:00
first_ndays = last_ndays = 365
first_ratio = (
Decimal(min(first_delta.days, first_ndays))
/ Decimal(first_ndays))
last_ratio = (
Decimal(min(last_delta.days, last_ndays))
/ Decimal(last_ndays))
2012-11-15 19:33:08 +01:00
depreciation = amount / (
len(dates) - 2 + first_ratio + last_ratio)
if date == dates[0]:
depreciation *= first_ratio
elif date == dates[-1]:
depreciation *= last_ratio
return self.company.currency.round(depreciation)
def depreciate(self):
"""
Returns all the depreciation amounts still to be accounted.
"""
Line = Pool().get('account.asset.line')
amounts = {}
dates = self.compute_move_dates()
2019-10-08 18:47:27 +02:00
depreciated_amount = self.get_depreciated_amount()
amount = (self.depreciating_value
- depreciated_amount
2012-11-15 19:33:08 +01:00
- self.residual_value)
if amount <= 0:
return amounts
2019-10-08 18:47:27 +02:00
residual_value, acc_depreciation = (
amount, depreciated_amount + self.depreciated_amount)
2012-11-15 19:33:08 +01:00
asset_line = None
for date in dates:
2019-10-08 18:47:27 +02:00
depreciation = self.compute_depreciation(amount, date, dates)
2014-09-17 21:44:52 +02:00
amounts[date] = asset_line = Line(
acquired_value=self.value,
depreciable_basis=amount,
)
2012-11-15 19:33:08 +01:00
if depreciation > residual_value:
asset_line.depreciation = residual_value
asset_line.accumulated_depreciation = (
2019-10-08 18:47:27 +02:00
acc_depreciation + residual_value)
2012-11-15 19:33:08 +01:00
break
else:
residual_value -= depreciation
acc_depreciation += depreciation
asset_line.depreciation = depreciation
2019-10-08 18:47:27 +02:00
asset_line.accumulated_depreciation = acc_depreciation
2012-11-15 19:33:08 +01:00
else:
if residual_value > 0 and asset_line is not None:
asset_line.depreciation += residual_value
asset_line.accumulated_depreciation += residual_value
2018-06-26 19:11:40 +02:00
for asset_line in amounts.values():
2019-10-08 18:47:27 +02:00
asset_line.actual_value = (self.value
- asset_line.accumulated_depreciation)
2012-11-15 19:33:08 +01:00
return amounts
@classmethod
@ModelView.button
def create_lines(cls, assets):
2016-06-09 17:37:44 +02:00
pool = Pool()
Line = pool.get('account.asset.line')
2012-11-15 19:33:08 +01:00
2016-06-09 17:37:44 +02:00
lines = []
2012-11-15 19:33:08 +01:00
for asset in assets:
2018-06-26 19:11:40 +02:00
for date, line in asset.depreciate().items():
2012-11-15 19:33:08 +01:00
line.asset = asset.id
line.date = date
2016-06-09 17:37:44 +02:00
lines.append(line)
Line.save(lines)
2012-11-15 19:33:08 +01:00
@classmethod
@ModelView.button
def clear_lines(cls, assets):
Line = Pool().get('account.asset.line')
lines_to_delete = []
for asset in assets:
for line in asset.lines:
if not line.move or line.move.state != 'posted':
lines_to_delete.append(line)
Line.delete(lines_to_delete)
@classmethod
@ModelView.button_action('account_asset.wizard_update')
def update(cls, assets):
pass
def get_move(self, line):
"""
Return the account.move generated by an asset line.
"""
pool = Pool()
Period = pool.get('account.period')
Move = pool.get('account.move')
MoveLine = pool.get('account.move.line')
period = Period.find(self.company, line.date)
2018-08-31 14:58:05 +02:00
with Transaction().set_context(date=line.date):
expense_line = MoveLine(
credit=0,
debit=line.depreciation,
account=self.product.account_expense_used,
)
depreciation_line = MoveLine(
debit=0,
credit=line.depreciation,
account=self.product.account_depreciation_used,
)
2012-11-15 19:33:08 +01:00
2014-09-17 21:44:52 +02:00
return Move(
2014-12-03 16:13:09 +01:00
company=self.company,
2014-09-17 21:44:52 +02:00
origin=line,
period=period,
2014-09-17 21:44:52 +02:00
journal=self.account_journal,
date=line.date,
lines=[expense_line, depreciation_line],
)
2012-11-15 19:33:08 +01:00
@classmethod
def create_moves(cls, assets, date):
"""
Creates all account move on assets before a date.
"""
pool = Pool()
Move = pool.get('account.move')
Line = pool.get('account.asset.line')
cls.create_lines(assets)
2012-11-15 19:33:08 +01:00
moves = []
lines = []
2014-07-17 18:56:57 +02:00
for asset_ids in grouped_slice(assets):
2012-11-15 19:33:08 +01:00
lines += Line.search([
2014-07-17 18:56:57 +02:00
('asset', 'in', list(asset_ids)),
2012-11-15 19:33:08 +01:00
('date', '<=', date),
('move', '=', None),
])
for line in lines:
2016-06-09 17:37:44 +02:00
moves.append(line.asset.get_move(line))
Move.save(moves)
for move, line in zip(moves, lines):
line.move = move
Line.save(lines)
2014-09-17 21:44:52 +02:00
Move.post(moves)
2012-11-15 19:33:08 +01:00
def get_closing_move(self, account, date=None):
2012-11-15 19:33:08 +01:00
"""
Returns closing move values.
"""
pool = Pool()
Period = pool.get('account.period')
Date = pool.get('ir.date')
Move = pool.get('account.move')
MoveLine = pool.get('account.move.line')
if date is None:
with Transaction().set_context(company=self.company.id):
date = Date.today()
period = Period.find(self.company, date)
2012-11-15 19:33:08 +01:00
if self.supplier_invoice_line:
2018-08-31 14:58:05 +02:00
account_asset = self.supplier_invoice_line.account.current()
2012-11-15 19:33:08 +01:00
else:
account_asset = self.product.account_asset_used
2014-09-17 21:44:52 +02:00
asset_line = MoveLine(
debit=0,
credit=self.value,
account=account_asset,
)
depreciation_line = MoveLine(
2019-10-08 18:47:27 +02:00
debit=self.get_depreciated_amount() + self.depreciated_amount,
2014-09-17 21:44:52 +02:00
credit=0,
account=self.product.account_depreciation_used,
)
2012-11-15 19:33:08 +01:00
lines = [asset_line, depreciation_line]
square_amount = asset_line.credit - depreciation_line.debit
2012-11-15 19:33:08 +01:00
if square_amount:
if not account:
account = self.product.account_revenue_used
2014-09-17 21:44:52 +02:00
counter_part_line = MoveLine(
debit=square_amount if square_amount > 0 else 0,
credit=-square_amount if square_amount < 0 else 0,
account=account,
2012-11-15 19:33:08 +01:00
)
2014-09-17 21:44:52 +02:00
lines.append(counter_part_line)
return Move(
2014-12-03 16:13:09 +01:00
company=self.company,
2014-09-17 21:44:52 +02:00
origin=self,
period=period,
2014-09-17 21:44:52 +02:00
journal=self.account_journal,
date=date,
lines=lines,
)
2012-11-15 19:33:08 +01:00
@classmethod
def set_number(cls, assets):
2012-11-15 19:33:08 +01:00
'''
Fill the number field with asset sequence.
2012-11-15 19:33:08 +01:00
'''
pool = Pool()
Config = pool.get('account.configuration')
config = Config(1)
for asset in assets:
if asset.number:
2012-11-15 19:33:08 +01:00
continue
2021-04-03 14:27:09 +02:00
asset.number = config.get_multivalue(
'asset_sequence', company=asset.company.id).get()
cls.save(assets)
2012-11-15 19:33:08 +01:00
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, assets):
for asset in assets:
if asset.lines:
raise AccessError(
2022-10-04 18:14:05 +02:00
gettext('account_asset.msg_asset_draft_lines',
asset=asset.rec_name))
2012-11-15 19:33:08 +01:00
@classmethod
@ModelView.button
@Workflow.transition('running')
def run(cls, assets):
cls.set_number(assets)
2012-11-15 19:33:08 +01:00
cls.create_lines(assets)
@classmethod
@ModelView.button
@Workflow.transition('closed')
def close(cls, assets, account=None, date=None):
2012-11-15 19:33:08 +01:00
"""
Close the assets.
If account is provided, it will be used instead of the expense account.
"""
Move = Pool().get('account.move')
cls.clear_lines(assets)
moves = []
for asset in assets:
moves.append(asset.get_closing_move(account, date=date))
2016-06-09 17:37:44 +02:00
Move.save(moves)
for move, asset in zip(moves, assets):
asset.move = move
cls.save(assets)
2014-09-17 21:44:52 +02:00
Move.post(moves)
2012-11-15 19:33:08 +01:00
def get_rec_name(self, name):
return '%s - %s' % (self.number, self.product.rec_name)
2012-11-15 19:33:08 +01:00
@classmethod
def search_rec_name(cls, name, clause):
domain = []
_, operator, value = clause
if value is not None:
names = value.split(' - ', 1)
domain.append(('number', operator, value))
if len(names) != 1 and names[1]:
domain.append(('product', operator, value))
if operator.startswith('!') or operator.startswith('not'):
domain.insert(0, 'OR')
elif not operator.startswith('!') and not operator.startswith('not'):
domain.append(('id', '<', 0))
return domain
2012-11-15 19:33:08 +01:00
@classmethod
def copy(cls, assets, default=None):
if default is None:
default = {}
2018-09-01 01:02:03 +02:00
else:
default = default.copy()
2012-11-15 19:33:08 +01:00
default.setdefault('lines', [])
2022-04-19 23:24:03 +02:00
default.setdefault('update_moves', [])
default.setdefault('number', None)
2012-11-15 19:33:08 +01:00
default.setdefault('supplier_invoice_line', None)
2019-09-24 15:13:16 +02:00
default.setdefault('move')
2022-04-19 23:24:18 +02:00
default.setdefault('revisions', [])
2012-11-15 19:33:08 +01:00
return super(Asset, cls).copy(assets, default=default)
@classmethod
def delete(cls, assets):
for asset in assets:
if asset.state != 'draft':
2018-12-29 14:20:28 +01:00
raise AccessError(
2022-10-04 18:14:05 +02:00
gettext('account_asset.msg_asset_delete_draft',
2018-12-29 14:20:28 +01:00
asset=asset.rec_name))
2012-11-15 19:33:08 +01:00
return super(Asset, cls).delete(assets)
class AssetLine(ModelSQL, ModelView):
'Asset Line'
__name__ = 'account.asset.line'
asset = fields.Many2One('account.asset', 'Asset', required=True,
2018-03-23 10:15:33 +01:00
ondelete='CASCADE', readonly=True)
date = fields.Date('Date', readonly=True)
2021-08-21 09:09:55 +02:00
depreciation = Monetary(
"Depreciation", currency='currency', digits='currency',
2018-03-23 10:15:33 +01:00
required=True, readonly=True)
2021-08-21 09:09:55 +02:00
acquired_value = Monetary(
"Acquired Value", currency='currency', digits='currency',
readonly=True)
depreciable_basis = Monetary(
"Depreciable Basis", currency='currency', digits='currency',
readonly=True)
actual_value = Monetary(
"Actual Value", currency='currency', digits='currency', readonly=True)
accumulated_depreciation = Monetary(
"Accumulated Depreciation", currency='currency', digits='currency',
readonly=True)
2012-11-15 19:33:08 +01:00
move = fields.Many2One('account.move', 'Account Move', readonly=True)
2020-10-09 19:09:05 +02:00
currency = fields.Function(fields.Many2One('currency.currency',
'Currency'), 'on_change_with_currency')
2012-11-15 19:33:08 +01:00
@classmethod
def __setup__(cls):
super(AssetLine, cls).__setup__()
2021-03-07 11:13:53 +01:00
cls.__access__.add('asset')
2012-11-15 19:33:08 +01:00
cls._order.insert(0, ('date', 'ASC'))
2020-10-09 19:09:05 +02:00
@fields.depends('asset', '_parent_asset.currency')
def on_change_with_currency(self, name=None):
2023-03-26 09:49:00 +02:00
return self.asset.currency if self.asset else None
2020-10-09 19:09:05 +02:00
2012-11-15 19:33:08 +01:00
class AssetUpdateMove(ModelSQL):
'Asset - Update - Move'
__name__ = 'account.asset-update-account.move'
asset = fields.Many2One(
'account.asset', "Asset", ondelete='CASCADE', required=True)
2012-11-15 19:33:08 +01:00
move = fields.Many2One('account.move', 'Move', required=True)
class CreateMovesStart(ModelView):
'Create Moves Start'
__name__ = 'account.asset.create_moves.start'
date = fields.Date('Date')
@staticmethod
def default_date():
Date = Pool().get('ir.date')
return Date.today()
class CreateMoves(Wizard):
'Create Moves'
__name__ = 'account.asset.create_moves'
start = StateView('account.asset.create_moves.start',
'account_asset.asset_create_moves_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('OK', 'create_moves', 'tryton-ok', True),
])
create_moves = StateTransition()
@without_check_access
2012-11-15 19:33:08 +01:00
def transition_create_moves(self):
pool = Pool()
with check_access():
Asset = pool.get('account.asset')
assets = Asset.search([
('state', '=', 'running'),
])
assets = Asset.browse(assets)
2012-11-15 19:33:08 +01:00
Asset.create_moves(assets, self.start.date)
return 'end'
class UpdateAssetShowDepreciation(ModelView):
'Update Asset Show Depreciation'
__name__ = 'account.asset.update.show_depreciation'
amount = fields.Numeric('Amount', readonly=True)
date = fields.Date('Date', required=True,
domain=[
('date', '>=', Eval('latest_move_date')),
('date', '<=', Eval('next_depreciation_date')),
],
help=('The date must be between the last update/depreciation date '
'and the next depreciation date.'))
latest_move_date = fields.Date('Latest Move Date', readonly=True)
next_depreciation_date = fields.Date('Next Depreciation Date',
readonly=True)
2012-11-15 19:33:08 +01:00
depreciation_account = fields.Many2One('account.account',
'Depreciation Account', readonly=True)
counterpart_account = fields.Many2One('account.account',
'Counterpart Account')
class UpdateAsset(Wizard):
'Update Asset'
__name__ = 'account.asset.update'
2021-10-04 18:53:55 +02:00
start = StateView('account.asset.revision',
'account_asset.asset_revision_view_form', [
2012-11-15 19:33:08 +01:00
Button('Cancel', 'end', 'tryton-cancel'),
Button('OK', 'update_asset', 'tryton-ok', True),
])
update_asset = StateTransition()
show_move = StateView('account.asset.update.show_depreciation',
'account_asset.asset_update_show_depreciation_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('OK', 'create_move', 'tryton-ok', True),
])
create_move = StateTransition()
create_lines = StateTransition()
def default_start(self, fields):
return {
'value': self.record.value,
'residual_value': self.record.residual_value,
'end_date': self.record.end_date,
2021-10-04 18:53:55 +02:00
'asset': self.record.id,
2012-11-15 19:33:08 +01:00
}
def transition_update_asset(self):
if self.start.value != self.record.value:
2012-11-15 19:33:08 +01:00
return 'show_move'
return 'create_lines'
def get_latest_move_date(self, asset):
previous_dates = [datetime.date.min]
previous_dates += [m.date for m in asset.update_moves
if m.state == 'posted']
previous_dates += [l.date for l in asset.lines
if l.move and l.move.state == 'posted']
return max(previous_dates)
def get_next_depreciation_date(self, asset):
next_dates = [datetime.date.max]
next_dates += [l.date for l in asset.lines
if not l.move or l.move.state != 'posted']
return min(next_dates)
2012-11-15 19:33:08 +01:00
def default_show_move(self, fields):
return {
'amount': self.start.value - self.record.value,
2012-11-15 19:33:08 +01:00
'date': datetime.date.today(),
'depreciation_account': (
self.record.product.account_depreciation_used.id),
'counterpart_account': self.record.product.account_expense_used.id,
'latest_move_date': self.get_latest_move_date(self.record),
'next_depreciation_date': self.get_next_depreciation_date(
self.record),
2012-11-15 19:33:08 +01:00
}
def get_move(self, asset):
pool = Pool()
Period = pool.get('account.period')
Move = pool.get('account.move')
period = Period.find(asset.company, self.show_move.date)
move = Move(
2014-12-03 16:13:09 +01:00
company=asset.company,
2012-11-15 19:33:08 +01:00
origin=asset,
journal=asset.account_journal.id,
period=period,
2012-11-15 19:33:08 +01:00
date=self.show_move.date,
)
move.lines = self.get_move_lines(asset)
return move
2012-11-15 19:33:08 +01:00
def get_move_lines(self, asset):
MoveLine = Pool().get('account.move.line')
expense_line = MoveLine(
account=self.show_move.counterpart_account,
credit=self.show_move.amount if self.show_move.amount > 0 else 0,
debit=-self.show_move.amount if self.show_move.amount < 0 else 0,
)
depreciation_line = MoveLine(
account=self.show_move.depreciation_account,
credit=expense_line.debit,
debit=expense_line.credit,
)
return [expense_line, depreciation_line]
def transition_create_move(self):
pool = Pool()
Move = pool.get('account.move')
latest_move_date = self.show_move.latest_move_date
next_date = self.show_move.next_depreciation_date
if not (latest_move_date <= self.show_move.date <= next_date):
raise ValueError('The update move date is invalid')
move = self.get_move(self.record)
2012-11-15 19:33:08 +01:00
move.save()
self.model.write([self.record], {
2012-11-15 19:33:08 +01:00
'update_moves': [('add', [move.id])],
})
Move.post([move])
return 'create_lines'
def transition_create_lines(self):
self.model.write([self.record], {
2012-11-15 19:33:08 +01:00
'value': self.start.value,
'residual_value': self.start.residual_value,
'end_date': self.start.end_date,
})
self.model.clear_lines([self.record])
self.model.create_lines([self.record])
2021-10-04 18:53:55 +02:00
self.start.asset = self.record
self.start.save()
2012-11-15 19:33:08 +01:00
return 'end'
2015-02-03 17:20:11 +01:00
2021-10-04 18:53:55 +02:00
class AssetRevision(ModelSQL, ModelView):
"Asset Revision"
__name__ = 'account.asset.revision'
currency = fields.Function(
fields.Many2One('currency.currency', "Currency"),
'on_change_with_currency')
value = Monetary(
"Asset Value", currency='currency', digits='currency',
required=True)
residual_value = Monetary(
"Residual Value", currency='currency', digits='currency',
required=True)
end_date = fields.Date("End Date", required=True)
origin = fields.Reference("Origin", selection='get_origins')
2021-10-04 18:53:55 +02:00
description = fields.Char("Description")
asset = fields.Many2One('account.asset', "Asset", required=True)
2021-10-04 18:53:55 +02:00
@classmethod
def __setup__(cls):
super().__setup__()
cls.__access__.add('asset')
@fields.depends('asset', '_parent_asset.currency')
def on_change_with_currency(self, name=None):
2023-03-26 09:49:00 +02:00
return self.asset.currency if self.asset else None
2021-10-04 18:53:55 +02:00
@fields.depends('origin', 'value', 'asset', '_parent_asset.value')
def on_change_origin(self, name=None):
pool = Pool()
InvoiceLine = pool.get('account.invoice.line')
if isinstance(self.origin, InvoiceLine) and self.origin.id >= 0:
self.value = self.asset.value + self.origin.amount
@staticmethod
def _get_origin():
"Return list of Model names for origin Reference"
return ['account.invoice.line']
@classmethod
def get_origins(cls):
pool = Pool()
IrModel = pool.get('ir.model')
get_name = IrModel.get_name
models = cls._get_origin()
return [(None, '')] + [(m, get_name(m)) for m in models]
2015-02-03 17:20:11 +01:00
class AssetDepreciationTable(CompanyReport):
'Asset Depreciation Table'
__name__ = 'account.asset.depreciation_table'
@classmethod
2021-02-21 19:19:26 +01:00
def get_context(cls, records, header, data):
context = super().get_context(records, header, data)
2015-02-03 17:20:11 +01:00
AssetDepreciation = cls.get_asset_depreciation()
AssetDepreciation.start_date = data['start_date']
AssetDepreciation.end_date = data['end_date']
Grouper = cls.get_grouper()
grouped_assets = groupby(sorted(records, key=cls.group_assets),
cls.group_assets)
context['grouped_depreciations'] = grouped_depreciations = []
for g_key, assets in grouped_assets:
depreciations = [AssetDepreciation(a) for a in assets]
grouped_depreciations.append(Grouper(g_key, depreciations))
return context
@staticmethod
def group_assets(asset):
return asset.product
@classmethod
def get_grouper(cls):
class Grouper(object):
def __init__(self, key, depreciations):
self.product = key
self.depreciations = depreciations
def adder(attr_name):
def _sum(self):
return sum(getattr(d, attr_name)
for d in self.depreciations if getattr(d, attr_name))
return _sum
2015-02-03 17:20:11 +01:00
grouped_attributes = {
'start_fixed_value',
'value_increase',
'value_decrease',
'end_fixed_value',
'start_value',
'amortization_increase',
'amortization_decrease',
'end_value',
'actual_value',
'closing_value',
}
for attr_name in grouped_attributes:
descr = cached_property(adder(attr_name))
setattr(Grouper, attr_name, descr)
if hasattr(descr, '__set_name__'):
descr.__set_name__(Grouper, attr_name)
2015-02-03 17:20:11 +01:00
return Grouper
@classmethod
def get_asset_depreciation(cls):
class AssetDepreciation(object):
def __init__(self, asset):
self.asset = asset
@cached_property
def asset_lines(self):
2018-07-24 21:59:19 +02:00
return [l for l in self.asset.lines
if self.start_date < l.date <= self.end_date]
2015-02-03 17:20:11 +01:00
@cached_property
def update_lines(self):
2020-03-01 12:33:51 +01:00
def filter_(l):
return (l.account.type.expense
and self.start_date < l.move.date <= self.end_date)
2018-06-26 19:11:40 +02:00
return list(filter(filter_,
(l for m in self.asset.update_moves for l in m.lines)))
2015-02-03 17:20:11 +01:00
@cached_property
def start_fixed_value(self):
if self.asset.end_date < self.start_date:
return self.asset.value
elif (self.start_date < self.asset.start_date
or not self.asset_lines):
2015-02-03 17:20:11 +01:00
return 0
value = self.asset_lines[0].acquired_value
date = self.asset_lines[0].date
for line in self.update_lines:
if line.move.date < date:
value += line.debit - line.credit
return value
@cached_property
def value_increase(self):
value = sum(l.debit - l.credit for l in self.update_lines
if l.debit > l.credit)
if (self.asset_lines
and self.start_date < self.asset.start_date):
2015-02-03 17:20:11 +01:00
value += self.asset_lines[0].acquired_value
return value
@cached_property
def value_decrease(self):
return sum(l.credit - l.debit for l in self.update_lines
if l.credit > l.debit)
@cached_property
def end_fixed_value(self):
if not self.asset_lines:
return self.start_fixed_value
2015-02-03 17:20:11 +01:00
value = self.asset_lines[-1].acquired_value
date = self.asset_lines[-1].date
for line in self.update_lines:
if line.move.date > date:
value += line.debit - line.credit
return value
@cached_property
def start_value(self):
if not self.asset_lines:
if self.asset.start_date > self.end_date:
return self.asset.value
else:
return 0
2015-02-03 17:20:11 +01:00
return (self.asset_lines[0].actual_value
+ self.asset_lines[0].depreciation)
@cached_property
def amortization_increase(self):
return sum(l.depreciation for l in self.asset_lines
if l.depreciation > 0)
@cached_property
def amortization_decrease(self):
return sum(l.depreciation for l in self.asset_lines
if l.depreciation < 0)
@cached_property
def end_value(self):
if not self.asset_lines:
return self.start_value
2015-02-03 17:20:11 +01:00
return self.asset_lines[-1].actual_value
@cached_property
def actual_value(self):
value = self.end_value
if self.asset_lines:
date = self.asset_lines[-1].date
value += sum(l.debit - l.credit for l in self.update_lines
if l.move.date > date)
2015-02-03 17:20:11 +01:00
return value
@cached_property
def closing_value(self):
if not self.asset.move:
return None
revenue_lines = [l for l in self.asset.move.lines
if l.account == self.asset.product.account_revenue_used]
return sum(l.debit - l.credit for l in revenue_lines)
return AssetDepreciation
class PrintDepreciationTableStart(ModelView):
'Asset Depreciation Table Start'
__name__ = 'account.asset.print_depreciation_table.start'
start_date = fields.Date('Start Date', required=True,
domain=[('start_date', '<', Eval('end_date'))])
2015-02-03 17:20:11 +01:00
end_date = fields.Date('End Date', required=True,
domain=[('end_date', '>', Eval('start_date'))])
2015-02-03 17:20:11 +01:00
@staticmethod
def default_start_date():
return datetime.date.today() - relativedelta.relativedelta(years=1)
@staticmethod
def default_end_date():
return datetime.date.today()
class PrintDepreciationTable(Wizard):
'Asset Depreciation Table'
__name__ = 'account.asset.print_depreciation_table'
start = StateView('account.asset.print_depreciation_table.start',
'account_asset.print_depreciation_table_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
])
2015-08-14 18:04:29 +02:00
print_ = StateReport('account.asset.depreciation_table')
2015-02-03 17:20:11 +01:00
@check_access
2015-02-03 17:20:11 +01:00
def do_print_(self, action):
pool = Pool()
Asset = pool.get('account.asset')
assets = Asset.search([
['OR',
('purchase_date', '<=', self.start.end_date),
('start_date', '<=', self.start.end_date),
],
['OR',
('move', '=', None),
('move.date', '>=', self.start.start_date),
],
('state', '!=', 'draft'),
])
2021-02-21 19:19:26 +01:00
if not assets:
raise PrintDepreciationTableError(
gettext('account_asset.msg_no_assets'))
2015-02-03 17:20:11 +01:00
return action, {
'ids': [a.id for a in assets],
'start_date': self.start.start_date,
'end_date': self.start.end_date,
}