Merge PR #1819 into 17.0

Signed-off-by pedrobaeza
This commit is contained in:
OCA-git-bot
2024-05-31 07:31:37 +00:00
119 changed files with 142794 additions and 0 deletions

View File

@@ -0,0 +1,176 @@
=================
Assets Management
=================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:843676bebe20651c6131f76d504144b859ebaca41703b5b8a63f97a7b4998070
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Mature-brightgreen.png
:target: https://odoo-community.org/page/development-status
:alt: Mature
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Faccount--financial--tools-lightgray.png?logo=github
:target: https://github.com/OCA/account-financial-tools/tree/17.0/account_asset_management
:alt: OCA/account-financial-tools
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/account-financial-tools-17-0/account-financial-tools-17-0-account_asset_management
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/account-financial-tools&target_branch=17.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This Module manages the assets owned by a company. It will keep track of
depreciation's occurred on those assets. And it allows to create
accounting entries from the depreciation lines.
The full asset life-cycle is managed (from asset creation to asset
removal).
Assets can be created manually as well as automatically (via the
creation of an accounting entry on the asset account).
Depreciation Journal Entries can be created manually in the "Deprecation
Board" tab, or automatically by two ways:
- Using the "Invoicing/Assets/Compute Assets" wizard.
- Activating the "Asset Management: Generate assets" cron.
These options are compatibles each other.
The module contains a large number of functional enhancements compared
to the standard account_asset module from Odoo.
**Table of contents**
.. contents::
:local:
Usage
=====
The module in NOT compatible with the standard account_asset module.
Changelog
=========
14.0.1.0.0 (2021-01-08)
-----------------------
- [BREAKING] Removed all functionality associated with
account.fiscal.year
13.0.3.0.0 (2021-07-06)
-----------------------
- Allow to reverse the posting of a depreciation line instead of
deleting the journal entry.
13.0.2.0.0 (2021-02-19)
-----------------------
- Add support for multi-company
13.0.1.0.0 (2019-10-21)
-----------------------
- Python code and views were adapted to be compatible with v13.
- When assets are created through accounting journal items, they are
created when the journal items is posted.
- When a Bill Invoice is created or modified, at the time it is saved,
for each line that has an Asset profile and Quantity 'N' greater than
1, it will be replaced by 'N' lines identical to it but with quantity
1. This was done to maintain the same behavior as in the previous
version, in which for each asset created there is a Journal Item. In
addition, this solution does not change the data model which does not
cause migration scripts.
- The configuration option was removed so the only function of that is
to allow the module to be uninstalled by unchecking that
configuration option.
- Tests were adapted.
12.0.2.1.0 (2019-10-21)
-----------------------
- [IMP] Add option to calculate depreciation table by days
12.0.1.0.0 (2019-01-13)
-----------------------
- [BREAKING] account.asset: parent_path has replaced parent_left &
parent_right (TODO: migration script)
- [BREAKING] account.asset.recompute.trigger: depends on date_range.py
(TODO: re-implement in account_fiscal_year.py)
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/account-financial-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/account-financial-tools/issues/new?body=module:%20account_asset_management%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* Noviat
Contributors
------------
- OpenERP SA
- Luc De Meyer (Noviat)
- Frédéric Clementi (camptocamp)
- Florian Dacosta (Akretion)
- Stéphane Bidoul (Acsone)
- Adrien Peiffer (Acsone)
- Akim Juillerat <akim.juillerat@camptocamp.com>
- Henrik Norlin (Apps2GROW)
- Maxence Groine <mgroine@fiefmanage.ch>
- Kitti Upariphutthiphong <kittiu@ecosoft.co.th>
- Saran Lim. <saranl@ecosoft.co.th>
- `Tecnativa <https://www.tecnativa.com>`__:
- Ernesto Tejeda
- Pedro M. Baeza
- João Marques
- Víctor Martínez
- `ForgeFlow <https://www.forgeflow.com>`__:
- Jordi Ballester <jordi.ballester@forgeflow.com>
- Miquel Raïch <miquel.raich@forgeflow.com>
- `Sygel <https://www.sygel.es>`__:
- Manuel Regidor <manuel.regidor@sygel.es>
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/account-financial-tools <https://github.com/OCA/account-financial-tools/tree/17.0/account_asset_management>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,3 @@
from . import models
from . import report
from . import wizard

View File

@@ -0,0 +1,33 @@
# Copyright 2009-2019 Noviat
# Copyright 2019 Tecnativa - Pedro M. Baeza
# Copyright 2021 Tecnativa - João Marques
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Assets Management",
"version": "17.0.1.0.0",
"license": "AGPL-3",
"depends": ["account", "report_xlsx_helper"],
"excludes": ["account_asset"],
"development_status": "Mature",
"external_dependencies": {"python": ["python-dateutil"]},
"author": "Noviat, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/account-financial-tools",
"category": "Accounting & Finance",
"data": [
"security/account_asset_security.xml",
"security/ir.model.access.csv",
"wizard/account_asset_compute.xml",
"wizard/account_asset_remove.xml",
"views/account_account.xml",
"views/account_asset.xml",
"views/account_asset_group.xml",
"views/account_asset_profile.xml",
"views/account_move.xml",
"views/account_move_line.xml",
"views/menuitem.xml",
"data/cron.xml",
"wizard/wiz_account_asset_report.xml",
"wizard/wiz_asset_move_reverse.xml",
],
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record forcecreate="True" id="ir_cron_assets_generator" model="ir.cron">
<field name="name">Asset Management: Generate assets</field>
<field name="model_id" ref="model_account_asset_compute" />
<field name="state">code</field>
<field name="code">model.create({}).asset_compute()</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="active" eval="False" />
<field name="doall" eval="False" />
</record>
</odoo>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
from . import account_account
from . import account_asset
from . import account_asset_group
from . import account_asset_profile
from . import account_asset_line
from . import account_asset_recompute_trigger
from . import account_move

View File

