# 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 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 ): vals = move._prepare_asset_vals(aml) if not aml.name: raise UserError( _("Asset name must be set in the label of the line.") ) if aml.asset_id: continue 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 refs = [ "%s" % tuple(name_get) for name_get in move.line_ids.filtered( "asset_profile_id" ).asset_id.name_get() ] if refs: message = _("This invoice created the asset(s): %s") % ", ".join(refs) 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 and self.quantity > 1.0: profile = self.asset_profile_id if profile.asset_product_item: aml = self.with_context(check_move_validity=False) qty = self.quantity name = self.name aml.write({"quantity": 1, "name": "{} {}".format(name, 1)}) for i in range(1, int(qty)): aml.copy({"name": "{} {}".format(name, i + 1)})