init
commit
533b72d6c0
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import wizard
|
||||
from . import report
|
|
@ -0,0 +1,33 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Odoo 14 Assets Management',
|
||||
'version': '14.0.2.6.0',
|
||||
'author': 'Odoo Mates, Odoo SA',
|
||||
'depends': ['account'],
|
||||
'description': """Manage assets owned by a company or a person.
|
||||
Keeps track of depreciation's, and creates corresponding journal entries""",
|
||||
'summary': 'Odoo 14 Assets Management',
|
||||
'category': 'Accounting',
|
||||
'sequence': 10,
|
||||
'website': 'https://www.odoomates.tech',
|
||||
'license': 'LGPL-3',
|
||||
'images': ['static/description/assets.gif'],
|
||||
'data': [
|
||||
'security/account_asset_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'wizard/asset_depreciation_confirmation_wizard_views.xml',
|
||||
'wizard/asset_modify_views.xml',
|
||||
'views/account_asset_views.xml',
|
||||
'views/account_invoice_views.xml',
|
||||
'views/account_asset_templates.xml',
|
||||
'views/product_views.xml',
|
||||
# 'views/res_config_settings_views.xml',
|
||||
'report/account_asset_report_views.xml',
|
||||
'data/account_asset_data.xml',
|
||||
],
|
||||
'qweb': [
|
||||
"static/src/xml/account_asset_template.xml",
|
||||
],
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding='UTF-8'?>
|
||||
<odoo>
|
||||
|
||||
<record id="account_asset_cron" model="ir.cron">
|
||||
<field name="name">Account Asset: Generate asset entries</field>
|
||||
<field name="model_id" ref="model_account_asset_asset"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_generate_entries()</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">months</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import account
|
||||
from . import account_asset
|
||||
from . import account_invoice
|
||||
from . import product
|
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
asset_depreciation_ids = fields.One2many('account.asset.depreciation.line', 'move_id',
|
||||
string='Assets Depreciation Lines')
|
||||
|
||||
def button_cancel(self):
|
||||
for move in self:
|
||||
for line in move.asset_depreciation_ids:
|
||||
line.move_posted_check = False
|
||||
return super(AccountMove, self).button_cancel()
|
||||
|
||||
def post(self):
|
||||
for move in self:
|
||||
for depreciation_line in move.asset_depreciation_ids:
|
||||
depreciation_line.post_lines_and_close_asset()
|
||||
return super(AccountMove, self).post()
|
|
@ -0,0 +1,653 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import calendar
|
||||
from datetime import date, datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools import float_compare, float_is_zero
|
||||
|
||||
|
||||
class AccountAssetCategory(models.Model):
|
||||
_name = 'account.asset.category'
|
||||
_description = 'Asset category'
|
||||
|
||||
active = fields.Boolean(default=True)
|
||||
name = fields.Char(required=True, index=True, string="Asset Type")
|
||||
account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account')
|
||||
analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tag')
|
||||
account_asset_id = fields.Many2one('account.account', string='Asset Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)], help="Account used to record the purchase of the asset at its original price.")
|
||||
account_depreciation_id = fields.Many2one('account.account', string='Depreciation Entries: Asset Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)], help="Account used in the depreciation entries, to decrease the asset value.")
|
||||
account_depreciation_expense_id = fields.Many2one('account.account', string='Depreciation Entries: Expense Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)], help="Account used in the periodical entries, to record a part of the asset as expense.")
|
||||
journal_id = fields.Many2one('account.journal', string='Journal', required=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env['res.company']._company_default_get('account.asset.category'))
|
||||
method = fields.Selection([('linear', 'Linear'), ('degressive', 'Degressive')], string='Computation Method', required=True, default='linear',
|
||||
help="Choose the method to use to compute the amount of depreciation lines.\n"
|
||||
" * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n"
|
||||
" * Degressive: Calculated on basis of: Residual Value * Degressive Factor")
|
||||
method_number = fields.Integer(string='Number of Depreciations', default=5, help="The number of depreciations needed to depreciate your asset")
|
||||
method_period = fields.Integer(string='Period Length', default=1, help="State here the time between 2 depreciations, in months", required=True)
|
||||
method_progress_factor = fields.Float('Degressive Factor', default=0.3)
|
||||
method_time = fields.Selection([('number', 'Number of Entries'), ('end', 'Ending Date')], string='Time Method', required=True, default='number',
|
||||
help="Choose the method to use to compute the dates and number of entries.\n"
|
||||
" * Number of Entries: Fix the number of entries and the time between 2 depreciations.\n"
|
||||
" * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.")
|
||||
method_end = fields.Date('Ending date')
|
||||
prorata = fields.Boolean(string='Prorata Temporis', help='Indicates that the first depreciation entry for this asset have to be done from the purchase date instead of the first of January')
|
||||
open_asset = fields.Boolean(string='Auto-Confirm Assets', help="Check this if you want to automatically confirm the assets of this category when created by invoices.")
|
||||
group_entries = fields.Boolean(string='Group Journal Entries', help="Check this if you want to group the generated entries by categories.")
|
||||
type = fields.Selection([('sale', 'Sale: Revenue Recognition'), ('purchase', 'Purchase: Asset')], required=True, index=True, default='purchase')
|
||||
date_first_depreciation = fields.Selection([
|
||||
('last_day_period', 'Based on Last Day of Purchase Period'),
|
||||
('manual', 'Manual (Defaulted on Purchase Date)')],
|
||||
string='Depreciation Dates', default='manual', required=True,
|
||||
help='The way to compute the date of the first depreciation.\n'
|
||||
' * Based on last day of purchase period: The depreciation dates will be based on the last day of the purchase month or the purchase year (depending on the periodicity of the depreciations).\n'
|
||||
' * Based on purchase date: The depreciation dates will be based on the purchase date.')
|
||||
|
||||
@api.onchange('account_asset_id')
|
||||
def onchange_account_asset(self):
|
||||
if self.type == "purchase":
|
||||
self.account_depreciation_id = self.account_asset_id
|
||||
elif self.type == "sale":
|
||||
self.account_depreciation_expense_id = self.account_asset_id
|
||||
|
||||
@api.onchange('type')
|
||||
def onchange_type(self):
|
||||
if self.type == 'sale':
|
||||
self.prorata = True
|
||||
self.method_period = 1
|
||||
else:
|
||||
self.method_period = 12
|
||||
|
||||
@api.onchange('method_time')
|
||||
def _onchange_method_time(self):
|
||||
if self.method_time != 'number':
|
||||
self.prorata = False
|
||||
|
||||
|
||||
class AccountAssetAsset(models.Model):
|
||||
_name = 'account.asset.asset'
|
||||
_description = 'Asset/Revenue Recognition'
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
entry_count = fields.Integer(compute='_entry_count', string='# Asset Entries')
|
||||
name = fields.Char(string='Asset Name', required=True, readonly=True, states={'draft': [('readonly', False)]})
|
||||
code = fields.Char(string='Reference', size=32, readonly=True, states={'draft': [('readonly', False)]})
|
||||
value = fields.Float(string='Gross Value', required=True, readonly=True, digits=0, states={'draft': [('readonly', False)]})
|
||||
currency_id = fields.Many2one('res.currency', string='Currency', required=True, readonly=True, states={'draft': [('readonly', False)]},
|
||||
default=lambda self: self.env.user.company_id.currency_id.id)
|
||||
company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, states={'draft': [('readonly', False)]},
|
||||
default=lambda self: self.env['res.company']._company_default_get('account.asset.asset'))
|
||||
note = fields.Text()
|
||||
category_id = fields.Many2one('account.asset.category', string='Category', required=True, change_default=True, readonly=True, states={'draft': [('readonly', False)]})
|
||||
date = fields.Date(string='Date', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=fields.Date.context_today)
|
||||
state = fields.Selection([('draft', 'Draft'), ('open', 'Running'), ('close', 'Close')], 'Status', required=True, copy=False, default='draft',
|
||||
help="When an asset is created, the status is 'Draft'.\n"
|
||||
"If the asset is confirmed, the status goes in 'Running' and the depreciation lines can be posted in the accounting.\n"
|
||||
"You can manually close an asset when the depreciation is over. If the last line of depreciation is posted, the asset automatically goes in that status.")
|
||||
active = fields.Boolean(default=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Partner', readonly=True, states={'draft': [('readonly', False)]})
|
||||
method = fields.Selection([('linear', 'Linear'), ('degressive', 'Degressive')], string='Computation Method', required=True, readonly=True, states={'draft': [('readonly', False)]}, default='linear',
|
||||
help="Choose the method to use to compute the amount of depreciation lines.\n * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n"
|
||||
" * Degressive: Calculated on basis of: Residual Value * Degressive Factor")
|
||||
method_number = fields.Integer(string='Number of Depreciations', readonly=True, states={'draft': [('readonly', False)]}, default=5, help="The number of depreciations needed to depreciate your asset")
|
||||
method_period = fields.Integer(string='Number of Months in a Period', required=True, readonly=True, default=12, states={'draft': [('readonly', False)]},
|
||||
help="The amount of time between two depreciations, in months")
|
||||
method_end = fields.Date(string='Ending Date', readonly=True, states={'draft': [('readonly', False)]})
|
||||
method_progress_factor = fields.Float(string='Degressive Factor', readonly=True, default=0.3, states={'draft': [('readonly', False)]})
|
||||
value_residual = fields.Float(compute='_amount_residual', digits=0, string='Residual Value')
|
||||
method_time = fields.Selection([('number', 'Number of Entries'), ('end', 'Ending Date')], string='Time Method', required=True, readonly=True, default='number', states={'draft': [('readonly', False)]},
|
||||
help="Choose the method to use to compute the dates and number of entries.\n"
|
||||
" * Number of Entries: Fix the number of entries and the time between 2 depreciations.\n"
|
||||
" * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.")
|
||||
prorata = fields.Boolean(string='Prorata Temporis', readonly=True, states={'draft': [('readonly', False)]},
|
||||
help='Indicates that the first depreciation entry for this asset have to be done from the asset date (purchase date) instead of the first January / Start date of fiscal year')
|
||||
depreciation_line_ids = fields.One2many('account.asset.depreciation.line', 'asset_id', string='Depreciation Lines', readonly=True, states={'draft': [('readonly', False)], 'open': [('readonly', False)]})
|
||||
salvage_value = fields.Float(string='Salvage Value', digits=0, readonly=True, states={'draft': [('readonly', False)]},
|
||||
help="It is the amount you plan to have that you cannot depreciate.")
|
||||
invoice_id = fields.Many2one('account.move', string='Invoice', states={'draft': [('readonly', False)]}, copy=False)
|
||||
type = fields.Selection(related="category_id.type", string='Type', required=True)
|
||||
account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account')
|
||||
analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tag')
|
||||
date_first_depreciation = fields.Selection([
|
||||
('last_day_period', 'Based on Last Day of Purchase Period'),
|
||||
('manual', 'Manual')],
|
||||
string='Depreciation Dates', default='manual',
|
||||
readonly=True, states={'draft': [('readonly', False)]}, required=True,
|
||||
help='The way to compute the date of the first depreciation.\n'
|
||||
' * Based on last day of purchase period: The depreciation dates will be based on the last day of the purchase month or the purchase year (depending on the periodicity of the depreciations).\n'
|
||||
' * Based on purchase date: The depreciation dates will be based on the purchase date.\n')
|
||||
first_depreciation_manual_date = fields.Date(
|
||||
string='First Depreciation Date',
|
||||
readonly=True, states={'draft': [('readonly', False)]},
|
||||
help='Note that this date does not alter the computation of the first journal entry in case of prorata temporis assets. It simply changes its accounting date'
|
||||
)
|
||||
|
||||
|
||||
def unlink(self):
|
||||
for asset in self:
|
||||
if asset.state in ['open', 'close']:
|
||||
raise UserError(_('You cannot delete a document that is in %s state.') % (asset.state,))
|
||||
for depreciation_line in asset.depreciation_line_ids:
|
||||
if depreciation_line.move_id:
|
||||
raise UserError(_('You cannot delete a document that contains posted entries.'))
|
||||
return super(AccountAssetAsset, self).unlink()
|
||||
|
||||
@api.model
|
||||
def _cron_generate_entries(self):
|
||||
self.compute_generated_entries(datetime.today())
|
||||
|
||||
@api.model
|
||||
def compute_generated_entries(self, date, asset_type=None):
|
||||
# Entries generated : one by grouped category and one by asset from ungrouped category
|
||||
created_move_ids = []
|
||||
type_domain = []
|
||||
if asset_type:
|
||||
type_domain = [('type', '=', asset_type)]
|
||||
|
||||
ungrouped_assets = self.env['account.asset.asset'].search(type_domain + [('state', '=', 'open'), ('category_id.group_entries', '=', False)])
|
||||
created_move_ids += ungrouped_assets._compute_entries(date, group_entries=False)
|
||||
|
||||
for grouped_category in self.env['account.asset.category'].search(type_domain + [('group_entries', '=', True)]):
|
||||
assets = self.env['account.asset.asset'].search([('state', '=', 'open'), ('category_id', '=', grouped_category.id)])
|
||||
created_move_ids += assets._compute_entries(date, group_entries=True)
|
||||
return created_move_ids
|
||||
|
||||
def _compute_board_amount(self, sequence, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date):
|
||||
amount = 0
|
||||
if sequence == undone_dotation_number:
|
||||
amount = residual_amount
|
||||
else:
|
||||
if self.method == 'linear':
|
||||
amount = amount_to_depr / (undone_dotation_number - len(posted_depreciation_line_ids))
|
||||
if self.prorata:
|
||||
amount = amount_to_depr / self.method_number
|
||||
if sequence == 1:
|
||||
date = self.date
|
||||
if self.method_period % 12 != 0:
|
||||
month_days = calendar.monthrange(date.year, date.month)[1]
|
||||
days = month_days - date.day + 1
|
||||
amount = (amount_to_depr / self.method_number) / month_days * days
|
||||
else:
|
||||
days = (self.company_id.compute_fiscalyear_dates(date)['date_to'] - date).days + 1
|
||||
amount = (amount_to_depr / self.method_number) / total_days * days
|
||||
elif self.method == 'degressive':
|
||||
amount = residual_amount * self.method_progress_factor
|
||||
if self.prorata:
|
||||
if sequence == 1:
|
||||
date = self.date
|
||||
if self.method_period % 12 != 0:
|
||||
month_days = calendar.monthrange(date.year, date.month)[1]
|
||||
days = month_days - date.day + 1
|
||||
amount = (residual_amount * self.method_progress_factor) / month_days * days
|
||||
else:
|
||||
days = (self.company_id.compute_fiscalyear_dates(date)['date_to'] - date).days + 1
|
||||
amount = (residual_amount * self.method_progress_factor) / total_days * days
|
||||
return amount
|
||||
|
||||
def _compute_board_undone_dotation_nb(self, depreciation_date, total_days):
|
||||
undone_dotation_number = self.method_number
|
||||
if self.method_time == 'end':
|
||||
end_date = self.method_end
|
||||
undone_dotation_number = 0
|
||||
while depreciation_date <= end_date:
|
||||
depreciation_date = date(depreciation_date.year, depreciation_date.month, depreciation_date.day) + relativedelta(months=+self.method_period)
|
||||
undone_dotation_number += 1
|
||||
if self.prorata:
|
||||
undone_dotation_number += 1
|
||||
return undone_dotation_number
|
||||
|
||||
|
||||
def compute_depreciation_board(self):
|
||||
self.ensure_one()
|
||||
|
||||
posted_depreciation_line_ids = self.depreciation_line_ids.filtered(lambda x: x.move_check).sorted(key=lambda l: l.depreciation_date)
|
||||
unposted_depreciation_line_ids = self.depreciation_line_ids.filtered(lambda x: not x.move_check)
|
||||
|
||||
# Remove old unposted depreciation lines. We cannot use unlink() with One2many field
|
||||
commands = [(2, line_id.id, False) for line_id in unposted_depreciation_line_ids]
|
||||
|
||||
if self.value_residual != 0.0:
|
||||
amount_to_depr = residual_amount = self.value_residual
|
||||
|
||||
# if we already have some previous validated entries, starting date is last entry + method period
|
||||
if posted_depreciation_line_ids and posted_depreciation_line_ids[-1].depreciation_date:
|
||||
last_depreciation_date = fields.Date.from_string(posted_depreciation_line_ids[-1].depreciation_date)
|
||||
depreciation_date = last_depreciation_date + relativedelta(months=+self.method_period)
|
||||
else:
|
||||
# depreciation_date computed from the purchase date
|
||||
depreciation_date = self.date
|
||||
if self.date_first_depreciation == 'last_day_period':
|
||||
# depreciation_date = the last day of the month
|
||||
depreciation_date = depreciation_date + relativedelta(day=31)
|
||||
# ... or fiscalyear depending the number of period
|
||||
if self.method_period == 12:
|
||||
depreciation_date = depreciation_date + relativedelta(month=self.company_id.fiscalyear_last_month)
|
||||
depreciation_date = depreciation_date + relativedelta(day=self.company_id.fiscalyear_last_day)
|
||||
if depreciation_date < self.date:
|
||||
depreciation_date = depreciation_date + relativedelta(years=1)
|
||||
elif self.first_depreciation_manual_date and self.first_depreciation_manual_date != self.date:
|
||||
# depreciation_date set manually from the 'first_depreciation_manual_date' field
|
||||
depreciation_date = self.first_depreciation_manual_date
|
||||
|
||||
total_days = (depreciation_date.year % 4) and 365 or 366
|
||||
month_day = depreciation_date.day
|
||||
undone_dotation_number = self._compute_board_undone_dotation_nb(depreciation_date, total_days)
|
||||
|
||||
for x in range(len(posted_depreciation_line_ids), undone_dotation_number):
|
||||
sequence = x + 1
|
||||
amount = self._compute_board_amount(sequence, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date)
|
||||
amount = self.currency_id.round(amount)
|
||||
if float_is_zero(amount, precision_rounding=self.currency_id.rounding):
|
||||
continue
|
||||
residual_amount -= amount
|
||||
vals = {
|
||||
'amount': amount,
|
||||
'asset_id': self.id,
|
||||
'sequence': sequence,
|
||||
'name': (self.code or '') + '/' + str(sequence),
|
||||
'remaining_value': residual_amount,
|
||||
'depreciated_value': self.value - (self.salvage_value + residual_amount),
|
||||
'depreciation_date': depreciation_date,
|
||||
}
|
||||
commands.append((0, False, vals))
|
||||
|
||||
depreciation_date = depreciation_date + relativedelta(months=+self.method_period)
|
||||
|
||||
if month_day > 28 and self.date_first_depreciation == 'manual':
|
||||
max_day_in_month = calendar.monthrange(depreciation_date.year, depreciation_date.month)[1]
|
||||
depreciation_date = depreciation_date.replace(day=min(max_day_in_month, month_day))
|
||||
|
||||
# datetime doesn't take into account that the number of days is not the same for each month
|
||||
if not self.prorata and self.method_period % 12 != 0 and self.date_first_depreciation == 'last_day_period':
|
||||
max_day_in_month = calendar.monthrange(depreciation_date.year, depreciation_date.month)[1]
|
||||
depreciation_date = depreciation_date.replace(day=max_day_in_month)
|
||||
|
||||
self.write({'depreciation_line_ids': commands})
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def validate(self):
|
||||
self.write({'state': 'open'})
|
||||
fields = [
|
||||
'method',
|
||||
'method_number',
|
||||
'method_period',
|
||||
'method_end',
|
||||
'method_progress_factor',
|
||||
'method_time',
|
||||
'salvage_value',
|
||||
'invoice_id',
|
||||
]
|
||||
ref_tracked_fields = self.env['account.asset.asset'].fields_get(fields)
|
||||
for asset in self:
|
||||
tracked_fields = ref_tracked_fields.copy()
|
||||
if asset.method == 'linear':
|
||||
del(tracked_fields['method_progress_factor'])
|
||||
if asset.method_time != 'end':
|
||||
del(tracked_fields['method_end'])
|
||||
else:
|
||||
del(tracked_fields['method_number'])
|
||||
dummy, tracking_value_ids = asset._message_track(tracked_fields, dict.fromkeys(fields))
|
||||
asset.message_post(subject=_('Asset created'), tracking_value_ids=tracking_value_ids)
|
||||
|
||||
def _return_disposal_view(self, move_ids):
|
||||
name = _('Disposal Move')
|
||||
view_mode = 'form'
|
||||
if len(move_ids) > 1:
|
||||
name = _('Disposal Moves')
|
||||
view_mode = 'tree,form'
|
||||
return {
|
||||
'name': name,
|
||||
'view_type': 'form',
|
||||
'view_mode': view_mode,
|
||||
'res_model': 'account.move',
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'current',
|
||||
'res_id': move_ids[0],
|
||||
}
|
||||
|
||||
def _get_disposal_moves(self):
|
||||
move_ids = []
|
||||
for asset in self:
|
||||
unposted_depreciation_line_ids = asset.depreciation_line_ids.filtered(lambda x: not x.move_check)
|
||||
if unposted_depreciation_line_ids:
|
||||
old_values = {
|
||||
'method_end': asset.method_end,
|
||||
'method_number': asset.method_number,
|
||||
}
|
||||
|
||||
# Remove all unposted depr. lines
|
||||
commands = [(2, line_id.id, False) for line_id in unposted_depreciation_line_ids]
|
||||
|
||||
# Create a new depr. line with the residual amount and post it
|
||||
sequence = len(asset.depreciation_line_ids) - len(unposted_depreciation_line_ids) + 1
|
||||
today = fields.Datetime.today()
|
||||
vals = {
|
||||
'amount': asset.value_residual,
|
||||
'asset_id': asset.id,
|
||||
'sequence': sequence,
|
||||
'name': (asset.code or '') + '/' + str(sequence),
|
||||
'remaining_value': 0,
|
||||
'depreciated_value': asset.value - asset.salvage_value, # the asset is completely depreciated
|
||||
'depreciation_date': today,
|
||||
}
|
||||
commands.append((0, False, vals))
|
||||
asset.write({'depreciation_line_ids': commands, 'method_end': today, 'method_number': sequence})
|
||||
tracked_fields = self.env['account.asset.asset'].fields_get(['method_number', 'method_end'])
|
||||
changes, tracking_value_ids = asset._message_track(tracked_fields, old_values)
|
||||
if changes:
|
||||
asset.message_post(subject=_('Asset sold or disposed. Accounting entry awaiting for validation.'), tracking_value_ids=tracking_value_ids)
|
||||
move_ids += asset.depreciation_line_ids[-1].create_move(post_move=False)
|
||||
|
||||
return move_ids
|
||||
|
||||
|
||||
def set_to_close(self):
|
||||
move_ids = self._get_disposal_moves()
|
||||
if move_ids:
|
||||
return self._return_disposal_view(move_ids)
|
||||
# Fallback, as if we just clicked on the smartbutton
|
||||
return self.open_entries()
|
||||
|
||||
def set_to_draft(self):
|
||||
self.write({'state': 'draft'})
|
||||
|
||||
@api.depends('value', 'salvage_value', 'depreciation_line_ids.move_check', 'depreciation_line_ids.amount')
|
||||
def _amount_residual(self):
|
||||
for rec in self:
|
||||
total_amount = 0.0
|
||||
for line in rec.depreciation_line_ids:
|
||||
if line.move_check:
|
||||
total_amount += line.amount
|
||||
rec.value_residual = rec.value - total_amount - rec.salvage_value
|
||||
|
||||
@api.onchange('company_id')
|
||||
def onchange_company_id(self):
|
||||
self.currency_id = self.company_id.currency_id.id
|
||||
|
||||
|
||||
@api.onchange('date_first_depreciation')
|
||||
def onchange_date_first_depreciation(self):
|
||||
for record in self:
|
||||
if record.date_first_depreciation == 'manual':
|
||||
record.first_depreciation_manual_date = record.date
|
||||
|
||||
|
||||
@api.depends('depreciation_line_ids.move_id')
|
||||
def _entry_count(self):
|
||||
for asset in self:
|
||||
res = self.env['account.asset.depreciation.line'].search_count([('asset_id', '=', asset.id), ('move_id', '!=', False)])
|
||||
asset.entry_count = res or 0
|
||||
|
||||
|
||||
@api.constrains('prorata', 'method_time')
|
||||
def _check_prorata(self):
|
||||
if self.prorata and self.method_time != 'number':
|
||||
raise ValidationError(_('Prorata temporis can be applied only for the "number of depreciations" time method.'))
|
||||
|
||||
@api.onchange('category_id')
|
||||
def onchange_category_id(self):
|
||||
vals = self.onchange_category_id_values(self.category_id.id)
|
||||
# We cannot use 'write' on an object that doesn't exist yet
|
||||
if vals:
|
||||
for k, v in vals['value'].items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def onchange_category_id_values(self, category_id):
|
||||
if category_id:
|
||||
category = self.env['account.asset.category'].browse(category_id)
|
||||
return {
|
||||
'value': {
|
||||
'method': category.method,
|
||||
'method_number': category.method_number,
|
||||
'method_time': category.method_time,
|
||||
'method_period': category.method_period,
|
||||
'method_progress_factor': category.method_progress_factor,
|
||||
'method_end': category.method_end,
|
||||
'prorata': category.prorata,
|
||||
'date_first_depreciation': category.date_first_depreciation,
|
||||
'account_analytic_id': category.account_analytic_id.id,
|
||||
'analytic_tag_ids': [(6, 0, category.analytic_tag_ids.ids)],
|
||||
}
|
||||
}
|
||||
|
||||
@api.onchange('method_time')
|
||||
def onchange_method_time(self):
|
||||
if self.method_time != 'number':
|
||||
self.prorata = False
|
||||
|
||||
|
||||
def copy_data(self, default=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
default['name'] = self.name + _(' (copy)')
|
||||
return super(AccountAssetAsset, self).copy_data(default)
|
||||
|
||||
|
||||
def _compute_entries(self, date, group_entries=False):
|
||||
depreciation_ids = self.env['account.asset.depreciation.line'].search([
|
||||
('asset_id', 'in', self.ids), ('depreciation_date', '<=', date),
|
||||
('move_check', '=', False)])
|
||||
if group_entries:
|
||||
return depreciation_ids.create_grouped_move()
|
||||
return depreciation_ids.create_move()
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
asset = super(AccountAssetAsset, self.with_context(mail_create_nolog=True)).create(vals)
|
||||
asset.sudo().compute_depreciation_board()
|
||||
return asset
|
||||
|
||||
|
||||
def write(self, vals):
|
||||
res = super(AccountAssetAsset, self).write(vals)
|
||||
if 'depreciation_line_ids' not in vals and 'state' not in vals:
|
||||
for rec in self:
|
||||
rec.compute_depreciation_board()
|
||||
return res
|
||||
|
||||
|
||||
def open_entries(self):
|
||||
move_ids = []
|
||||
for asset in self:
|
||||
for depreciation_line in asset.depreciation_line_ids:
|
||||
if depreciation_line.move_id:
|
||||
move_ids.append(depreciation_line.move_id.id)
|
||||
return {
|
||||
'name': _('Journal Entries'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'account.move',
|
||||
'view_id': False,
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': [('id', 'in', move_ids)],
|
||||
}
|
||||
|
||||
|
||||
class AccountAssetDepreciationLine(models.Model):
|
||||
_name = 'account.asset.depreciation.line'
|
||||
_description = 'Asset depreciation line'
|
||||
|
||||
name = fields.Char(string='Depreciation Name', required=True, index=True)
|
||||
sequence = fields.Integer(required=True)
|
||||
asset_id = fields.Many2one('account.asset.asset', string='Asset', required=True, ondelete='cascade')
|
||||
parent_state = fields.Selection(related='asset_id.state', string='State of Asset')
|
||||
amount = fields.Float(string='Current Depreciation', digits=0, required=True)
|
||||
remaining_value = fields.Float(string='Next Period Depreciation', digits=0, required=True)
|
||||
depreciated_value = fields.Float(string='Cumulative Depreciation', required=True)
|
||||
depreciation_date = fields.Date('Depreciation Date', index=True)
|
||||
move_id = fields.Many2one('account.move', string='Depreciation Entry')
|
||||
move_check = fields.Boolean(compute='_get_move_check', string='Linked', store=True)
|
||||
move_posted_check = fields.Boolean(compute='_get_move_posted_check', string='Posted', store=True)
|
||||
|
||||
|
||||
@api.depends('move_id')
|
||||
def _get_move_check(self):
|
||||
for line in self:
|
||||
line.move_check = bool(line.move_id)
|
||||
|
||||
|
||||
@api.depends('move_id.state')
|
||||
def _get_move_posted_check(self):
|
||||
for line in self:
|
||||
line.move_posted_check = True if line.move_id and line.move_id.state == 'posted' else False
|
||||
|
||||
|
||||
def create_move(self, post_move=True):
|
||||
created_moves = self.env['account.move']
|
||||
for line in self:
|
||||
if line.move_id:
|
||||
raise UserError(_('This depreciation is already linked to a journal entry. Please post or delete it.'))
|
||||
move_vals = self._prepare_move(line)
|
||||
move = self.env['account.move'].create(move_vals)
|
||||
line.write({'move_id': move.id, 'move_check': True})
|
||||
created_moves |= move
|
||||
|
||||
if post_move and created_moves:
|
||||
created_moves.filtered(lambda m: any(m.asset_depreciation_ids.mapped('asset_id.category_id.open_asset'))).post()
|
||||
return [x.id for x in created_moves]
|
||||
|
||||
def _prepare_move(self, line):
|
||||
category_id = line.asset_id.category_id
|
||||
account_analytic_id = line.asset_id.account_analytic_id
|
||||
analytic_tag_ids = line.asset_id.analytic_tag_ids
|
||||
depreciation_date = self.env.context.get('depreciation_date') or line.depreciation_date or fields.Date.context_today(self)
|
||||
company_currency = line.asset_id.company_id.currency_id
|
||||
current_currency = line.asset_id.currency_id
|
||||
prec = company_currency.decimal_places
|
||||
amount = current_currency._convert(
|
||||
line.amount, company_currency, line.asset_id.company_id, depreciation_date)
|
||||
asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, len(line.asset_id.depreciation_line_ids))
|
||||
move_line_1 = {
|
||||
'name': asset_name,
|
||||
'account_id': category_id.account_depreciation_id.id,
|
||||
'debit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount,
|
||||
'credit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0,
|
||||
'partner_id': line.asset_id.partner_id.id,
|
||||
'analytic_account_id': account_analytic_id.id if category_id.type == 'sale' else False,
|
||||
'analytic_tag_ids': [(6, 0, analytic_tag_ids.ids)] if category_id.type == 'sale' else False,
|
||||
'currency_id': company_currency != current_currency and current_currency.id or False,
|
||||
'amount_currency': company_currency != current_currency and - 1.0 * line.amount or 0.0,
|
||||
}
|
||||
move_line_2 = {
|
||||
'name': asset_name,
|
||||
'account_id': category_id.account_depreciation_expense_id.id,
|
||||
'credit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount,
|
||||
'debit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0,
|
||||
'partner_id': line.asset_id.partner_id.id,
|
||||
'analytic_account_id': account_analytic_id.id if category_id.type == 'purchase' else False,
|
||||
'analytic_tag_ids': [(6, 0, analytic_tag_ids.ids)] if category_id.type == 'purchase' else False,
|
||||
'currency_id': company_currency != current_currency and current_currency.id or False,
|
||||
'amount_currency': company_currency != current_currency and line.amount or 0.0,
|
||||
}
|
||||
move_vals = {
|
||||
'ref': line.asset_id.code,
|
||||
'date': depreciation_date or False,
|
||||
'journal_id': category_id.journal_id.id,
|
||||
'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)],
|
||||
}
|
||||
return move_vals
|
||||
|
||||
def _prepare_move_grouped(self):
|
||||
asset_id = self[0].asset_id
|
||||
category_id = asset_id.category_id # we can suppose that all lines have the same category
|
||||
account_analytic_id = asset_id.account_analytic_id
|
||||
analytic_tag_ids = asset_id.analytic_tag_ids
|
||||
depreciation_date = self.env.context.get('depreciation_date') or fields.Date.context_today(self)
|
||||
amount = 0.0
|
||||
for line in self:
|
||||
# Sum amount of all depreciation lines
|
||||
company_currency = line.asset_id.company_id.currency_id
|
||||
current_currency = line.asset_id.currency_id
|
||||
company = line.asset_id.company_id
|
||||
amount += current_currency._convert(line.amount, company_currency, company, fields.Date.today())
|
||||
|
||||
name = category_id.name + _(' (grouped)')
|
||||
move_line_1 = {
|
||||
'name': name,
|
||||
'account_id': category_id.account_depreciation_id.id,
|
||||
'debit': 0.0,
|
||||
'credit': amount,
|
||||
'journal_id': category_id.journal_id.id,
|
||||
'analytic_account_id': account_analytic_id.id if category_id.type == 'sale' else False,
|
||||
'analytic_tag_ids': [(6, 0, analytic_tag_ids.ids)] if category_id.type == 'sale' else False,
|
||||
}
|
||||
move_line_2 = {
|
||||
'name': name,
|
||||
'account_id': category_id.account_depreciation_expense_id.id,
|
||||
'credit': 0.0,
|
||||
'debit': amount,
|
||||
'journal_id': category_id.journal_id.id,
|
||||
'analytic_account_id': account_analytic_id.id if category_id.type == 'purchase' else False,
|
||||
'analytic_tag_ids': [(6, 0, analytic_tag_ids.ids)] if category_id.type == 'purchase' else False,
|
||||
}
|
||||
move_vals = {
|
||||
'ref': category_id.name,
|
||||
'date': depreciation_date or False,
|
||||
'journal_id': category_id.journal_id.id,
|
||||
'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)],
|
||||
}
|
||||
|
||||
return move_vals
|
||||
|
||||
|
||||
def create_grouped_move(self, post_move=True):
|
||||
if not self.exists():
|
||||
return []
|
||||
|
||||
created_moves = self.env['account.move']
|
||||
move = self.env['account.move'].create(self._prepare_move_grouped())
|
||||
self.write({'move_id': move.id, 'move_check': True})
|
||||
created_moves |= move
|
||||
|
||||
if post_move and created_moves:
|
||||
self.post_lines_and_close_asset()
|
||||
created_moves.post()
|
||||
return [x.id for x in created_moves]
|
||||
|
||||
|
||||
def post_lines_and_close_asset(self):
|
||||
# we re-evaluate the assets to determine whether we can close them
|
||||
for line in self:
|
||||
line.log_message_when_posted()
|
||||
asset = line.asset_id
|
||||
if asset.currency_id.is_zero(asset.value_residual):
|
||||
asset.message_post(body=_("Document closed."))
|
||||
asset.write({'state': 'close'})
|
||||
|
||||
|
||||
def log_message_when_posted(self):
|
||||
def _format_message(message_description, tracked_values):
|
||||
message = ''
|
||||
if message_description:
|
||||
message = '<span>%s</span>' % message_description
|
||||
for name, values in tracked_values.items():
|
||||
message += '<div> • <b>%s</b>: ' % name
|
||||
message += '%s</div>' % values
|
||||
return message
|
||||
|
||||
for line in self:
|
||||
if line.move_id and line.move_id.state == 'draft':
|
||||
partner_name = line.asset_id.partner_id.name
|
||||
currency_name = line.asset_id.currency_id.name
|
||||
msg_values = {_('Currency'): currency_name, _('Amount'): line.amount}
|
||||
if partner_name:
|
||||
msg_values[_('Partner')] = partner_name
|
||||
msg = _format_message(_('Depreciation line posted.'), msg_values)
|
||||
line.asset_id.message_post(body=msg)
|
||||
|
||||
|
||||
def unlink(self):
|
||||
for record in self:
|
||||
if record.move_check:
|
||||
if record.asset_id.category_id.type == 'purchase':
|
||||
msg = _("You cannot delete posted depreciation lines.")
|
||||
else:
|
||||
msg = _("You cannot delete posted installment lines.")
|
||||
raise UserError(msg)
|
||||
return super(AccountAssetDepreciationLine, self).unlink()
|
|
@ -0,0 +1,119 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AccountInvoice(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
@api.model
|
||||
def _refund_cleanup_lines(self, lines):
|
||||
result = super(AccountInvoice, self)._refund_cleanup_lines(lines)
|
||||
for i, line in enumerate(lines):
|
||||
for name, field in line._fields.items():
|
||||
if name == 'asset_category_id':
|
||||
result[i][2][name] = False
|
||||
break
|
||||
return result
|
||||
|
||||
def action_cancel(self):
|
||||
res = super(AccountInvoice, self).action_cancel()
|
||||
self.env['account.asset.asset'].sudo().search([('invoice_id', 'in', self.ids)]).write({'active': False})
|
||||
return res
|
||||
|
||||
def action_post(self):
|
||||
result = super(AccountInvoice, self).action_post()
|
||||
for inv in self:
|
||||
context = dict(self.env.context)
|
||||
context.pop('default_type', None)
|
||||
for mv_line in inv.invoice_line_ids:
|
||||
mv_line.with_context(context).asset_create()
|
||||
return result
|
||||
|
||||
|
||||
class AccountInvoiceLine(models.Model):
|
||||
_inherit = 'account.move.line'
|
||||
|
||||
asset_category_id = fields.Many2one('account.asset.category', string='Asset Category')
|
||||
asset_start_date = fields.Date(string='Asset Start Date', compute='_get_asset_date', readonly=True, store=True)
|
||||
asset_end_date = fields.Date(string='Asset End Date', compute='_get_asset_date', readonly=True, store=True)
|
||||
asset_mrr = fields.Float(string='Monthly Recurring Revenue', compute='_get_asset_date', readonly=True,
|
||||
digits="Account", store=True)
|
||||
|
||||
@api.depends('asset_category_id', 'move_id.invoice_date')
|
||||
def _get_asset_date(self):
|
||||
for rec in self:
|
||||
rec.asset_mrr = 0
|
||||
rec.asset_start_date = False
|
||||
rec.asset_end_date = False
|
||||
cat = rec.asset_category_id
|
||||
if cat:
|
||||
if cat.method_number == 0 or cat.method_period == 0:
|
||||
raise UserError(_('The number of depreciations or the period length of '
|
||||
'your asset category cannot be 0.'))
|
||||
months = cat.method_number * cat.method_period
|
||||
if rec.move_id.move_type in ['out_invoice', 'out_refund']:
|
||||
rec.asset_mrr = rec.price_subtotal / months
|
||||
if rec.move_id.invoice_date:
|
||||
start_date = rec.move_id.invoice_date.replace(day=1)
|
||||
end_date = (start_date + relativedelta(months=months, days=-1))
|
||||
rec.asset_start_date = start_date
|
||||
rec.asset_end_date = end_date
|
||||
|
||||
def asset_create(self):
|
||||
if self.asset_category_id:
|
||||
vals = {
|
||||
'name': self.name,
|
||||
'code': self.name or False,
|
||||
'category_id': self.asset_category_id.id,
|
||||
'value': self.price_subtotal,
|
||||
'partner_id': self.move_id.partner_id.id,
|
||||
'company_id': self.move_id.company_id.id,
|
||||
'currency_id': self.move_id.company_currency_id.id,
|
||||
'date': self.move_id.invoice_date,
|
||||
'invoice_id': self.move_id.id,
|
||||
}
|
||||
changed_vals = self.env['account.asset.asset'].onchange_category_id_values(vals['category_id'])
|
||||
vals.update(changed_vals['value'])
|
||||
asset = self.env['account.asset.asset'].create(vals)
|
||||
if self.asset_category_id.open_asset:
|
||||
asset.validate()
|
||||
return True
|
||||
|
||||
@api.onchange('asset_category_id')
|
||||
def onchange_asset_category_id(self):
|
||||
if self.move_id.move_type == 'out_invoice' and self.asset_category_id:
|
||||
self.account_id = self.asset_category_id.account_asset_id.id
|
||||
elif self.move_id.move_type == 'in_invoice' and self.asset_category_id:
|
||||
self.account_id = self.asset_category_id.account_asset_id.id
|
||||
|
||||
@api.onchange('uom_id')
|
||||
def _onchange_uom_id(self):
|
||||
result = super(AccountInvoiceLine, self)._onchange_uom_id()
|
||||
self.onchange_asset_category_id()
|
||||
return result
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_id(self):
|
||||
vals = super(AccountInvoiceLine, self)._onchange_product_id()
|
||||
if self.product_id:
|
||||
if self.move_id.move_type == 'out_invoice':
|
||||
self.asset_category_id = self.product_id.product_tmpl_id.deferred_revenue_category_id
|
||||
elif self.move_id.move_type == 'in_invoice':
|
||||
self.asset_category_id = self.product_id.product_tmpl_id.asset_category_id
|
||||
return vals
|
||||
|
||||
def _set_additional_fields(self, invoice):
|
||||
if not self.asset_category_id:
|
||||
if invoice.move_type == 'out_invoice':
|
||||
self.asset_category_id = self.product_id.product_tmpl_id.deferred_revenue_category_id.id
|
||||
elif invoice.move_type == 'in_invoice':
|
||||
self.asset_category_id = self.product_id.product_tmpl_id.asset_category_id.id
|
||||
self.onchange_asset_category_id()
|
||||
super(AccountInvoiceLine, self)._set_additional_fields(invoice)
|
||||
|
||||
def get_invoice_line_account(self, type, product, fpos, company):
|
||||
return product.asset_category_id.account_asset_id or super(AccountInvoiceLine, self).get_invoice_line_account(type, product, fpos, company)
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
|
||||
asset_category_id = fields.Many2one('account.asset.category', string='Asset Type', company_dependent=True, ondelete="restrict")
|
||||
deferred_revenue_category_id = fields.Many2one('account.asset.category', string='Deferred Revenue Type', company_dependent=True, ondelete="restrict")
|
||||
|
||||
|
||||
def _get_asset_accounts(self):
|
||||
res = super(ProductTemplate, self)._get_asset_accounts()
|
||||
if self.asset_category_id:
|
||||
res['stock_input'] = self.property_account_expense_id
|
||||
if self.deferred_revenue_category_id:
|
||||
res['stock_output'] = self.property_account_income_id
|
||||
return res
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import account_asset_report
|
|
@ -0,0 +1,68 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, tools
|
||||
|
||||
|
||||
class AssetAssetReport(models.Model):
|
||||
_name = "asset.asset.report"
|
||||
_description = "Assets Analysis"
|
||||
_auto = False
|
||||
|
||||
name = fields.Char(string='Year', required=False, readonly=True)
|
||||
date = fields.Date(readonly=True)
|
||||
depreciation_date = fields.Date(string='Depreciation Date', readonly=True)
|
||||
asset_id = fields.Many2one('account.asset.asset', string='Asset', readonly=True)
|
||||
asset_category_id = fields.Many2one('account.asset.category', string='Asset category', readonly=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Partner', readonly=True)
|
||||
state = fields.Selection([('draft', 'Draft'), ('open', 'Running'), ('close', 'Close')], string='Status', readonly=True)
|
||||
depreciation_value = fields.Float(string='Amount of Depreciation Lines', readonly=True)
|
||||
installment_value = fields.Float(string='Amount of Installment Lines', readonly=True)
|
||||
move_check = fields.Boolean(string='Posted', readonly=True)
|
||||
installment_nbr = fields.Integer(string='Installment Count', readonly=True)
|
||||
depreciation_nbr = fields.Integer(string='Depreciation Count', readonly=True)
|
||||
gross_value = fields.Float(string='Gross Amount', readonly=True)
|
||||
posted_value = fields.Float(string='Posted Amount', readonly=True)
|
||||
unposted_value = fields.Float(string='Unposted Amount', readonly=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', readonly=True)
|
||||
|
||||
def init(self):
|
||||
tools.drop_view_if_exists(self._cr, 'asset_asset_report')
|
||||
self._cr.execute("""
|
||||
create or replace view asset_asset_report as (
|
||||
select
|
||||
min(dl.id) as id,
|
||||
dl.name as name,
|
||||
dl.depreciation_date as depreciation_date,
|
||||
a.date as date,
|
||||
(CASE WHEN dlmin.id = min(dl.id)
|
||||
THEN a.value
|
||||
ELSE 0
|
||||
END) as gross_value,
|
||||
dl.amount as depreciation_value,
|
||||
dl.amount as installment_value,
|
||||
(CASE WHEN dl.move_check
|
||||
THEN dl.amount
|
||||
ELSE 0
|
||||
END) as posted_value,
|
||||
(CASE WHEN NOT dl.move_check
|
||||
THEN dl.amount
|
||||
ELSE 0
|
||||
END) as unposted_value,
|
||||
dl.asset_id as asset_id,
|
||||
dl.move_check as move_check,
|
||||
a.category_id as asset_category_id,
|
||||
a.partner_id as partner_id,
|
||||
a.state as state,
|
||||
count(dl.*) as installment_nbr,
|
||||
count(dl.*) as depreciation_nbr,
|
||||
a.company_id as company_id
|
||||
from account_asset_depreciation_line dl
|
||||
left join account_asset_asset a on (dl.asset_id=a.id)
|
||||
left join (select min(d.id) as id,ac.id as ac_id from account_asset_depreciation_line as d inner join account_asset_asset as ac ON (ac.id=d.asset_id) group by ac_id) as dlmin on dlmin.ac_id=a.id
|
||||
where a.active is true
|
||||
group by
|
||||
dl.amount,dl.asset_id,dl.depreciation_date,dl.name,
|
||||
a.date, dl.move_check, a.state, a.category_id, a.partner_id, a.company_id,
|
||||
a.value, a.id, a.salvage_value, dlmin.id
|
||||
)""")
|
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="action_account_asset_report_pivot">
|
||||
<field name="name">asset.asset.report.pivot</field>
|
||||
<field name="model">asset.asset.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Assets Analysis" disable_linking="True">
|
||||
<field name="asset_category_id" type="row"/>
|
||||
<field name="gross_value" type="measure"/>
|
||||
<field name="unposted_value" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="action_account_asset_report_graph">
|
||||
<field name="name">asset.asset.report.graph</field>
|
||||
<field name="model">asset.asset.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Assets Analysis">
|
||||
<field name="asset_category_id" type="row"/>
|
||||
<field name="gross_value" type="measure"/>
|
||||
<field name="unposted_value" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_asset_asset_report_search" model="ir.ui.view">
|
||||
<field name="name">asset.asset.report.search</field>
|
||||
<field name="model">asset.asset.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Assets Analysis">
|
||||
<field name="date"/>
|
||||
<field name="depreciation_date"/>
|
||||
<filter string="Draft" name="draft" domain="[('state','=','draft')]" help="Assets in draft state"/>
|
||||
<filter string="Running" name="running" domain="[('state','=','open')]" help="Assets in running state"/>
|
||||
<separator/>
|
||||
<filter string="Posted" name="posted" domain="[('move_check','=',True)]" help="Posted depreciation lines" context="{'unposted_value_visible': 0}"/>
|
||||
<field name="asset_id"/>
|
||||
<field name="asset_category_id"/>
|
||||
<group expand="0" string="Extended Filters...">
|
||||
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<group expand="1" string="Group By">
|
||||
<filter string="Asset" name="asset" context="{'group_by':'asset_id'}"/>
|
||||
<filter string="Asset Category" name="asset_category" context="{'group_by':'asset_category_id'}"/>
|
||||
<filter string="Company" name="company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
<separator/>
|
||||
<filter string="Purchase Month" name="purchase_month" help="Date of asset purchase"
|
||||
context="{'group_by':'date:month'}"/>
|
||||
<filter string="Depreciation Month" name="deprecation_month" help="Date of depreciation"
|
||||
context="{'group_by':'depreciation_date:month'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_asset_asset_report">
|
||||
<field name="name">Assets Analysis</field>
|
||||
<field name="res_model">asset.asset.report</field>
|
||||
<field name="view_mode">graph,pivot</field>
|
||||
<field name="search_view_id" ref="view_asset_asset_report_search"/>
|
||||
<field name="domain">[('asset_category_id.type', '=', 'purchase')]</field>
|
||||
<field name="context">{}</field> <!-- force empty -->
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
No content
|
||||
</p><p>
|
||||
From this report, you can have an overview on all depreciations. The
|
||||
search bar can also be used to personalize your assets depreciation reporting.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Assets"
|
||||
action="action_asset_asset_report"
|
||||
id="menu_action_asset_asset_report"
|
||||
parent="account.account_reports_management_menu"
|
||||
sequence="21"/>
|
||||
</odoo>
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="account_asset_category_multi_company_rule" model="ir.rule">
|
||||
<field name="name">Account Asset Category multi-company</field>
|
||||
<field ref="model_account_asset_category" name="model_id"/>
|
||||
<field eval="True" name="global"/>
|
||||
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
|
||||
</record>
|
||||
|
||||
<record id="account_asset_asset_multi_company_rule" model="ir.rule">
|
||||
<field name="name">Account Asset multi-company</field>
|
||||
<field ref="model_account_asset_asset" name="model_id"/>
|
||||
<field eval="True" name="global"/>
|
||||
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
|
||||
</record>
|
||||
|
||||
<record id="account.group_account_user" model="res.groups">
|
||||
<field name="name">Accountant</field>
|
||||
<field name="implied_ids" eval="[(4, ref('account.group_account_invoice'))]"/>
|
||||
<field name="category_id" ref="base.module_category_accounting_accounting"/>
|
||||
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
|
@ -0,0 +1,14 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_account_asset_category,account.asset.category,model_account_asset_category,account.group_account_user,1,0,0,0
|
||||
access_asset_depreciation_confirmation_wizard,access_asset_depreciation_confirmation_wizard,model_asset_depreciation_confirmation_wizard,,1,1,1,0
|
||||
access_asset_modify,access_asset_modify,model_asset_modify,,1,1,1,0
|
||||
access_account_asset_asset,account.asset.asset,model_account_asset_asset,account.group_account_user,1,0,0,0
|
||||
access_account_asset_category_manager,account.asset.category,model_account_asset_category,account.group_account_manager,1,1,1,1
|
||||
access_account_asset_asset_manager,account.asset.asset,model_account_asset_asset,account.group_account_manager,1,1,1,1
|
||||
access_account_asset_depreciation_line,account.asset.depreciation.line,model_account_asset_depreciation_line,account.group_account_user,1,0,0,0
|
||||
access_account_asset_depreciation_line_manager,account.asset.depreciation.line,model_account_asset_depreciation_line,account.group_account_manager,1,1,1,1
|
||||
access_asset_asset_report,asset.asset.report,model_asset_asset_report,account.group_account_user,1,0,0,0
|
||||
access_asset_asset_report_manager,asset.asset.report,model_asset_asset_report,account.group_account_manager,1,1,1,1
|
||||
access_account_asset_category_invoicing_payment,account.asset.category,model_account_asset_category,account.group_account_invoice,1,0,0,0
|
||||
access_account_asset_asset_invoicing_payment,account.asset.asset,model_account_asset_asset,account.group_account_invoice,1,0,1,0
|
||||
access_account_asset_depreciation_line_invoicing_payment,account.asset.depreciation.line,model_account_asset_depreciation_line,account.group_account_invoice,1,0,1,0
|
|
Binary file not shown.
After Width: | Height: | Size: 126 KiB |
Binary file not shown.
After Width: | Height: | Size: 1023 KiB |
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70"><defs><path id="a" d="M4 0h61c4 0 5 1 5 5v60c0 4-1 5-5 5H4c-3 0-4-1-4-5V5c0-4 1-5 4-5z"/><linearGradient id="c" x1="100%" x2="0%" y1="0%" y2="100%"><stop offset="0%" stop-color="#DA956B"/><stop offset="100%" stop-color="#CC7039"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><path fill="url(#c)" d="M0 0H70V70H0z"/><path fill="#FFF" fill-opacity=".383" d="M4 1h61c2.667 0 4.333.667 5 2V0H0v3c.667-1.333 2-2 4-2z"/><path fill="#393939" d="M4 69c-2 0-4-.146-4-4.09V40.738L18.16 24H52l1 2.045v6.137l-10.585 11.3 10.05 4.09L37.071 69H4z" opacity=".324"/><path fill="#000" fill-opacity=".383" d="M4 69h61c2.667 0 4.333-1 5-3v4H0v-4c.667 2 2 3 4 3z"/><path fill="#000" d="M53 42.084v5.66c0 1.837-1.111 3.34-3.556 3.34h-28c-2.466 0-4.444-1.503-4.444-3.34V28.34c0-1.837 1.978-3.34 4.444-3.34H49c2.444 0 4 1.368 4 3.205v5.88H37c-2.667 0-4 1.857-4 3.957 0 2.1 1.333 4.042 4 4.042h16zm-15-1.39a2.656 2.656 0 0 1-2.667-2.652A2.656 2.656 0 0 1 38 35.39a2.656 2.656 0 0 1 2.667 2.653A2.656 2.656 0 0 1 38 40.695z" opacity=".3"/><path fill="#FFF" d="M53 40.084v5.66c0 1.837-1.111 3.34-3.556 3.34h-28c-2.466 0-4.444-1.503-4.444-3.34V26.34c0-1.837 1.978-3.34 4.444-3.34H49c2.444 0 4 1.368 4 3.205v5.88H37c-2.667 0-4 1.857-4 3.957 0 2.1 1.333 4.042 4 4.042h16zm-15-1.39a2.656 2.656 0 0 1-2.667-2.652A2.656 2.656 0 0 1 38 33.39a2.656 2.656 0 0 1 2.667 2.653A2.656 2.656 0 0 1 38 38.695z"/></g></g></svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,84 @@
|
|||
<section class="oe_container oe_dark">
|
||||
<div class="col-md-12">
|
||||
<h2 class="oe_slogan" style="font-size: 35px;color:#2C0091"><b>Odoo 14 Asset Management</b></h2>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<div style="text-align:center;">
|
||||
<p class="fa fa-hand-o-right" style="color:CRIMSON;font-size: 25px;">
|
||||
<span style="color:#2dd280;font-size: 15px;">Manage assets owned by a company or a person.</span>
|
||||
</p><br/>
|
||||
<p class="fa fa-hand-o-right" style="color:CRIMSON;font-size: 25px;">
|
||||
<span style="color:#2dd280;font-size: 15px;">Keeps track of depreciation's, and creates corresponding journal entries</span>
|
||||
</p><br/>
|
||||
|
||||
</div>
|
||||
<br/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<div class="oe_centeralign oe_websiteonly">
|
||||
<h4 class="oe_slogan"><a href="https://www.youtube.com/watch?v=KudvDOTvx2I" target="_blank" style="color: #FFFFFF !important; border-radius: 0; background-color: #9c676e; border-color: #005ca7; padding: 15px; font-weight: bold;">
|
||||
<i class="fa fa-youtube"></i>
|
||||
Watch on YouTube
|
||||
</a></h4>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h3 class="oe_slogan" style="color:#332c3c;font-size: 28px;">Asset Types</h3>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="asset_types.png">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h3 class="oe_slogan" style="color:#332c3c;font-size: 28px;">Assets</h3>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="assets.png">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<hr style="width: 100%;height: 4px;background: #2C0091;margin: 0px 0px;">
|
||||
<hr style="width: 100%;height: 4px;background: #148963;margin: 0px 0px;">
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row ">
|
||||
<div class="oe_slogan text-center">
|
||||
<img src="odoo_mates.png"/>
|
||||
<div style="color:#269900;">
|
||||
<h3 style="color:#2C0091;font-size: 25px;">If you need any help or want more features, just contact us:</h3><br>
|
||||
<h3 style="color:#2C0091;font-size: 20px;">Email: <a href="odoomates@gmail.com">odoomates@gmail.com</a> <br></h3>
|
||||
</div>
|
||||
<div class="oe_slogan">
|
||||
<h2>
|
||||
<a target="_blank" href="https://www.facebook.com/odoomate/" target="new">
|
||||
<i class="fa fa-facebook-square" style="font-size:38px;"></i>
|
||||
</a>
|
||||
<a target="_blank" href="https://twitter.com/odoomates/" target="new">
|
||||
<i class="fa fa-twitter" style="font-size:38px;"></i>
|
||||
</a>
|
||||
<a href="#" target="_blank">
|
||||
<i class="fa fa-linkedin" style="font-size:38px;"></i>
|
||||
</a>
|
||||
<a target="_blank" href="https://www.youtube.com/channel/UCVKlUZP7HAhdQgs-9iTJklQ">
|
||||
<i class="fa fa-youtube-play" style="font-size:38px;"></i>
|
||||
</a>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<hr style="width: 100%;height: 4px;background: #148963;margin: 0px 0px;">
|
||||
<hr style="width: 100%;height: 4px;background: #2C0091;margin: 0px 0px;">
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
|
@ -0,0 +1,87 @@
|
|||
odoo.define('om_account_asset.widget', function(require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* The purpose of this widget is to shows a toggle button on depreciation and
|
||||
* installment lines for posted/unposted line. When clicked, it calls the method
|
||||
* create_move on the object account.asset.depreciation.line.
|
||||
* Note that this widget can only work on the account.asset.depreciation.line
|
||||
* model as some of its fields are harcoded.
|
||||
*/
|
||||
|
||||
var AbstractField = require('web.AbstractField');
|
||||
var core = require('web.core');
|
||||
var registry = require('web.field_registry');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
var AccountAssetWidget = AbstractField.extend({
|
||||
events: _.extend({}, AbstractField.prototype.events, {
|
||||
'click': '_onClick',
|
||||
}),
|
||||
description: "",
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
isSet: function () {
|
||||
return true; // it should always be displayed, whatever its value
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_render: function () {
|
||||
var className = '';
|
||||
var disabled = true;
|
||||
var title;
|
||||
if (this.recordData.move_posted_check) {
|
||||
className = 'o_is_posted';
|
||||
title = _t('Posted');
|
||||
} else if (this.recordData.move_check) {
|
||||
className = 'o_unposted';
|
||||
title = _t('Accounting entries waiting for manual verification');
|
||||
} else {
|
||||
disabled = false;
|
||||
title = _t('Unposted');
|
||||
}
|
||||
var $button = $('<button/>', {
|
||||
type: 'button',
|
||||
title: title,
|
||||
disabled: disabled,
|
||||
}).addClass('btn btn-link fa fa-circle o_deprec_lines_toggler ' + className);
|
||||
this.$el.html($button);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
_onClick: function (event) {
|
||||
event.stopPropagation();
|
||||
this.trigger_up('button_clicked', {
|
||||
attrs: {
|
||||
name: 'create_move',
|
||||
type: 'object',
|
||||
},
|
||||
record: this.record,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
registry.add("deprec_lines_toggler", AccountAssetWidget);
|
||||
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
.o_web_client .o_deprec_lines_toggler {
|
||||
color: theme-color('danger');
|
||||
&.o_is_posted {
|
||||
color: theme-color('success');
|
||||
}
|
||||
&.o_unposted {
|
||||
color: theme-color('warning');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
odoo.define('om_account_asset.widget_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var FormView = require('web.FormView');
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
var createView = testUtils.createView;
|
||||
|
||||
QUnit.module('fields', {}, function () {
|
||||
|
||||
QUnit.module('om_account_asset', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
asset: {
|
||||
fields: {
|
||||
display_name: { string: "Displayed name", type: "char" },
|
||||
line_ids: {
|
||||
string: "Lines",
|
||||
type: "one2many",
|
||||
relation: 'line',
|
||||
relation_field: 'asset_id',
|
||||
},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
display_name: "asset name",
|
||||
line_ids: [1, 2, 3, 4],
|
||||
}],
|
||||
},
|
||||
line: {
|
||||
fields: {
|
||||
move_check: {string: "Move Check", type: 'boolean'},
|
||||
move_posted_check: {string: "Move Posted Check", type: 'boolean'},
|
||||
asset_id: {string: "Asset", type: 'many2one', relation: 'asset'},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
move_check: true,
|
||||
move_posted_check: true,
|
||||
}, {
|
||||
id: 2,
|
||||
move_check: false,
|
||||
move_posted_check: true,
|
||||
}, {
|
||||
id: 3,
|
||||
move_check: true,
|
||||
move_posted_check: false,
|
||||
}, {
|
||||
id: 4,
|
||||
move_check: false,
|
||||
move_posted_check: false,
|
||||
}],
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('basic rendering', function (assert) {
|
||||
assert.expect(18);
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'asset',
|
||||
data: this.data,
|
||||
arch: '<form string="Asset">' +
|
||||
'<sheet>' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="line_ids">' +
|
||||
'<tree>' +
|
||||
'<field name="move_check" widget="deprec_lines_toggler"/>' +
|
||||
'<field name="move_posted_check" invisible="1"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
// check the header
|
||||
assert.strictEqual(form.$('thead th').text(), "", "toggler column should have no title");
|
||||
|
||||
// check the classnames
|
||||
assert.ok(form.$('.o_deprec_lines_toggler_cell:nth(0) button').hasClass('o_is_posted'),
|
||||
"first line toggler should have classname 'o_is_posted'");
|
||||
assert.ok(!form.$('.o_deprec_lines_toggler_cell:nth(0) button').hasClass('o_unposted'),
|
||||
"first line toggler should not have classname 'o_unposted'");
|
||||
|
||||
assert.ok(form.$('.o_deprec_lines_toggler_cell:nth(1) button').hasClass('o_is_posted'),
|
||||
"second line toggler should have classname 'o_is_posted'");
|
||||
assert.ok(!form.$('.o_deprec_lines_toggler_cell:nth(1) button').hasClass('o_unposted'),
|
||||
"second line toggler should not have classname 'o_unposted'");
|
||||
|
||||
assert.ok(!form.$('.o_deprec_lines_toggler_cell:nth(2) button').hasClass('o_is_posted'),
|
||||
"third line toggler should not have classname 'o_is_posted'");
|
||||
assert.ok(form.$('.o_deprec_lines_toggler_cell:nth(2) button').hasClass('o_unposted'),
|
||||
"third line toggler should have classname 'o_unposted'");
|
||||
|
||||
assert.ok(!form.$('.o_deprec_lines_toggler_cell:nth(3) button').hasClass('o_is_posted'),
|
||||
"fourth line toggler should not have classname 'o_is_posted'");
|
||||
assert.ok(!form.$('.o_deprec_lines_toggler_cell:nth(3) button').hasClass('o_unposted'),
|
||||
"fourth line toggler should not have classname 'o_unposted'");
|
||||
|
||||
// check the titles
|
||||
assert.strictEqual(form.$('.o_deprec_lines_toggler_cell:nth(0) button').attr('title'),
|
||||
'Posted', "first line toggler should have correct title");
|
||||
assert.strictEqual(form.$('.o_deprec_lines_toggler_cell:nth(1) button').attr('title'),
|
||||
'Posted', "second line toggler should have correct title");
|
||||
assert.strictEqual(form.$('.o_deprec_lines_toggler_cell:nth(2) button').attr('title'),
|
||||
'Accounting entries waiting for manual verification',
|
||||
"third line toggler should have correct title");
|
||||
assert.strictEqual(form.$('.o_deprec_lines_toggler_cell:nth(3) button').attr('title'),
|
||||
'Unposted', "fourth line toggler should have correct title");
|
||||
|
||||
// check disabled property
|
||||
assert.ok(form.$('.o_deprec_lines_toggler_cell:nth(0) button').attr('disabled'),
|
||||
"first line toggle should be disabled");
|
||||
assert.ok(form.$('.o_deprec_lines_toggler_cell:nth(1) button').attr('disabled'),
|
||||
"second line toggle should be disabled");
|
||||
assert.ok(form.$('.o_deprec_lines_toggler_cell:nth(2) button').attr('disabled'),
|
||||
"third line toggle should be disabled");
|
||||
assert.ok(!form.$('.o_deprec_lines_toggler_cell:nth(3) button').attr('disabled'),
|
||||
"fourth line toggle should not be disabled");
|
||||
|
||||
// check the visibility: the widget should always be visible, regardless its value
|
||||
assert.strictEqual(form.$('.o_deprec_lines_toggler:visible').length, 4,
|
||||
"all togglers should be visible");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('click events are correctly triggered', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'asset',
|
||||
data: this.data,
|
||||
arch: '<form string="Asset">' +
|
||||
'<sheet>' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="line_ids">' +
|
||||
'<tree>' +
|
||||
'<field name="move_check" widget="deprec_lines_toggler"/>' +
|
||||
'<field name="move_posted_check" invisible="1"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
intercepts: {
|
||||
execute_action: function (event) {
|
||||
var data = event.data;
|
||||
assert.strictEqual(data.env.model, 'line', "should have correct model");
|
||||
assert.strictEqual(data.action_data.name, 'create_move',
|
||||
"should call correct method");
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
// click on last row toggler
|
||||
form.$('.o_deprec_lines_toggler_cell:nth(3) button').click();
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="account_asset_category_fixedassets_test0" model="account.asset.category">
|
||||
<field name="account_depreciation_id" ref="xfa"/>
|
||||
<field name="account_depreciation_expense_id" ref="a_expense"/>
|
||||
<field name="account_asset_id" ref="xfa"/>
|
||||
<field name="journal_id" ref="expenses_journal"/>
|
||||
<field name="name">Hardware - 3 Years</field>
|
||||
<field name="method_number">3</field>
|
||||
<field name="method_period">12</field>
|
||||
<field name="open_asset">True</field>
|
||||
</record>
|
||||
|
||||
<record id="account_asset_category_fixedassets_test1" model="account.asset.category">
|
||||
<field name="account_depreciation_id" ref="xfa"/>
|
||||
<field name="account_depreciation_expense_id" ref="a_expense"/>
|
||||
<field name="account_asset_id" ref="xfa"/>
|
||||
<field name="journal_id" ref="expenses_journal"/>
|
||||
<field name="name">Cars - 5 Years</field>
|
||||
<field name="method_number">5</field>
|
||||
<field name="method_period">12</field>
|
||||
</record>
|
||||
|
||||
<record id="account_asset_asset_vehicles_test0" model="account.asset.asset">
|
||||
<field eval="2000.0" name="salvage_value"/>
|
||||
<field name="state">open</field>
|
||||
<field eval="12" name="method_period"/>
|
||||
<field eval="5" name="method_number"/>
|
||||
<field name="name">CEO's Car</field>
|
||||
<field eval="12000.0" name="value"/>
|
||||
<field name="category_id" ref="account_asset_category_fixedassets_test0"/>
|
||||
</record>
|
||||
|
||||
<record id="account_asset_asset_cab_test0" model="account.asset.asset">
|
||||
<field name="method_end" eval="(DateTime.now().replace(month=8, day=11) + timedelta(days=3*365)).strftime('%Y-%m-%d')"/>
|
||||
<field eval="0.0" name="salvage_value"/>
|
||||
<field name="method_time">end</field>
|
||||
<field name="name">V6 Engine and 10 inches tires</field>
|
||||
<field eval="2800.0" name="value"/>
|
||||
<field name="category_id" ref="account_asset_category_fixedassets_test0"/>
|
||||
</record>
|
||||
|
||||
<record id="account_asset_asset_office_test0" model="account.asset.asset">
|
||||
<field eval="1" name="prorata"/>
|
||||
<field eval="100000.0" name="salvage_value"/>
|
||||
<field name="state">open</field>
|
||||
<field eval="12" name="method_period"/>
|
||||
<field eval="3" name="method_number"/>
|
||||
<field name="date" eval="time.strftime('%Y-01-01')"/>
|
||||
<field name="name">Office</field>
|
||||
<field eval="500000.0" name="value"/>
|
||||
<field name="category_id" ref="account_asset_category_fixedassets_test0"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_account_asset
|
|
@ -0,0 +1,66 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import tools
|
||||
from odoo.tests import common
|
||||
from odoo.modules.module import get_resource_path
|
||||
|
||||
|
||||
class TestAccountAsset(common.TransactionCase):
|
||||
|
||||
def _load(self, module, *args):
|
||||
tools.convert_file(self.cr, 'account_asset',
|
||||
get_resource_path(module, *args),
|
||||
{}, 'init', False, 'test', self.registry._assertion_report)
|
||||
|
||||
def test_00_account_asset_asset(self):
|
||||
self._load('account', 'test', 'account_minimal_test.xml')
|
||||
self._load('account_asset', 'test', 'account_asset_demo_test.xml')
|
||||
|
||||
# In order to test the process of Account Asset, I perform a action to confirm Account Asset.
|
||||
self.browse_ref("account_asset.account_asset_asset_vehicles_test0").validate()
|
||||
|
||||
# I check Asset is now in Open state.
|
||||
self.assertEqual(self.browse_ref("account_asset.account_asset_asset_vehicles_test0").state, 'open',
|
||||
'Asset should be in Open state')
|
||||
|
||||
# I compute depreciation lines for asset of CEOs Car.
|
||||
self.browse_ref("account_asset.account_asset_asset_vehicles_test0").compute_depreciation_board()
|
||||
value = self.browse_ref("account_asset.account_asset_asset_vehicles_test0")
|
||||
self.assertEqual(value.method_number, len(value.depreciation_line_ids),
|
||||
'Depreciation lines not created correctly')
|
||||
|
||||
# I create account move for all depreciation lines.
|
||||
ids = self.env['account.asset.depreciation.line'].search([('asset_id', '=', self.ref('account_asset.account_asset_asset_vehicles_test0'))])
|
||||
for line in ids:
|
||||
line.create_move()
|
||||
|
||||
# I check the move line is created.
|
||||
asset = self.env['account.asset.asset'].browse([self.ref("account_asset.account_asset_asset_vehicles_test0")])[0]
|
||||
self.assertEqual(len(asset.depreciation_line_ids), asset.entry_count,
|
||||
'Move lines not created correctly')
|
||||
|
||||
# I Check that After creating all the moves of depreciation lines the state "Close".
|
||||
self.assertEqual(self.browse_ref("account_asset.account_asset_asset_vehicles_test0").state, 'close',
|
||||
'State of asset should be close')
|
||||
|
||||
# WIZARD
|
||||
# I create a record to change the duration of asset for calculating depreciation.
|
||||
account_asset_asset_office0 = self.browse_ref('account_asset.account_asset_asset_office_test0')
|
||||
asset_modify_number_0 = self.env['asset.modify'].create({
|
||||
'name': 'Test reason',
|
||||
'method_number': 10.0,
|
||||
}).with_context({'active_id': account_asset_asset_office0.id})
|
||||
# I change the duration.
|
||||
asset_modify_number_0.with_context({'active_id': account_asset_asset_office0.id}).modify()
|
||||
|
||||
# I check the proper depreciation lines created.
|
||||
self.assertEqual(account_asset_asset_office0.method_number, len(account_asset_asset_office0.depreciation_line_ids))
|
||||
# I compute a asset on period.
|
||||
context = {
|
||||
"active_ids": [self.ref("account_asset.menu_asset_depreciation_confirmation_wizard")],
|
||||
"active_id": self.ref('account_asset.menu_asset_depreciation_confirmation_wizard'),
|
||||
'type': 'sale'
|
||||
}
|
||||
asset_compute_period_0 = self.env['asset.depreciation.confirmation.wizard'].create({})
|
||||
asset_compute_period_0.with_context(context).asset_compute()
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="assets_backend" name="account assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" type="text/scss" href="/om_account_asset/static/src/scss/account_asset.scss"/>
|
||||
<script type="text/javascript" src="/om_account_asset/static/src/js/account_asset.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="qunit_suite" name="account_asset_tests" inherit_id="web.qunit_suite">
|
||||
<xpath expr="//t[@t-set='head']" position="inside">
|
||||
<script type="text/javascript" src="/om_account_asset/static/tests/account_asset_tests.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,345 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_account_asset_category_form">
|
||||
<field name="name">account.asset.category.form</field>
|
||||
<field name="model">account.asset.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Asset category">
|
||||
<sheet>
|
||||
<group>
|
||||
<div class="oe_title">
|
||||
<label for="name" string="Asset Type" class="oe_edit_only" attrs="{'invisible': [('type','!=','purchase')]}"/>
|
||||
<label for="name" string="Deferred Revenue Type" class="oe_edit_only" attrs="{'invisible': [('type','==','purchase')]}"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="e.g. Computers"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<field name="type" attrs="{'invisible': 1}"/>
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<group string="Journal Entries">
|
||||
<field name="journal_id"/>
|
||||
<div>
|
||||
<label for="account_asset_id"
|
||||
attrs="{'invisible': [('type','!=','purchase')]}"
|
||||
style="font-weight: bold" class="o_light_label"/>
|
||||
<label for="account_asset_id" string="Deferred Revenue Account"
|
||||
attrs="{'invisible': [('type','!=','sale')]}"
|
||||
style="font-weight: bold" class="o_light_label"/>
|
||||
</div>
|
||||
<field name="account_asset_id" nolabel="1" attrs="{'invisible': [('type','=', False)]}"/>
|
||||
<div>
|
||||
<label for="account_depreciation_id"
|
||||
attrs="{'invisible': [('type','!=','purchase')]}"
|
||||
style="font-weight: bold" class="o_light_label"/>
|
||||
<label for="account_depreciation_id" string="Recognition Income Account"
|
||||
attrs="{'invisible': [('type','!=','sale')]}"
|
||||
style="font-weight: bold" class="o_light_label"/>
|
||||
</div>
|
||||
<field name="account_depreciation_id" nolabel="1"/>
|
||||
<div>
|
||||
<label for="account_depreciation_expense_id"
|
||||
attrs="{'invisible': [('type','!=','purchase')]}"
|
||||
style="font-weight: bold" class="o_light_label"/>
|
||||
<label for="account_depreciation_expense_id" string="Recognition Account"
|
||||
attrs="{'invisible': [('type','!=','sale')]}"
|
||||
style="font-weight: bold" class="o_light_label"/>
|
||||
</div>
|
||||
<field name="account_depreciation_expense_id" nolabel="1"/>
|
||||
<field name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
|
||||
<field name="analytic_tag_ids" groups="analytic.group_analytic_accounting" widget="many2many_tags"/>
|
||||
</group>
|
||||
<group string="Periodicity">
|
||||
<field name="method_time" string="Time Method Based On" widget="radio" attrs="{'invisible': [('type','!=','purchase')]}"/>
|
||||
<field name="method_number" string="Number of Entries"
|
||||
attrs="{'invisible':['|',('method_time','!=','number'), ('type','=', False)],
|
||||
'required':[('method_time','=','number')]}"/>
|
||||
<label for="method_period" string="One Entry Every"/>
|
||||
<div>
|
||||
<field name="method_period" nolabel="1" attrs="{'invisible': [('type','=', False)]}" class="oe_inline"/>
|
||||
months
|
||||
</div>
|
||||
<field name="method_end" attrs="{'required': [('method_time','=','end')], 'invisible':[('method_time','!=','end')]}"/>
|
||||
</group>
|
||||
<group string="Additional Options">
|
||||
<field name="open_asset"/>
|
||||
<field name="group_entries"/>
|
||||
<field name="date_first_depreciation"/>
|
||||
</group>
|
||||
<group attrs="{'invisible': [('type','=','sale')]}" string="Depreciation Method">
|
||||
<field name="method" widget="radio"/>
|
||||
<field name="method_progress_factor" attrs="{'invisible':[('method','=','linear')], 'required':[('method','=','degressive')]}"/>
|
||||
<field name="prorata" attrs="{'invisible': [('method_time','=','end')]}"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_asset_asset_category_kanban" model="ir.ui.view">
|
||||
<field name="name">account.asset.category.kanban</field>
|
||||
<field name="model">account.asset.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_mobile">
|
||||
<field name="name"/>
|
||||
<field name="journal_id"/>
|
||||
<field name="method"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
|
||||
<div class="row mb4">
|
||||
<div class="col-6">
|
||||
<strong><span><t t-esc="record.name.value"/></span></strong>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<span class="badge badge-pill"><strong><t t-esc="record.method.value"/></strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<div> <t t-esc="record.journal_id.value"/></div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_account_asset_category_tree">
|
||||
<field name="name">account.asset.category.tree</field>
|
||||
<field name="model">account.asset.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Asset category">
|
||||
<field name="name"/>
|
||||
<field name="journal_id"/>
|
||||
<field name="method"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_account_asset_category_search">
|
||||
<field name="name">account.asset.category.search</field>
|
||||
<field name="model">account.asset.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Asset Category">
|
||||
<filter string="Sales" name="sales" domain="[('type','=', 'sale')]" help="Deferred Revenues"/>
|
||||
<filter string="Purchase" name="purchase" domain="[('type','=', 'purchase')]" help="Assets"/>
|
||||
<field name="name" string="Category"/>
|
||||
<field name="journal_id"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Type" name="type" domain="[]" context="{'group_by':'type'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_account_asset_asset_form">
|
||||
<field name="name">account.asset.asset.form</field>
|
||||
<field name="model">account.asset.asset</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Asset">
|
||||
<header>
|
||||
<button name="validate" states="draft" string="Confirm" type="object" class="oe_highlight"/>
|
||||
<button type="object" name="compute_depreciation_board" string="Compute Depreciation" states="draft"/>
|
||||
<button name="set_to_close" states="open" string="Sell or Dispose" type="object" class="oe_highlight"/>
|
||||
<button name="set_to_draft" string="Set to Draft" type="object" attrs="{'invisible': ['|', ('entry_count', '!=', 0), ('state', '!=', 'open')]}"/>
|
||||
<button name="%(action_asset_modify)d" states="open" string="Modify Depreciation" type="action"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,open"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" name="open_entries" type="object" icon="fa-pencil">
|
||||
<field string="Items" name="entry_count" widget="statinfo" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="e.g. Laptop iBook"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="category_id" string="Asset Category" domain="[('type', '=', 'purchase')]" context="{'default_type': 'purchase'}" help="Category of asset"/>
|
||||
<field name="code"/>
|
||||
<field name="date" help="Date of asset"/>
|
||||
<field name="date_first_depreciation"/>
|
||||
<field name="first_depreciation_manual_date"
|
||||
attrs="{'invisible': [('date_first_depreciation', '!=', 'manual')], 'required': [('date_first_depreciation', '=', 'manual')]}"/>
|
||||
<field name="type" invisible="1"/>
|
||||
<field name="account_analytic_id" groups="analytic.group_analytic_accounting" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="currency_id" groups="base.group_multi_currency"/>
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
<field name="value" widget="monetary" options="{'currency_field': 'currency_id'}" help="Gross value of asset"/>
|
||||
<field name="salvage_value" widget="monetary" options="{'currency_field': 'currency_id'}" attrs="{'invisible': [('type','=','sale')]}"/>
|
||||
<field name="value_residual" widget="monetary" options="{'currency_field': 'currency_id'}"/>
|
||||
<field name="partner_id" string="Vendor" widget="res_partner_many2one"
|
||||
context="{'res_partner_search_mode': 'supplier'}"/>
|
||||
<field name="invoice_id" string="Invoice" options="{'no_create': True}"/>
|
||||
<field name="analytic_tag_ids" groups="analytic.group_analytic_accounting" widget="many2many_tags"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="Depreciation Board">
|
||||
<field name="depreciation_line_ids" mode="tree" options="{'reload_whole_on_button': true}">
|
||||
<tree string="Depreciation Lines" decoration-info="(move_check == False)" create="false">
|
||||
<field name="depreciation_date"/>
|
||||
<field name="amount" widget="monetary" string="Depreciation"/>
|
||||
<field name="depreciated_value" readonly="1"/>
|
||||
<field name="remaining_value" readonly="1" widget="monetary" string="Residual"/>
|
||||
<field name="move_check" widget="deprec_lines_toggler" attrs="{'invisible': [('parent_state', '!=', 'open')]}"/>
|
||||
<field name="move_posted_check" invisible="1"/>
|
||||
<field name="parent_state" invisible="1"/>
|
||||
</tree>
|
||||
<form string="Depreciation Lines" create="false">
|
||||
<group>
|
||||
<group>
|
||||
<field name="parent_state" invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="sequence"/>
|
||||
<field name="move_id"/>
|
||||
<field name="move_check"/>
|
||||
<field name="parent_state" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="amount" widget="monetary"/>
|
||||
<field name="depreciation_date"/>
|
||||
<field name="depreciated_value"/>
|
||||
<field name="remaining_value"/>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Depreciation Information">
|
||||
<group>
|
||||
<field name="method" widget="radio" attrs="{'invisible': [('type','=','sale')]}"/>
|
||||
<field name="method_progress_factor" attrs="{'invisible':[('method','=','linear')], 'required':[('method','=','degressive')]}"/>
|
||||
<field name="method_time" string="Time Method Based On" widget="radio" attrs="{'invisible': [('type','!=','purchase')]}"/>
|
||||
<field name="prorata" attrs="{'invisible': [('method_time','=','end')]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="method_number" attrs="{'invisible':[('method_time','=','end')], 'required':[('method_time','=','number')]}"/>
|
||||
<field name="method_period"/>
|
||||
<field name="method_end" attrs="{'required': [('method_time','=','end')], 'invisible':[('method_time','=','number')]}"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_asset_asset_kanban" model="ir.ui.view">
|
||||
<field name="name">account.asset.asset.kanban</field>
|
||||
<field name="model">account.asset.asset</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_mobile">
|
||||
<field name="name"/>
|
||||
<field name="category_id"/>
|
||||
<field name="date"/>
|
||||
<field name="state"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_global_click">
|
||||
<div class="row mb4">
|
||||
<div class="col-6">
|
||||
<strong><span><t t-esc="record.name.value"/></span></strong>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<strong><t t-esc="record.date.value"/></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 text-muted">
|
||||
<span><t t-esc="record.category_id.value"/></span>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<span class="float-right text-right">
|
||||
<field name="state" widget="kanban_label_selection" options="{'classes': {'draft': 'primary', 'open': 'success', 'close': 'default'}}"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_account_asset_asset_purchase_tree">
|
||||
<field name="name">account.asset.asset.purchase.tree</field>
|
||||
<field name="model">account.asset.asset</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Assets" decoration-info="(state == 'draft')" decoration-muted="(state == 'close')">
|
||||
<field name="name"/>
|
||||
<field name="category_id" string="Asset Category"/>
|
||||
<field name="date"/>
|
||||
<field name="partner_id" string="Vendor"/>
|
||||
<field name="value"/>
|
||||
<field name="value_residual" widget="monetary"/>
|
||||
<field name="currency_id" groups="base.group_multi_currency"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_asset_search" model="ir.ui.view">
|
||||
<field name="name">account.asset.asset.search</field>
|
||||
<field name="model">account.asset.asset</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Asset Account">
|
||||
<field name="name" string="Asset"/>
|
||||
<field name="date"/>
|
||||
<filter string="Current" name="current" domain="[('state','in', ('draft','open'))]" help="Assets in draft and open states"/>
|
||||
<filter string="Closed" name="closed" domain="[('state','=', 'close')]" help="Assets in closed state"/>
|
||||
<field name="category_id" string="Asset Category"/>
|
||||
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Date" name="month" domain="[]" context="{'group_by':'date'}"/>
|
||||
<filter string="Asset Category" name="category" domain="[]" context="{'group_by':'category_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
<record model="ir.actions.act_window" id="action_account_asset_asset_form">
|
||||
<field name="name">Assets</field>
|
||||
<field name="res_model">account.asset.asset</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
<field name="view_id" ref="view_account_asset_asset_purchase_tree"/>
|
||||
<field name="domain">[('category_id.type', '=', 'purchase')]</field>
|
||||
</record>
|
||||
|
||||
<menuitem parent="account.menu_finance_entries_management" id="menu_action_account_asset_asset_form" action="action_account_asset_asset_form" sequence="101" groups="account.group_account_manager"/>
|
||||
|
||||
|
||||
<!-- Configuration -->
|
||||
|
||||
<menuitem id="menu_finance_config_assets" name="Assets and Revenues" parent="account.menu_finance_configuration" sequence="25"/>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_account_asset_asset_list_normal_purchase">
|
||||
<field name="name">Asset Types</field>
|
||||
<field name="res_model">account.asset.category</field>
|
||||
<field name="domain">[('type', '=', 'purchase')]</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
<field name="context">{'default_type': 'purchase'}</field>
|
||||
</record>
|
||||
|
||||
<menuitem parent="account.account_management_menu"
|
||||
id="menu_action_account_asset_asset_list_normal_purchase"
|
||||
action="action_account_asset_asset_list_normal_purchase"
|
||||
sequence="6"/>
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_invoice_asset_category">
|
||||
<field name="name">account.move.supplier.form</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='account_id']" position="before">
|
||||
<field string="Asset Category" name="asset_category_id" force_save="1"
|
||||
domain="[('type','=','purchase')]" context="{'default_type':'purchase'}"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='line_ids']/tree/field[@name='account_id']" position="before">
|
||||
<field string="Asset Category" name="asset_category_id" invisibl="1"
|
||||
domain="[('type','=','purchase')]" context="{'default_type':'purchase'}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Product Template -->
|
||||
<record id="view_product_template_form_inherit" model="ir.ui.view">
|
||||
<field name="name">Product Template (form)</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="account.product_template_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="property_account_expense_id" position="after">
|
||||
<field name="asset_category_id"
|
||||
domain="[('type', '=', 'purchase')]"
|
||||
context="{'default_type': 'purchase'}"
|
||||
groups="account.group_account_user"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!--<record id="res_config_settings_view_form" model="ir.ui.view">-->
|
||||
<!--<field name="name">res.config.settings.view.form.inherit.account.asset</field>-->
|
||||
<!--<field name="model">res.config.settings</field>-->
|
||||
<!--<field name="inherit_id" ref="account.res_config_settings_view_form"/>-->
|
||||
<!--<field name="arch" type="xml">-->
|
||||
<!--<field name="module_account_budget" position="after">-->
|
||||
<!--<!–<div id="msg_account_asset" position="replace">–>-->
|
||||
<!--<div class="content-group">-->
|
||||
<!--<div class="mt16">-->
|
||||
<!--<button name="%(om_account_asset.action_account_asset_asset_list_normal_purchase)d" icon="fa-arrow-right" type="action" string="Asset Types" class="btn-link"/>-->
|
||||
<!--</div>-->
|
||||
<!--</div>-->
|
||||
<!--</field>-->
|
||||
<!--</field>-->
|
||||
<!--</record>-->
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import asset_depreciation_confirmation_wizard
|
||||
from . import asset_modify
|
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class AssetDepreciationConfirmationWizard(models.TransientModel):
|
||||
_name = "asset.depreciation.confirmation.wizard"
|
||||
_description = "asset.depreciation.confirmation.wizard"
|
||||
|
||||
date = fields.Date('Account Date', required=True, help="Choose the period for which you want to automatically post the depreciation lines of running assets", default=fields.Date.context_today)
|
||||
|
||||
|
||||
def asset_compute(self):
|
||||
self.ensure_one()
|
||||
context = self._context
|
||||
created_move_ids = self.env['account.asset.asset'].compute_generated_entries(self.date, asset_type=context.get('asset_type'))
|
||||
|
||||
return {
|
||||
'name': _('Created Asset Moves') if context.get('asset_type') == 'purchase' else _('Created Revenue Moves'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'account.move',
|
||||
'view_id': False,
|
||||
'domain': "[('id','in',[" + ','.join(str(id) for id in created_move_ids) + "])]",
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_asset_depreciation_confirmation_wizard" model="ir.ui.view">
|
||||
<field name="name">asset.depreciation.confirmation.wizard</field>
|
||||
<field name="model">asset.depreciation.confirmation.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Compute Asset">
|
||||
<div>
|
||||
<p>
|
||||
This wizard will post installment/depreciation lines for the selected month.<br/>
|
||||
This will generate journal entries for all related installment lines on this period of asset/revenue recognition as well.
|
||||
</p>
|
||||
</div>
|
||||
<group>
|
||||
<field name="date"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Generate Entries" name="asset_compute" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_asset_depreciation_confirmation_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Post Depreciation Lines</field>
|
||||
<field name="res_model">asset.depreciation.confirmation.wizard</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="view_asset_depreciation_confirmation_wizard"/>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'asset_type': 'purchase'}</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Generate Assets Entries"
|
||||
action="action_asset_depreciation_confirmation_wizard"
|
||||
id="menu_asset_depreciation_confirmation_wizard"
|
||||
parent="account.menu_finance_entries_generate_entries"
|
||||
sequence="111"
|
||||
groups="account.group_account_manager"/>
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,86 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
# from odoo.osv.orm import setup_modifiers
|
||||
|
||||
|
||||
class AssetModify(models.TransientModel):
|
||||
_name = 'asset.modify'
|
||||
_description = 'Modify Asset'
|
||||
|
||||
name = fields.Text(string='Reason', required=True)
|
||||
method_number = fields.Integer(string='Number of Depreciations', required=True)
|
||||
method_period = fields.Integer(string='Period Length')
|
||||
method_end = fields.Date(string='Ending date')
|
||||
asset_method_time = fields.Char(compute='_get_asset_method_time', string='Asset Method Time', readonly=True)
|
||||
|
||||
|
||||
def _get_asset_method_time(self):
|
||||
if self.env.context.get('active_id'):
|
||||
asset = self.env['account.asset.asset'].browse(self.env.context.get('active_id'))
|
||||
self.asset_method_time = asset.method_time
|
||||
|
||||
# @api.model
|
||||
# def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
|
||||
# result = super(AssetModify, self).fields_view_get(view_id, view_type, toolbar=toolbar, submenu=submenu)
|
||||
# asset_id = self.env.context.get('active_id')
|
||||
# active_model = self.env.context.get('active_model')
|
||||
# if active_model == 'account.asset.asset' and asset_id:
|
||||
# asset = self.env['account.asset.asset'].browse(asset_id)
|
||||
# doc = etree.XML(result['arch'])
|
||||
# if asset.method_time == 'number' and doc.xpath("//field[@name='method_end']"):
|
||||
# node = doc.xpath("//field[@name='method_end']")[0]
|
||||
# node.set('invisible', '1')
|
||||
# setup_modifiers(node, result['fields']['method_end'])
|
||||
# elif asset.method_time == 'end' and doc.xpath("//field[@name='method_number']"):
|
||||
# node = doc.xpath("//field[@name='method_number']")[0]
|
||||
# node.set('invisible', '1')
|
||||
# setup_modifiers(node, result['fields']['method_number'])
|
||||
# result['arch'] = etree.tostring(doc, encoding='unicode')
|
||||
# return result
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super(AssetModify, self).default_get(fields)
|
||||
asset_id = self.env.context.get('active_id')
|
||||
asset = self.env['account.asset.asset'].browse(asset_id)
|
||||
if 'name' in fields:
|
||||
res.update({'name': asset.name})
|
||||
if 'method_number' in fields and asset.method_time == 'number':
|
||||
res.update({'method_number': asset.method_number})
|
||||
if 'method_period' in fields:
|
||||
res.update({'method_period': asset.method_period})
|
||||
if 'method_end' in fields and asset.method_time == 'end':
|
||||
res.update({'method_end': asset.method_end})
|
||||
if self.env.context.get('active_id'):
|
||||
active_asset = self.env['account.asset.asset'].browse(self.env.context.get('active_id'))
|
||||
res['asset_method_time'] = active_asset.method_time
|
||||
return res
|
||||
|
||||
|
||||
def modify(self):
|
||||
""" Modifies the duration of asset for calculating depreciation
|
||||
and maintains the history of old values, in the chatter.
|
||||
"""
|
||||
asset_id = self.env.context.get('active_id', False)
|
||||
asset = self.env['account.asset.asset'].browse(asset_id)
|
||||
old_values = {
|
||||
'method_number': asset.method_number,
|
||||
'method_period': asset.method_period,
|
||||
'method_end': asset.method_end,
|
||||
}
|
||||
asset_vals = {
|
||||
'method_number': self.method_number,
|
||||
'method_period': self.method_period,
|
||||
'method_end': self.method_end,
|
||||
}
|
||||
asset.write(asset_vals)
|
||||
asset.compute_depreciation_board()
|
||||
tracked_fields = self.env['account.asset.asset'].fields_get(['method_number', 'method_period', 'method_end'])
|
||||
changes, tracking_value_ids = asset._message_track(tracked_fields, old_values)
|
||||
if changes:
|
||||
asset.message_post(subject=_('Depreciation board modified'), body=self.name, tracking_value_ids=tracking_value_ids)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="asset_modify_form">
|
||||
<field name="name">wizard.asset.modify.form</field>
|
||||
<field name="model">asset.modify</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Modify Asset">
|
||||
<field name="asset_method_time" invisible="1"/>
|
||||
<group string="Asset Durations to Modify" col="4">
|
||||
<group colspan="2" col="2">
|
||||
<field name="name"/>
|
||||
<field name="method_number" attrs="{'invisible': [('asset_method_time', '=', 'end')]}"/>
|
||||
</group>
|
||||
<group colspan="2" col="2">
|
||||
<field name="method_end" attrs="{'invisible': [('asset_method_time', '=', 'number')]}"/>
|
||||
<label for="method_period"/>
|
||||
<div>
|
||||
<field name="method_period" class="oe_inline"/> months
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="modify" string="Modify" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_asset_modify" model="ir.actions.act_window">
|
||||
<field name="name">Modify Asset</field>
|
||||
<field name="res_model">asset.modify</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="asset_modify_form"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
Loading…
Reference in New Issue