@@ -0,0 +1,30 @@
# Copyright 2009-2017 Noviat
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class AccountAccount(models.Model):
_inherit = "account.account"
asset_profile_id = fields.Many2one(
comodel_name="account.asset.profile",
string="Asset Profile",
check_company=True,
help="Default Asset Profile when creating invoice lines with this account.",
)
@api.constrains("asset_profile_id")
def _check_asset_profile(self):
for account in self:
if (
account.asset_profile_id
and account.asset_profile_id.account_asset_id != account
):
raise ValidationError(
_(
"The Asset Account defined in the Asset Profile "
"must be equal to the account."
)
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
# Copyright 2009-2020 Noviat
# Copyright 2019 Tecnativa - Pedro M. Baeza
# Copyright 2021 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class AccountAssetGroup(models.Model):
_name = "account.asset.group"
_description = "Asset Group"
_order = "code, name"
_parent_store = True
_check_company_auto = True
_rec_names_search = ["code", "name"]
name = fields.Char(size=64, required=True, index=True)
code = fields.Char(index=True)
parent_path = fields.Char(index=True, unaccent=False)
company_id = fields.Many2one(
comodel_name="res.company",
string="Company",
required=True,
default=lambda self: self._default_company_id(),
)
parent_id = fields.Many2one(
comodel_name="account.asset.group",
string="Parent Asset Group",
ondelete="restrict",
check_company=True,
)
child_ids = fields.One2many(
comodel_name="account.asset.group",
inverse_name="parent_id",
string="Child Asset Groups",
check_company=True,
)
@api.model
def _default_company_id(self):
return self.env.company
@api.depends("code", "name")
def _compute_display_name(self):
params = self.env.context.get("params")
list_view = params and params.get("view_type") == "list"
short_name_len = 16
for rec in self:
if rec.code:
full_name = rec.code + " " + rec.name
short_name = rec.code
else:
full_name = rec.name
if len(full_name) > short_name_len:
short_name = full_name[:16] + "..."
else:
short_name = full_name
if list_view:
name = short_name
else:
name = full_name
rec.display_name = name

View File

@@ -0,0 +1,333 @@
# Copyright 2009-2018 Noviat
# Copyright 2021 Tecnativa - João Marques
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class AccountAssetLine(models.Model):
_name = "account.asset.line"
_description = "Asset depreciation table line"
_order = "type, line_date"
_check_company_auto = True
name = fields.Char(string="Depreciation Name", size=64, readonly=True)
asset_id = fields.Many2one(
comodel_name="account.asset",
string="Asset",
required=True,
ondelete="cascade",
check_company=True,
index=True,
)
previous_id = fields.Many2one(
comodel_name="account.asset.line",
string="Previous Depreciation Line",
readonly=True,
)
parent_state = fields.Selection(
related="asset_id.state",
string="State of Asset",
)
depreciation_base = fields.Monetary(
related="asset_id.depreciation_base",
string="Depreciation Base",
)
amount = fields.Monetary(required=True)
remaining_value = fields.Monetary(
compute="_compute_values",
string="Next Period Depreciation",
store=True,
)
depreciated_value = fields.Monetary(
compute="_compute_values",
string="Amount Already Depreciated",
store=True,
)
line_date = fields.Date(string="Date", required=True)
line_days = fields.Integer(string="Days", readonly=True)
move_id = fields.Many2one(
comodel_name="account.move",
string="Depreciation Entry",
readonly=True,
check_company=True,
)
move_check = fields.Boolean(
compute="_compute_move_check", string="Posted", store=True
)
type = fields.Selection(
selection=[
("create", "Depreciation Base"),
("depreciate", "Depreciation"),
("remove", "Asset Removal"),
],
readonly=True,
default="depreciate",
)
init_entry = fields.Boolean(
string="Initial Balance Entry",
help="Set this flag for entries of previous fiscal years "
"for which Odoo has not generated accounting entries.",
)
company_id = fields.Many2one(related="asset_id.company_id", store=True)
currency_id = fields.Many2one(
related="asset_id.company_id.currency_id", store=True, string="Company Currency"
)
@api.depends("amount", "previous_id", "type")
def _compute_values(self):
self.depreciated_value = 0.0
self.remaining_value = 0.0
dlines = self
if self.env.context.get("no_compute_asset_line_ids"):
# skip compute for lines in unlink
exclude_ids = self.env.context["no_compute_asset_line_ids"]
dlines = self.filtered(lambda line: line.id not in exclude_ids)
dlines = dlines.filtered(lambda line: line.type == "depreciate")
dlines = dlines.sorted(key=lambda line: line.line_date)
# Give value 0 to the lines that are not going to be calculated
# to avoid cache miss error
all_excluded_lines = self - dlines
all_excluded_lines.depreciated_value = 0
all_excluded_lines.remaining_value = 0
# Group depreciation lines per asset
asset_ids = dlines.mapped("asset_id")
grouped_dlines = []
for asset in asset_ids:
grouped_dlines.append(
dlines.filtered(lambda line, asset=asset: line.asset_id.id == asset.id)
)
for dlines in grouped_dlines:
for i, dl in enumerate(dlines):
if i == 0:
depreciation_base = dl.depreciation_base
tmp = depreciation_base - dl.previous_id.remaining_value
depreciated_value = dl.previous_id and tmp or 0.0
remaining_value = depreciation_base - depreciated_value - dl.amount
else:
depreciated_value += dl.previous_id.amount
remaining_value -= dl.amount
dl.depreciated_value = depreciated_value
dl.remaining_value = remaining_value
@api.depends("move_id")
def _compute_move_check(self):
for line in self:
line.move_check = bool(line.move_id)
@api.onchange("amount")
def _onchange_amount(self):
if self.type == "depreciate":
self.remaining_value = (
self.depreciation_base - self.depreciated_value - self.amount
)
def write(self, vals):
for dl in self:
line_date = vals.get("line_date") or dl.line_date
asset_lines = dl.asset_id.depreciation_line_ids
if list(vals.keys()) == ["move_id"] and not vals["move_id"]:
# allow to remove an accounting entry via the
# 'Delete Move' button on the depreciation lines.
if not self.env.context.get("unlink_from_asset"):
raise UserError(
_(
"You are not allowed to remove an accounting entry "
"linked to an asset."
"\nYou should remove such entries from the asset."
)
)
elif list(vals.keys()) == ["asset_id"]:
continue
elif (
dl.move_id
and not self.env.context.get("allow_asset_line_update")
and dl.type != "create"
):
raise UserError(
_(
"You cannot change a depreciation line "
"with an associated accounting entry."
)
)
elif vals.get("init_entry"):
check = asset_lines.filtered(
lambda line, line_date=line_date: line.move_check
and line.type == "depreciate"
and line.line_date <= line_date
)
if check:
raise UserError(
_(
"You cannot set the 'Initial Balance Entry' flag "
"on a depreciation line "
"with prior posted entries."
)
)
elif vals.get("line_date"):
if dl.type == "create":
check = asset_lines.filtered(
lambda line: line.type != "create"
and (line.init_entry or line.move_check)
and line.line_date < fields.Date.to_date(vals["line_date"])
)
if check:
raise UserError(
_(
"You cannot set the Asset Start Date "
"after already posted entries."
)
)
else:
check = asset_lines.filtered(
lambda al, dl=dl: al != dl
and (al.init_entry or al.move_check)
and al.line_date > fields.Date.to_date(vals["line_date"])
)
if check:
raise UserError(
_(
"You cannot set the date on a depreciation line "
"prior to already posted entries."
)
)
return super().write(vals)
def unlink(self):
for dl in self:
if dl.type == "create" and dl.amount:
raise UserError(
_("You cannot remove an asset line of type 'Depreciation Base'.")
)
elif dl.move_id:
raise UserError(
_(
"You cannot delete a depreciation line with "
"an associated accounting entry."
)
)
previous = dl.previous_id
next_line = dl.asset_id.depreciation_line_ids.filtered(
lambda line, dl=dl: line.previous_id == dl and line not in self
)
if next_line:
next_line.previous_id = previous
return super(
AccountAssetLine, self.with_context(no_compute_asset_line_ids=self.ids)
).unlink()
def _setup_move_data(self, depreciation_date):
asset = self.asset_id
move_data = {
"date": depreciation_date,
"ref": f"{asset.name} - {self.name}",
"journal_id": asset.profile_id.journal_id.id,
}
return move_data
def _setup_move_line_data(self, depreciation_date, account, ml_type, move):
"""Prepare data to be propagated to account.move.line"""
asset = self.asset_id
currency = asset.company_id.currency_id
amount = self.amount
amount_comp = currency.compare_amounts(amount, 0)
analytic_distribution = False
if ml_type == "depreciation":
debit = amount_comp < 0 and -amount or 0.0
credit = amount_comp > 0 and amount or 0.0
elif ml_type == "expense":
debit = amount_comp > 0 and amount or 0.0
credit = amount_comp < 0 and -amount or 0.0
analytic_distribution = asset.analytic_distribution
move_line_data = {
"name": asset.name,
"ref": self.name,
"move_id": move.id,
"account_id": account.id,
"credit": credit,
"debit": debit,
"journal_id": asset.profile_id.journal_id.id,
"partner_id": asset.partner_id.id,
"analytic_distribution": analytic_distribution,
"date": depreciation_date,
"asset_id": asset.id,
}
return move_line_data
def create_move(self):
created_move_ids = []
asset_ids = set()
ctx = dict(self.env.context, allow_asset=True, check_move_validity=False)
for line in self:
asset = line.asset_id
depreciation_date = line.line_date
am_vals = line._setup_move_data(depreciation_date)
move = self.env["account.move"].with_context(**ctx).create(am_vals)
depr_acc = asset.profile_id.account_depreciation_id
exp_acc = asset.profile_id.account_expense_depreciation_id
aml_d_vals = line._setup_move_line_data(
depreciation_date, depr_acc, "depreciation", move
)
self.env["account.move.line"].with_context(**ctx).create(aml_d_vals)
aml_e_vals = line._setup_move_line_data(
depreciation_date, exp_acc, "expense", move
)
self.env["account.move.line"].with_context(**ctx).create(aml_e_vals)
move.action_post()
line.with_context(allow_asset_line_update=True).write({"move_id": move.id})
created_move_ids.append(move.id)
asset_ids.add(asset.id)
# we re-evaluate the assets to determine if we can close them
for asset in self.env["account.asset"].browse(list(asset_ids)):
if asset.currency_id.is_zero(asset.value_residual):
asset.state = "close"
return created_move_ids
def open_move(self):
self.ensure_one()
return {
"name": _("Journal Entry"),
"view_mode": "form",
"res_id": self.move_id.id,
"res_model": "account.move",
"view_id": False,
"type": "ir.actions.act_window",
"context": self.env.context,
}
def update_asset_line_after_unlink_move(self):
self.write({"move_id": False})
if self.parent_state == "close":
self.asset_id.write({"state": "open"})
elif self.parent_state == "removed" and self.type == "remove":
self.asset_id.write({"state": "close", "date_remove": False})
self.unlink()
def unlink_move(self):
for line in self:
if line.asset_id.profile_id.allow_reversal:
context = dict(self._context or {})
context.update(
{
"active_model": self._name,
"active_ids": line.ids,
"active_id": line.id,
}
)
return {
"name": _("Reverse Move"),
"view_mode": "form",
"res_model": "wiz.asset.move.reverse",
"target": "new",
"type": "ir.actions.act_window",
"context": context,
}
else:
move = line.move_id
move.button_draft()
move.with_context(force_delete=True, unlink_from_asset=True).unlink()
line.with_context(
unlink_from_asset=True
).update_asset_line_after_unlink_move()
return True

View File

@@ -0,0 +1,232 @@
# Copyright 2009-2018 Noviat
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class AccountAssetProfile(models.Model):
_name = "account.asset.profile"
_inherit = "analytic.mixin"
_check_company_auto = True
_description = "Asset profile"
_order = "name"
name = fields.Char(size=64, required=True, index=True)
note = fields.Text()
account_asset_id = fields.Many2one(
comodel_name="account.account",
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]",
string="Asset Account",
check_company=True,
required=True,
)
account_depreciation_id = fields.Many2one(
comodel_name="account.account",
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]",
string="Depreciation Account",
check_company=True,
required=True,
)
account_expense_depreciation_id = fields.Many2one(
comodel_name="account.account",
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]",
string="Depr. Expense Account",
check_company=True,
required=True,
)
account_plus_value_id = fields.Many2one(
comodel_name="account.account",
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]",
check_company=True,
string="Plus-Value Account",
)
account_min_value_id = fields.Many2one(
comodel_name="account.account",
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]",
check_company=True,
string="Min-Value Account",
)
account_residual_value_id = fields.Many2one(
comodel_name="account.account",
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]",
check_company=True,
string="Residual Value Account",
)
journal_id = fields.Many2one(
comodel_name="account.journal",
domain="[('type', '=', 'general'), ('company_id', '=', company_id)]",
string="Journal",
check_company=True,
required=True,
)
company_id = fields.Many2one(
comodel_name="res.company",
string="Company",
required=True,
default=lambda self: self._default_company_id(),
)
group_ids = fields.Many2many(
comodel_name="account.asset.group",
relation="account_asset_profile_group_rel",
column1="profile_id",
column2="group_id",
check_company=True,
string="Asset Groups",
)
method = fields.Selection(
selection=lambda self: self._selection_method(),
string="Computation Method",
required=True,
help="Choose the method to use to compute the depreciation lines.\n"
" * Linear: Calculated on basis of: "
"Depreciation Base / Number of Depreciations. "
"Depreciation Base = Purchase Value - Salvage Value.\n"
" * Linear-Limit: Linear up to Salvage Value. "
"Depreciation Base = Purchase Value.\n"
" * Degressive: Calculated on basis of: "
"Residual Value * Degressive Factor.\n"
" * Degressive-Linear (only for Time Method = Year): "
"Degressive becomes linear when the annual linear "
"depreciation exceeds the annual degressive depreciation.\n"
" * Degressive-Limit: Degressive up to Salvage Value. "
"The Depreciation Base is equal to the asset value.",
default="linear",
)
method_number = fields.Integer(
string="Number of Years",
help="The number of years needed to depreciate your asset",
default=5,
)
method_period = fields.Selection(
selection=lambda self: self._selection_method_period(),
string="Period Length",
required=True,
default="year",
help="Period length for the depreciation accounting entries",
)
method_progress_factor = fields.Float(string="Degressive Factor", default=0.3)
method_time = fields.Selection(
selection=lambda self: self._selection_method_time(),
string="Time Method",
required=True,
default="year",
help="Choose the method to use to compute the dates and "
"number of depreciation lines.\n"
" * Number of Years: Specify the number of years "
"for the depreciation.\n"
" * Number of Depreciations: Fix the number of "
"depreciation lines and the time between 2 depreciations.\n",
)
days_calc = fields.Boolean(
string="Calculate by days",
default=False,
help="Use number of days to calculate depreciation amount",
)
use_leap_years = fields.Boolean(
default=False,
help="If not set, the system will distribute evenly the amount to "
"amortize across the years, based on the number of years. "
"So the amount per year will be the "
"depreciation base / number of years.\n "
"If set, the system will consider if the current year "
"is a leap year. The amount to depreciate per year will be "
"calculated as depreciation base / (depreciation end date - "
"start date + 1) * days in the current year.",
)
prorata = fields.Boolean(
string="Prorata Temporis",
compute="_compute_prorrata",
readonly=False,
store=True,
help="Indicates that the first depreciation entry for this asset "
"has to be done from the depreciation start date instead of "
"the first day of the fiscal year.",
)
open_asset = fields.Boolean(
string="Skip Draft State",
help="Check this if you want to automatically confirm the assets "
"of this profile when created by invoices.",
)
asset_product_item = fields.Boolean(
string="Create an asset by product item",
help="By default during the validation of an invoice, an asset "
"is created by invoice line as long as an accounting entry is "
"created by invoice line. "
"With this setting, an accounting entry will be created by "
"product item. So, there will be an asset by product item.",
)
active = fields.Boolean(default=True)
allow_reversal = fields.Boolean(
"Allow Reversal of journal entries",
help="If set, when pressing the Delete/Reverse Move button in a "
"posted depreciation line will prompt the option to reverse the "
"journal entry, instead of deleting them.",
)
@api.model
def _default_company_id(self):
return self.env.company
@api.model
def _selection_method(self):
return [
("linear", _("Linear")),
("linear-limit", _("Linear up to Salvage Value")),
("degressive", _("Degressive")),
("degr-linear", _("Degressive-Linear")),
("degr-limit", _("Degressive up to Salvage Value")),
]
@api.model
def _selection_method_period(self):
return [("month", _("Month")), ("quarter", _("Quarter")), ("year", _("Year"))]
@api.model
def _selection_method_time(self):
return [
("year", _("Number of Years or end date")),
("number", _("Number of Depreciations")),
]
@api.constrains("method", "method_time")
def _check_method(self):
if any(a.method == "degr-linear" and a.method_time != "year" for a in self):
raise UserError(
_("Degressive-Linear is only supported for Time Method = Year.")
)
@api.depends("method_time")
def _compute_prorrata(self):
for profile in self:
if profile.method_time != "year":
profile.prorata = True
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get("method_time") != "year" and not vals.get("prorata"):
vals["prorata"] = True
profile_ids = super().create(vals_list)
account_dict = {}
for profile_id in profile_ids.filtered(
lambda x: not x.account_asset_id.asset_profile_id
):
account_dict.setdefault(profile_id.account_asset_id, []).append(
profile_id.id
)
for account, profile_list in account_dict.items():
account.write({"asset_profile_id": profile_list[-1]})
return profile_ids
def write(self, vals):
if vals.get("method_time"):
if vals["method_time"] != "year" and not vals.get("prorata"):
vals["prorata"] = True
res = super().write(vals)
# TODO last profile in self is defined as default on the related
# account. must be improved.
account = self.env["account.account"].browse(vals.get("account_asset_id"))
if self and account and not account.asset_profile_id:
account.write({"asset_profile_id": self[-1].id})
return res

