Files
Pedro M. Baeza aa30b2173e [FIX] account_asset_management: Allow to unlink w/o billing permission
Some users may be allowed to unlink invoices without billing access,
but with current code, they are not able, as the asset line check is
done without sudo, and other users except billing or higher ones
aren't allowed to access to that model.

TT49673
2024-06-19 12:02:40 +02:00

272 lines
9.9 KiB
Python

# 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"]
.sudo()
.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 _post(self, soft=True):
ret_val = super()._post(soft=soft)
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 = [
"<a href=# data-oe-model=account.asset data-oe-id=%s>%s</a>"
% 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,
copy=False,
)
@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.quantity > 1.0 and self.asset_profile_id.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),
# Make sure the price is not changed, like with account_invoice_pricelist
"price_unit": self.price_unit,
}
)
for i in range(1, int(qty)):
aml.copy({"name": "{} {}".format(name, i + 1)})