View File

@@ -0,0 +1,23 @@
# Copyright 2009-2018 Noviat
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class AccountAssetRecomputeTrigger(models.Model):
_name = "account.asset.recompute.trigger"
_description = "Asset table recompute triggers"
reason = fields.Char(required=True)
company_id = fields.Many2one("res.company", string="Company", required=True)
date_trigger = fields.Datetime(
"Trigger Date",
readonly=True,
help="Date of the event triggering the need to recompute the Asset Tables.",
)
date_completed = fields.Datetime("Completion Date", readonly=True)
state = fields.Selection(
selection=[("open", "Open"), ("done", "Done")],
default="open",
readonly=True,
)

View File

@@ -0,0 +1,263 @@
# Copyright 2009-2018 Noviat
# Copyright 2021 Tecnativa - João Marques
# Copyright 2021 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from markupsafe import Markup
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.tests.common import Form
_logger = logging.getLogger(__name__)
# List of move's fields that can't be modified if move is linked
# with a depreciation line
FIELDS_AFFECTS_ASSET_MOVE = {"journal_id", "date"}
# List of move line's fields that can't be modified if move is linked
# with a depreciation line
FIELDS_AFFECTS_ASSET_MOVE_LINE = {
"credit",
"debit",
"account_id",
"journal_id",
"date",
"asset_profile_id",
"asset_id",
}
class AccountMove(models.Model):
_inherit = "account.move"
asset_count = fields.Integer(compute="_compute_asset_count")
def _compute_asset_count(self):
rg_res = self.env["account.asset.line"].read_group(
[("move_id", "in", self.ids)], ["move_id"], ["move_id"]
)
mapped_data = {x["move_id"][0]: x["move_id_count"] for x in rg_res}
for move in self:
move.asset_count = mapped_data.get(move.id, 0)
def unlink(self):
# for move in self:
deprs = self.env["account.asset.line"].search(
[("move_id", "in", self.ids), ("type", "in", ["depreciate", "remove"])]
)
if deprs and not self.env.context.get("unlink_from_asset"):
raise UserError(
_(
"You are not allowed to remove an accounting entry "
"linked to an asset."
"\nYou should remove such entries from the asset."
)
)
# trigger store function
deprs.write({"move_id": False})
return super().unlink()
def write(self, vals):
if set(vals).intersection(FIELDS_AFFECTS_ASSET_MOVE):
deprs = (
self.env["account.asset.line"]
.sudo()
.search([("move_id", "in", self.ids), ("type", "=", "depreciate")])
)
if deprs:
raise UserError(
_(
"You cannot change an accounting entry "
"linked to an asset depreciation line."
)
)
return super().write(vals)
def _prepare_asset_vals(self, aml):
depreciation_base = aml.balance
return {
"name": aml.name,
"code": self.name,
"profile_id": aml.asset_profile_id,
"purchase_value": depreciation_base,
"partner_id": aml.partner_id,
"date_start": self.date,
}
def action_post(self):
ret_val = super().action_post()
for move in self:
for aml in move.line_ids.filtered(
lambda line: line.asset_profile_id and not line.tax_line_id
):
if not aml.name:
raise UserError(
_("Asset name must be set in the label of the line.")
)
if aml.asset_id:
continue
vals = move._prepare_asset_vals(aml)
asset_form = Form(
self.env["account.asset"]
.with_company(move.company_id)
.with_context(create_asset_from_move_line=True, move_id=move.id)
)
for key, val in vals.items():
setattr(asset_form, key, val)
asset = asset_form.save()
asset.analytic_distribution = aml.analytic_distribution
aml.with_context(
allow_asset=True, allow_asset_removal=True
).asset_id = asset.id
new_name_get = []
for asset in move.line_ids.filtered("asset_profile_id").asset_id:
new_name_get = [asset.id, asset.display_name]
if new_name_get:
message = _(
"This invoice created the asset(s): %s",
Markup(
"""<a href=# data-oe-model=account.asset data-oe-id={}"""
""">{}</a>""".format(new_name_get[0], new_name_get[1])
),
)
move.message_post(body=message)
return ret_val
def button_draft(self):
invoices = self.filtered(lambda r: r.is_purchase_document())
if invoices:
invoices.line_ids.asset_id.unlink()
return super().button_draft()
def _reverse_move_vals(self, default_values, cancel=True):
move_vals = super()._reverse_move_vals(default_values, cancel)
if move_vals["move_type"] not in ("out_invoice", "out_refund"):
for line_command in move_vals.get("line_ids", []):
line_vals = line_command[2] # (0, 0, {...})
asset = self.env["account.asset"].browse(line_vals["asset_id"])
# We remove the asset if we recognize that we are reversing
# the asset creation
if asset:
asset_line = self.env["account.asset.line"].search(
[("asset_id", "=", asset.id), ("type", "=", "create")], limit=1
)
if asset_line and asset_line.move_id == self:
asset.unlink()
line_vals.update(asset_profile_id=False, asset_id=False)
return move_vals
def action_view_assets(self):
assets = (
self.env["account.asset.line"]
.search([("move_id", "=", self.id)])
.mapped("asset_id")
)
action = self.env.ref("account_asset_management.account_asset_action")
action_dict = action.sudo().read()[0]
if len(assets) == 1:
res = self.env.ref(
"account_asset_management.account_asset_view_form", False
)
action_dict["views"] = [(res and res.id or False, "form")]
action_dict["res_id"] = assets.id
elif assets:
action_dict["domain"] = [("id", "in", assets.ids)]
else:
action_dict = {"type": "ir.actions.act_window_close"}
return action_dict
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
asset_profile_id = fields.Many2one(
comodel_name="account.asset.profile",
string="Asset Profile",
compute="_compute_asset_profile",
store=True,
readonly=False,
)
asset_id = fields.Many2one(
comodel_name="account.asset",
string="Asset",
ondelete="restrict",
check_company=True,
)
@api.depends("account_id", "asset_id")
def _compute_asset_profile(self):
for rec in self:
if rec.account_id.asset_profile_id and not rec.asset_id:
rec.asset_profile_id = rec.account_id.asset_profile_id
elif rec.asset_id:
rec.asset_profile_id = rec.asset_id.profile_id
@api.onchange("asset_profile_id")
def _onchange_asset_profile_id(self):
if self.asset_profile_id.account_asset_id:
self.account_id = self.asset_profile_id.account_asset_id
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
move = self.env["account.move"].browse(vals.get("move_id"))
if not move.is_sale_document():
if vals.get("asset_id") and not self.env.context.get("allow_asset"):
raise UserError(
_(
"You are not allowed to link "
"an accounting entry to an asset."
"\nYou should generate such entries from the asset."
)
)
records = super().create(vals_list)
for record in records:
record._expand_asset_line()
return records
def write(self, vals):
if set(vals).intersection(FIELDS_AFFECTS_ASSET_MOVE_LINE) and not (
self.env.context.get("allow_asset_removal")
and list(vals.keys()) == ["asset_id"]
):
# Check if at least one asset is linked to a move
linked_asset = False
for move_line in self.filtered(lambda r: not r.move_id.is_sale_document()):
linked_asset = move_line.asset_id
if linked_asset:
raise UserError(
_(
"You cannot change an accounting item "
"linked to an asset depreciation line."
)
)
if (
self.filtered(lambda r: not r.move_id.is_sale_document())
and vals.get("asset_id")
and not self.env.context.get("allow_asset")
):
raise UserError(
_(
"You are not allowed to link "
"an accounting entry to an asset."
"\nYou should generate such entries from the asset."
)
)
super().write(vals)
if "quantity" in vals or "asset_profile_id" in vals:
for record in self:
record._expand_asset_line()
return True
def _expand_asset_line(self):
self.ensure_one()
if self.asset_profile_id.asset_product_item and self.quantity > 1.0:
aml = self.with_context(check_move_validity=False)
qty = self.quantity
name = self.name
aml.write({"quantity": 1, "name": f"{name} {1}"})
for i in range(1, int(qty)):
aml.copy({"name": f"{name} {i + 1}"})

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1,21 @@
- OpenERP SA
- Luc De Meyer (Noviat)
- Frédéric Clementi (camptocamp)
- Florian Dacosta (Akretion)
- Stéphane Bidoul (Acsone)
- Adrien Peiffer (Acsone)
- Akim Juillerat \<<akim.juillerat@camptocamp.com>\>
- Henrik Norlin (Apps2GROW)
- Maxence Groine \<<mgroine@fiefmanage.ch>\>
- Kitti Upariphutthiphong \<<kittiu@ecosoft.co.th>\>
- Saran Lim. \<<saranl@ecosoft.co.th>\>
- [Tecnativa](https://www.tecnativa.com):
- Ernesto Tejeda
- Pedro M. Baeza
- João Marques
- Víctor Martínez
- [ForgeFlow](https://www.forgeflow.com):
- Jordi Ballester \<<jordi.ballester@forgeflow.com>\>
- Miquel Raïch \<<miquel.raich@forgeflow.com>\>
- [Sygel](https://www.sygel.es):
- Manuel Regidor \<<manuel.regidor@sygel.es>\>

View File

@@ -0,0 +1,20 @@
This Module manages the assets owned by a company. It will keep track of
depreciation's occurred on those assets. And it allows to create
accounting entries from the depreciation lines.
The full asset life-cycle is managed (from asset creation to asset
removal).
Assets can be created manually as well as automatically (via the
creation of an accounting entry on the asset account).
Depreciation Journal Entries can be created manually in the "Deprecation
Board" tab, or automatically by two ways:
- Using the "Invoicing/Assets/Compute Assets" wizard.
- Activating the "Asset Management: Generate assets" cron.
These options are compatibles each other.
The module contains a large number of functional enhancements compared
to the standard account_asset module from Odoo.

View File

@@ -0,0 +1,41 @@
## 14.0.1.0.0 (2021-01-08)
> - \[BREAKING\] Removed all functionality associated with
> account.fiscal.year
## 13.0.3.0.0 (2021-07-06)
- Allow to reverse the posting of a depreciation line instead of
deleting the journal entry.
## 13.0.2.0.0 (2021-02-19)
- Add support for multi-company
## 13.0.1.0.0 (2019-10-21)
- Python code and views were adapted to be compatible with v13.
- When assets are created through accounting journal items, they are
created when the journal items is posted.
- When a Bill Invoice is created or modified, at the time it is saved,
for each line that has an Asset profile and Quantity 'N' greater than
1, it will be replaced by 'N' lines identical to it but with
quantity 1. This was done to maintain the same behavior as in the
previous version, in which for each asset created there is a Journal
Item. In addition, this solution does not change the data model which
does not cause migration scripts.
- The configuration option was removed so the only function of that is
to allow the module to be uninstalled by unchecking that configuration
option.
- Tests were adapted.
## 12.0.2.1.0 (2019-10-21)
- \[IMP\] Add option to calculate depreciation table by days
## 12.0.1.0.0 (2019-01-13)
- \[BREAKING\] account.asset: parent_path has replaced parent_left &
parent_right (TODO: migration script)
- \[BREAKING\] account.asset.recompute.trigger: depends on date_range.py
(TODO: re-implement in account_fiscal_year.py)

View File

@@ -0,0 +1 @@
The module in NOT compatible with the standard account_asset module.

View File

@@ -0,0 +1 @@
from . import account_asset_report_xls

View File

@@ -0,0 +1,730 @@
# Copyright 2009-2019 Noviat
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import _, models
from odoo.exceptions import UserError
from odoo.addons.report_xlsx_helper.report.report_xlsx_format import (
FORMATS,
XLS_HEADERS,
)
_logger = logging.getLogger(__name__)
IR_TRANSLATION_NAME = "account.asset.report"
class AssetReportXlsx(models.AbstractModel):
_name = "report.account_asset_management.asset_report_xls"
_description = "Dynamic XLS asset report generator"
_inherit = "report.report_xlsx.abstract"
def _get_ws_params(self, wb, data, wiz):
self._get_assets(wiz, data)
s1 = self._get_acquisition_ws_params(wb, data, wiz)
s2 = self._get_active_ws_params(wb, data, wiz)
s3 = self._get_removal_ws_params(wb, data, wiz)
return [s1, s2, s3]
def _get_asset_template(self):
asset_template = {
"account": {
"header": {"type": "string", "value": _("Account")},
"asset": {
"type": "string",
"value": self._render(
"asset.profile_id.account_asset_id.code or ''"
),
},
"totals": {"type": "string", "value": _("Totals")},
"width": 20,
},
"name": {
"header": {"type": "string", "value": _("Name")},
"asset_group": {
"type": "string",
"value": self._render("group.name or ''"),
},
"asset": {"type": "string", "value": self._render("asset.name")},
"width": 40,
},
"code": {
"header": {"type": "string", "value": _("Reference")},
"asset_group": {
"type": "string",
"value": self._render("group.code or ''"),
},
"asset": {"type": "string", "value": self._render("asset.code or ''")},
"width": 20,
},
"date_start": {
"header": {"type": "string", "value": _("Asset Start Date")},
"asset": {
"value": self._render("asset.date_start or ''"),
"format": FORMATS["format_tcell_date_left"],
},
"width": 20,
},
"date_remove": {
"header": {"type": "string", "value": _("Asset Removal Date")},
"asset": {
"value": self._render("asset.date_remove or ''"),
"format": FORMATS["format_tcell_date_left"],
},
"width": 20,
},
"depreciation_base": {
"header": {
"type": "string",
"value": _("Depreciation Base"),
"format": FORMATS["format_theader_yellow_right"],
},
"asset_group": {
"type": "number",
"value": self._render('group_entry["_depreciation_base"]'),
"format": FORMATS["format_theader_blue_amount_right"],
},
"asset": {
"type": "number",
"value": self._render("asset.depreciation_base"),
"format": FORMATS["format_tcell_amount_right"],
},
"totals": {
"type": "formula",
"value": self._render("asset_total_formula"),
"format": FORMATS["format_theader_yellow_amount_right"],
},
"width": 18,
},
"salvage_value": {
"header": {
"type": "string",
"value": _("Salvage Value"),
"format": FORMATS["format_theader_yellow_right"],
},
"asset_group": {
"type": "number",
"value": self._render('group_entry["_salvage_value"]'),
"format": FORMATS["format_theader_blue_amount_right"],
},
"asset": {
"type": "number",
"value": self._render("asset.salvage_value"),
"format": FORMATS["format_tcell_amount_right"],
},
"totals": {
"type": "formula",
"value": self._render("salvage_total_formula"),
"format": FORMATS["format_theader_yellow_amount_right"],
},
"width": 18,
},
"purchase_value": {
"header": {
"type": "string",
"value": _("Purchase Value"),
"format": FORMATS["format_theader_yellow_right"],
},
"asset_group": {
"type": "number",
"value": self._render('group_entry["_purchase_value"]'),
"format": FORMATS["format_theader_blue_amount_right"],
},
"asset": {
"type": "number",
"value": self._render("asset.purchase_value"),
"format": FORMATS["format_tcell_amount_right"],
},
"totals": {
"type": "formula",
"value": self._render("purchase_total_formula"),
"format": FORMATS["format_theader_yellow_amount_right"],
},
"width": 18,
},
"period_start_value": {
"header": {
"type": "string",
"value": _("Period Start Value"),
"format": FORMATS["format_theader_yellow_right"],
},
"asset_group": {
"type": "number",
"value": self._render('group_entry["_period_start_value"]'),
"format": FORMATS["format_theader_blue_amount_right"],
},
"asset": {
"type": "number",
"value": self._render('asset_entry["_period_start_value"]'),
"format": FORMATS["format_tcell_amount_right"],
},
"totals": {
"type": "formula",
"value": self._render("period_start_total_formula"),
"format": FORMATS["format_theader_yellow_amount_right"],
},
"width": 18,
},
"period_depr": {
"header": {
"type": "string",
"value": _("Period Depreciation"),
"format": FORMATS["format_theader_yellow_right"],
},
"asset_group": {
"type": "formula",
"value": self._render("period_diff_formula"),
"format": FORMATS["format_theader_blue_amount_right"],
},
"asset": {
"type": "formula",
"value": self._render("period_diff_formula"),
"format": FORMATS["format_tcell_amount_right"],
},
"totals": {
"type": "formula",
"value": self._render("period_diff_formula"),
"format": FORMATS["format_theader_yellow_amount_right"],
},
"width": 18,
},
"period_end_value": {
"header": {
"type": "string",
"value": _("Period End Value"),
"format": FORMATS["format_theader_yellow_right"],
},
"asset_group": {
"type": "number",
"value": self._render('group_entry["_period_end_value"]'),
"format": FORMATS["format_theader_blue_amount_right"],
},
"asset": {
"type": "number",
"value": self._render('asset_entry["_period_end_value"]'),
"format": FORMATS["format_tcell_amount_right"],
},
"totals": {
"type": "formula",
"value": self._render("period_end_total_formula"),
"format": FORMATS["format_theader_yellow_amount_right"],
},
"width": 18,
},
"period_end_depr": {
"header": {
"type": "string",
"value": _("Tot. Depreciation"),
"format": FORMATS["format_theader_yellow_right"],
},
"asset_group": {
"type": "formula",
"value": self._render("total_depr_formula"),
"format": FORMATS["format_theader_blue_amount_right"],
},
"asset": {
"type": "formula",
"value": self._render("total_depr_formula"),
"format": FORMATS["format_tcell_amount_right"],
},
"totals": {
"type": "formula",
"value": self._render("total_depr_formula"),
"format": FORMATS["format_theader_yellow_amount_right"],
},
"width": 18,
},
"method": {
"header": {
"type": "string",
"value": _("Comput. Method"),
"format": FORMATS["format_theader_yellow_center"],
},
"asset": {
"type": "string",
"value": self._render("asset.method or ''"),
"format": FORMATS["format_tcell_center"],
},
"width": 20,
},
"method_number": {
"header": {
"type": "string",
"value": _("Number of Years"),
"format": FORMATS["format_theader_yellow_center"],
},
"asset": {
"type": "number",
"value": self._render("asset.method_number"),
"format": FORMATS["format_tcell_integer_center"],
},
"width": 20,
},
"prorata": {
"header": {
"type": "string",
"value": _("Prorata Temporis"),
"format": FORMATS["format_theader_yellow_center"],
},
"asset": {
"type": "boolean",
"value": self._render("asset.prorata"),
"format": FORMATS["format_tcell_center"],
},
"width": 20,
},
"state": {
"header": {
"type": "string",
"value": _("Status"),
"format": FORMATS["format_theader_yellow_center"],
},
"asset": {
"type": "string",
"value": self._render("asset.state"),
"format": FORMATS["format_tcell_center"],
},
"width": 8,
},
}
asset_template.update(self.env["account.asset"]._xls_asset_template())
return asset_template
def _get_acquisition_ws_params(self, wb, data, wiz):
acquisition_template = self._get_asset_template()
acquisition_template.update(
self.env["account.asset"]._xls_acquisition_template()
)
wl_acq = self.env["account.asset"]._xls_acquisition_fields()
title = self._get_title(wiz, "acquisition", frmt="normal")
title_short = self._get_title(wiz, "acquisition", frmt="short")
sheet_name = title_short[:31].replace("/", "-")
return {
"ws_name": sheet_name,
"generate_ws_method": "_asset_report",
"title": title,
"wanted_list": wl_acq,
"col_specs": acquisition_template,
"report_type": "acquisition",
}
def _get_active_ws_params(self, wb, data, wiz):
active_template = self._get_asset_template()
active_template.update(self.env["account.asset"]._xls_active_template())
wl_act = self.env["account.asset"]._xls_active_fields()
title = self._get_title(wiz, "active", frmt="normal")
title_short = self._get_title(wiz, "active", frmt="short")
sheet_name = title_short[:31].replace("/", "-")
return {
"ws_name": sheet_name,
"generate_ws_method": "_asset_report",
"title": title,
"wanted_list": wl_act,
"col_specs": active_template,
"report_type": "active",
}
def _get_removal_ws_params(self, wb, data, wiz):
removal_template = self._get_asset_template()
removal_template.update(self.env["account.asset"]._xls_removal_template())
wl_dsp = self.env["account.asset"]._xls_removal_fields()
title = self._get_title(wiz, "removal", frmt="normal")
title_short = self._get_title(wiz, "removal", frmt="short")
sheet_name = title_short[:31].replace("/", "-")
return {
"ws_name": sheet_name,
"generate_ws_method": "_asset_report",
"title": title,
"wanted_list": wl_dsp,
"col_specs": removal_template,
"report_type": "removal",
}
def _get_title(self, wiz, report, frmt="normal"):
prefix = f"{wiz.date_from} - {wiz.date_to}"
if report == "acquisition":
if frmt == "normal":
title = prefix + " : " + _("New Acquisitions")
else:
title = "ACQ"
elif report == "active":
if frmt == "normal":
title = prefix + " : " + _("Active Assets")
else:
title = "ACT"
else:
if frmt == "normal":
title = prefix + " : " + _("Removed Assets")
else:
title = "DSP"
return title
def _report_title(self, ws, row_pos, ws_params, data, wiz):
return self._write_ws_title(ws, row_pos, ws_params)
def _empty_report(self, ws, row_pos, ws_params, data, wiz):
report = ws_params["report_type"]
if report == "acquisition":
suffix = _("New Acquisitions")
elif report == "active":
suffix = _("Active Assets")
else:
suffix = _("Removed Assets")
no_entries = _("No") + " " + suffix
ws.write_string(row_pos, 0, no_entries, FORMATS["format_left_bold"])
def _get_assets(self, wiz, data):
"""Add the selected assets, both grouped and ungrouped, to `data`"""
dom = [
("date_start", "<=", wiz.date_to),
"|",
("date_remove", "=", False),
("date_remove", ">=", wiz.date_from),
]
parent_group = wiz.asset_group_id
if parent_group:
def _child_get(parent):
groups = [parent]
children = parent.child_ids
children = children.sorted(lambda r: r.code or r.name)
for child in children:
if child in groups:
raise UserError(
_(
"Inconsistent reporting structure."
"\nPlease correct Asset Group '{group}' (id {id})"
).format(group=child.name, id=child.id)
)
groups.extend(_child_get(child))
return groups
groups = _child_get(parent_group)
dom.append(("group_ids", "in", [x.id for x in groups]))
if not wiz.draft:
dom.append(("state", "!=", "draft"))
assets = self.env["account.asset"].search(dom)
grouped_assets = {}
self._group_assets(assets, parent_group, grouped_assets)
data.update(
{
"assets": assets,
"grouped_assets": grouped_assets,
}
)
@staticmethod
def acquisition_filter(wiz, asset):
return asset.date_start >= wiz.date_from
@staticmethod
def active_filter(wiz, asset):
return True
@staticmethod
def removal_filter(wiz, asset):
return (
asset.date_remove
and asset.date_remove >= wiz.date_from
and asset.date_remove <= wiz.date_to
)
def _group_assets(self, assets, group, grouped_assets):
if group:
group_assets = assets.filtered(lambda r: group in r.group_ids)
else:
group_assets = assets
group_assets = group_assets.sorted(
lambda r: (r.date_start or "", r.code or "", r.name)
)
grouped_assets[group] = {"assets": group_assets}
for child in group.child_ids:
self._group_assets(assets, child, grouped_assets[group])
def _create_report_entries(
self, ws_params, wiz, entries, group, group_val, error_dict
):
report = ws_params["report_type"]
def asset_filter(asset):
filt = getattr(self, f"{report}_filter")
return filt(wiz, asset)
def _has_assets(group, group_val):
assets = group_val.get("assets")
assets = assets.filtered(asset_filter)
if assets:
return True
for child in group.child_ids:
if _has_assets(child, group_val[child]):
return True
return False
assets = group_val.get("assets")
assets = assets.filtered(asset_filter)
# remove empty entries
if not _has_assets(group, group_val):
return
asset_entries = []
group_entry = {
"_purchase_value": 0.0,
"_depreciation_base": 0.0,
"_salvage_value": 0.0,
"_period_start_value": 0.0,
"_period_end_value": 0.0,
"group": group,
}
for asset in assets:
asset_entry = {"asset": asset}
group_entry["_purchase_value"] += asset.purchase_value
group_entry["_depreciation_base"] += asset.depreciation_base
group_entry["_salvage_value"] += asset.salvage_value
dls_all = asset.depreciation_line_ids.filtered(
lambda r: r.type == "depreciate"
)
dls_all = dls_all.sorted(key=lambda r: r.line_date)
if not dls_all:
error_dict["no_table"] += asset
# period_start_value
dls = dls_all.filtered(lambda r: r.line_date <= wiz.date_from)
if dls:
value_depreciated = dls[-1].depreciated_value + dls[-1].amount
else:
value_depreciated = 0.0
asset_entry["_period_start_value"] = (
asset.depreciation_base - value_depreciated
)
group_entry["_period_start_value"] += asset_entry["_period_start_value"]
# period_end_value
dls = dls_all.filtered(lambda r: r.line_date <= wiz.date_to)
if dls:
value_depreciated = dls[-1].depreciated_value + dls[-1].amount
else:
value_depreciated = 0.0
asset_entry["_period_end_value"] = (
asset.depreciation_base - value_depreciated
)
group_entry["_period_end_value"] += asset_entry["_period_end_value"]
asset_entries.append(asset_entry)
todos = []
for g in group.child_ids:
if _has_assets(g, group_val[g]):
todos.append(g)
entries.append(group_entry)
entries.extend(asset_entries)
for todo in todos:
self._create_report_entries(
ws_params, wiz, entries, todo, group_val[todo], error_dict
)
def _asset_report(self, workbook, ws, ws_params, data, wiz):
report = ws_params["report_type"]
ws.set_portrait()
ws.fit_to_pages(1, 0)
ws.set_header(XLS_HEADERS["xls_headers"]["standard"])
ws.set_footer(XLS_HEADERS["xls_footers"]["standard"])
wl = ws_params["wanted_list"]
if "account" not in wl:
raise UserError(
_(
"The 'account' field is a mandatory entry of the "
"'_xls_%s_fields' list !"
)
% report
)
self._set_column_width(ws, ws_params)
row_pos = 0
row_pos = self._report_title(ws, row_pos, ws_params, data, wiz)
def asset_filter(asset):
filt = getattr(self, f"{report}_filter")
return filt(wiz, asset)
assets = data["assets"].filtered(asset_filter)
if not assets:
return self._empty_report(ws, row_pos, ws_params, data, wiz)
row_pos = self._write_line(
ws,
row_pos,
ws_params,
col_specs_section="header",
default_format=FORMATS["format_theader_yellow_left"],
)
ws.freeze_panes(row_pos, 0)
row_pos_start = row_pos
purchase_value_pos = "purchase_value" in wl and wl.index("purchase_value")
depreciation_base_pos = "depreciation_base" in wl and wl.index(
"depreciation_base"
)
salvage_value_pos = "salvage_value" in wl and wl.index("salvage_value")
period_start_value_pos = "period_start_value" in wl and wl.index(
"period_start_value"
)
period_end_value_pos = "period_end_value" in wl and wl.index("period_end_value")
entries = []
root = wiz.asset_group_id
root_val = data["grouped_assets"][root]
error_dict = {
"no_table": self.env["account.asset"],
"dups": self.env["account.asset"],
}
self._create_report_entries(ws_params, wiz, entries, root, root_val, error_dict)
# traverse entries in reverse order to calc totals
for i, entry in enumerate(reversed(entries)):
if "group" in entry:
parent = entry["group"].parent_id
for parent_entry in reversed(entries[: -i - 1]):
if "group" in parent_entry and parent_entry["group"] == parent:
parent_entry["_purchase_value"] += entry["_purchase_value"]
parent_entry["_depreciation_base"] += entry[
"_depreciation_base"
]
parent_entry["_salvage_value"] += entry["_salvage_value"]
parent_entry["_period_start_value"] += entry[
"_period_start_value"
]
parent_entry["_period_end_value"] += entry["_period_end_value"]
continue
processed = []
for entry in entries:
period_start_value_cell = period_start_value_pos and self._rowcol_to_cell(
row_pos, period_start_value_pos
)
period_end_value_cell = period_end_value_pos and self._rowcol_to_cell(
row_pos, period_end_value_pos
)
depreciation_base_cell = depreciation_base_pos and self._rowcol_to_cell(
row_pos, depreciation_base_pos
)
period_diff_formula = period_end_value_cell and (
period_start_value_cell + "-" + period_end_value_cell
)
total_depr_formula = period_end_value_cell and (
depreciation_base_cell + "-" + period_end_value_cell
)
if "group" in entry:
row_pos = self._write_line(
ws,
row_pos,
ws_params,
col_specs_section="asset_group",
render_space={
"group": entry["group"],
"group_entry": entry,
"period_diff_formula": period_diff_formula,
"total_depr_formula": total_depr_formula,
},
default_format=FORMATS["format_theader_blue_left"],
)
else:
asset = entry["asset"]
if asset in processed:
error_dict["dups"] += asset
continue
else:
processed.append(asset)
row_pos = self._write_line(
ws,
row_pos,
ws_params,
col_specs_section="asset",
render_space={
"asset": entry["asset"],
"asset_entry": entry,
"period_diff_formula": period_diff_formula,
"total_depr_formula": total_depr_formula,
},
default_format=FORMATS["format_tcell_left"],
)
purchase_total_formula = purchase_value_pos and self._rowcol_to_cell(
row_pos_start, purchase_value_pos
)
asset_total_formula = depreciation_base_pos and self._rowcol_to_cell(
row_pos_start, depreciation_base_pos
)
salvage_total_formula = salvage_value_pos and self._rowcol_to_cell(
row_pos_start, salvage_value_pos
)
period_start_total_formula = period_start_value_pos and self._rowcol_to_cell(
row_pos_start, period_start_value_pos
)
period_end_total_formula = period_end_value_pos and self._rowcol_to_cell(
row_pos_start, period_end_value_pos
)
period_start_value_cell = period_start_value_pos and self._rowcol_to_cell(
row_pos, period_start_value_pos
)
period_end_value_cell = period_end_value_pos and self._rowcol_to_cell(
row_pos, period_end_value_pos
)
depreciation_base_cell = depreciation_base_pos and self._rowcol_to_cell(
row_pos, depreciation_base_pos
)
period_diff_formula = period_end_value_cell and (
period_start_value_cell + "-" + period_end_value_cell
)
total_depr_formula = period_end_value_cell and (
depreciation_base_cell + "-" + period_end_value_cell
)
row_pos = self._write_line(
ws,
row_pos,
ws_params,
col_specs_section="totals",
render_space={
"purchase_total_formula": purchase_total_formula,
"asset_total_formula": asset_total_formula,
"salvage_total_formula": salvage_total_formula,
"period_start_total_formula": period_start_total_formula,
"period_end_total_formula": period_end_total_formula,
"period_diff_formula": period_diff_formula,
"total_depr_formula": total_depr_formula,
},
default_format=FORMATS["format_theader_yellow_left"],
)
for k in error_dict:
if error_dict[k]:
if k == "no_table":
reason = _("Missing depreciation table")
elif k == "dups":
reason = _("Duplicate reporting entries")
else:
reason = _("Undetermined error")
row_pos += 1
err_msg = _("Assets to be corrected") + ": "
err_msg += "%s" % [
x[1] for x in [(error_dict[k].id, error_dict[k].display_name)]
]
err_msg += " - " + _("Reason") + ": " + reason
ws.write_string(row_pos, 0, err_msg, FORMATS["format_left_bold"])

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="account_asset_profile_multi_company_rule" model="ir.rule">
<field name="name">Account Asset Profile multi-company</field>
<field ref="model_account_asset_profile" name="model_id" />
<field eval="True" name="global" />
<field
name="domain_force"
>['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
<record id="account_asset_multi_company_rule" model="ir.rule">
<field name="name">Account Asset multi-company</field>
<field ref="model_account_asset" name="model_id" />
<field eval="True" name="global" />
<field
name="domain_force"
>['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
<record id="account_asset_group_multi_company_rule" model="ir.rule">
<field name="name">Account Asset Group multi-company</field>
<field ref="model_account_asset_group" name="model_id" />
<field eval="True" name="global" />
<field
name="domain_force"
>['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
</odoo>

View File

@@ -0,0 +1,19 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_asset_profile_invoice,account.asset.profile,model_account_asset_profile,account.group_account_invoice,1,0,0,0
access_account_asset_profile_user,account.asset.profile,model_account_asset_profile,account.group_account_user,1,0,0,0
access_account_asset_profile_manager,account.asset.profile,model_account_asset_profile,account.group_account_manager,1,1,1,1
access_account_asset_invoice,account.asset,model_account_asset,account.group_account_invoice,1,1,1,1
access_account_asset_user,account.asset,model_account_asset,account.group_account_user,1,1,1,1
access_account_asset_manager,account.asset,model_account_asset,account.group_account_manager,1,1,1,1
access_account_asset_line_invoice,account.asset.line,model_account_asset_line,account.group_account_invoice,1,1,1,1
access_account_asset_line_user,account.asset.line,model_account_asset_line,account.group_account_user,1,1,1,1
access_account_asset_line_manager,account.asset.line,model_account_asset_line,account.group_account_manager,1,1,1,1
access_account_asset_recompute_trigger_user,account.asset.recompute.trigger,model_account_asset_recompute_trigger,account.group_account_user,1,1,1,1
access_account_asset_recompute_trigger_manager,account.asset.recompute.trigger,model_account_asset_recompute_trigger,account.group_account_manager,1,1,1,1
access_account_asset_group_invoice,account.asset.group,model_account_asset_group,account.group_account_invoice,1,0,0,0
access_account_asset_group_user,account.asset.group,model_account_asset_group,account.group_account_user,1,0,0,0
access_account_asset_group_manager,account.asset.group,model_account_asset_group,account.group_account_manager,1,1,1,1
access_account_asset_remove_user,account.asset.remove,model_account_asset_remove,account.group_account_user,1,1,1,1
access_account_asset_compute_user,account.asset.compute,model_account_asset_compute,account.group_account_user,1,1,1,1
access_wiz_account_asset_report,wiz.account.asset.report,model_wiz_account_asset_report,account.group_account_readonly,1,1,1,0
access_wiz_asset_move_reverse_user,wiz.asset.move.reverse,model_wiz_asset_move_reverse,account.group_account_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_asset_profile_invoice account.asset.profile model_account_asset_profile account.group_account_invoice 1 0 0 0
3 access_account_asset_profile_user account.asset.profile model_account_asset_profile account.group_account_user 1 0 0 0
4 access_account_asset_profile_manager account.asset.profile model_account_asset_profile account.group_account_manager 1 1 1 1
5 access_account_asset_invoice account.asset model_account_asset account.group_account_invoice 1 1 1 1
6 access_account_asset_user account.asset model_account_asset account.group_account_user 1 1 1 1
7 access_account_asset_manager account.asset model_account_asset account.group_account_manager 1 1 1 1
8 access_account_asset_line_invoice account.asset.line model_account_asset_line account.group_account_invoice 1 1 1 1
9 access_account_asset_line_user account.asset.line model_account_asset_line account.group_account_user 1 1 1 1
10 access_account_asset_line_manager account.asset.line model_account_asset_line account.group_account_manager 1 1 1 1
11 access_account_asset_recompute_trigger_user account.asset.recompute.trigger model_account_asset_recompute_trigger account.group_account_user 1 1 1 1
12 access_account_asset_recompute_trigger_manager account.asset.recompute.trigger model_account_asset_recompute_trigger account.group_account_manager 1 1 1 1
13 access_account_asset_group_invoice account.asset.group model_account_asset_group account.group_account_invoice 1 0 0 0
14 access_account_asset_group_user account.asset.group model_account_asset_group account.group_account_user 1 0 0 0
15 access_account_asset_group_manager account.asset.group model_account_asset_group account.group_account_manager 1 1 1 1
16 access_account_asset_remove_user account.asset.remove model_account_asset_remove account.group_account_user 1 1 1 1
17 access_account_asset_compute_user account.asset.compute model_account_asset_compute account.group_account_user 1 1 1 1
18 access_wiz_account_asset_report wiz.account.asset.report model_wiz_account_asset_report account.group_account_readonly 1 1 1 0
19 access_wiz_asset_move_reverse_user wiz.asset.move.reverse model_wiz_asset_move_reverse account.group_account_user 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,535 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Assets Management</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="assets-management">
<h1 class="title">Assets Management</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:843676bebe20651c6131f76d504144b859ebaca41703b5b8a63f97a7b4998070
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Mature" src="https://img.shields.io/badge/maturity-Mature-brightgreen.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/account-financial-tools/tree/17.0/account_asset_management"><img alt="OCA/account-financial-tools" src="https://img.shields.io/badge/github-OCA%2Faccount--financial--tools-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/account-financial-tools-17-0/account-financial-tools-17-0-account_asset_management"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/account-financial-tools&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This Module manages the assets owned by a company. It will keep track of
depreciations occurred on those assets. And it allows to create
accounting entries from the depreciation lines.</p>
<p>The full asset life-cycle is managed (from asset creation to asset
removal).</p>
<p>Assets can be created manually as well as automatically (via the
creation of an accounting entry on the asset account).</p>
<p>Depreciation Journal Entries can be created manually in the “Deprecation
Board” tab, or automatically by two ways:</p>
<ul class="simple">
<li>Using the “Invoicing/Assets/Compute Assets” wizard.</li>
<li>Activating the “Asset Management: Generate assets” cron.</li>
</ul>
<p>These options are compatibles each other.</p>
<p>The module contains a large number of functional enhancements compared
to the standard account_asset module from Odoo.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
<li><a class="reference internal" href="#changelog" id="toc-entry-2">Changelog</a><ul>
<li><a class="reference internal" href="#section-1" id="toc-entry-3">14.0.1.0.0 (2021-01-08)</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-4">13.0.3.0.0 (2021-07-06)</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-5">13.0.2.0.0 (2021-02-19)</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-6">13.0.1.0.0 (2019-10-21)</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-7">12.0.2.1.0 (2019-10-21)</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-8">12.0.1.0.0 (2019-01-13)</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-9">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-10">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-11">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-12">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-13">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<p>The module in NOT compatible with the standard account_asset module.</p>
</div>
<div class="section" id="changelog">
<h1><a class="toc-backref" href="#toc-entry-2">Changelog</a></h1>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-3">14.0.1.0.0 (2021-01-08)</a></h2>
<blockquote>
<ul class="simple">
<li>[BREAKING] Removed all functionality associated with
account.fiscal.year</li>
</ul>
</blockquote>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-4">13.0.3.0.0 (2021-07-06)</a></h2>
<ul class="simple">
<li>Allow to reverse the posting of a depreciation line instead of
deleting the journal entry.</li>
</ul>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-5">13.0.2.0.0 (2021-02-19)</a></h2>
<ul class="simple">
<li>Add support for multi-company</li>
</ul>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-6">13.0.1.0.0 (2019-10-21)</a></h2>
<ul class="simple">
<li>Python code and views were adapted to be compatible with v13.</li>
<li>When assets are created through accounting journal items, they are
created when the journal items is posted.</li>
<li>When a Bill Invoice is created or modified, at the time it is saved,
for each line that has an Asset profile and Quantity N greater than
1, it will be replaced by N lines identical to it but with quantity
1. This was done to maintain the same behavior as in the previous
version, in which for each asset created there is a Journal Item. In
addition, this solution does not change the data model which does not
cause migration scripts.</li>
<li>The configuration option was removed so the only function of that is
to allow the module to be uninstalled by unchecking that
configuration option.</li>
<li>Tests were adapted.</li>
</ul>
</div>
<div class="section" id="section-5">
<h2><a class="toc-backref" href="#toc-entry-7">12.0.2.1.0 (2019-10-21)</a></h2>
<ul class="simple">
<li>[IMP] Add option to calculate depreciation table by days</li>
</ul>
</div>
<div class="section" id="section-6">
<h2><a class="toc-backref" href="#toc-entry-8">12.0.1.0.0 (2019-01-13)</a></h2>
<ul class="simple">
<li>[BREAKING] account.asset: parent_path has replaced parent_left &amp;
parent_right (TODO: migration script)</li>
<li>[BREAKING] account.asset.recompute.trigger: depends on date_range.py
(TODO: re-implement in account_fiscal_year.py)</li>
</ul>
</div>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-9">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/account-financial-tools/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/account-financial-tools/issues/new?body=module:%20account_asset_management%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-10">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-11">Authors</a></h2>
<ul class="simple">
<li>Noviat</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-12">Contributors</a></h2>
<ul class="simple">
<li>OpenERP SA</li>
<li>Luc De Meyer (Noviat)</li>
<li>Frédéric Clementi (camptocamp)</li>
<li>Florian Dacosta (Akretion)</li>
<li>Stéphane Bidoul (Acsone)</li>
<li>Adrien Peiffer (Acsone)</li>
<li>Akim Juillerat &lt;<a class="reference external" href="mailto:akim.juillerat&#64;camptocamp.com">akim.juillerat&#64;camptocamp.com</a>&gt;</li>
<li>Henrik Norlin (Apps2GROW)</li>
<li>Maxence Groine &lt;<a class="reference external" href="mailto:mgroine&#64;fiefmanage.ch">mgroine&#64;fiefmanage.ch</a>&gt;</li>
<li>Kitti Upariphutthiphong &lt;<a class="reference external" href="mailto:kittiu&#64;ecosoft.co.th">kittiu&#64;ecosoft.co.th</a>&gt;</li>
<li>Saran Lim. &lt;<a class="reference external" href="mailto:saranl&#64;ecosoft.co.th">saranl&#64;ecosoft.co.th</a>&gt;</li>
<li><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:<ul>
<li>Ernesto Tejeda</li>
<li>Pedro M. Baeza</li>
<li>João Marques</li>
<li>Víctor Martínez</li>
</ul>
</li>
<li><a class="reference external" href="https://www.forgeflow.com">ForgeFlow</a>:<ul>
<li>Jordi Ballester &lt;<a class="reference external" href="mailto:jordi.ballester&#64;forgeflow.com">jordi.ballester&#64;forgeflow.com</a>&gt;</li>
<li>Miquel Raïch &lt;<a class="reference external" href="mailto:miquel.raich&#64;forgeflow.com">miquel.raich&#64;forgeflow.com</a>&gt;</li>
</ul>
</li>
<li><a class="reference external" href="https://www.sygel.es">Sygel</a>:<ul>
<li>Manuel Regidor &lt;<a class="reference external" href="mailto:manuel.regidor&#64;sygel.es">manuel.regidor&#64;sygel.es</a>&gt;</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-13">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/account-financial-tools/tree/17.0/account_asset_management">OCA/account-financial-tools</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,2 @@
from . import test_account_asset_management
from . import test_asset_management_xls

Some files were not shown because too many files have changed in this diff Show More