Skip to content

Commit ba241f3

Browse files
committed
[IMP] l10n_it_asset_management: Recharge asset
1 parent a85b5d2 commit ba241f3

7 files changed

+330
-14
lines changed

l10n_it_asset_management/models/account_move.py

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ def open_wizard_manage_asset(self):
107107
{
108108
"default_company_id": self.company_id.id,
109109
"default_dismiss_date": self.invoice_date or self.invoice_date_due,
110+
"default_recharge_date": self.invoice_date or self.invoice_date_due,
110111
"default_move_ids": [Command.set(self.ids)],
111112
"default_move_line_ids": [Command.set(lines.ids)],
112113
"default_purchase_date": self.invoice_date or self.invoice_date_due,

l10n_it_asset_management/models/asset_accounting_info.py

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class AssetAccountingInfo(models.Model):
5151
relation_type = fields.Selection(
5252
[
5353
("create", "Asset Creation"),
54+
("partial_recharge", "Partial Recharge"),
5455
("update", "Asset Update"),
5556
("partial_dismiss", "Asset Partial Dismissal"),
5657
("dismiss", "Asset Dismissal"),

l10n_it_asset_management/models/asset_depreciation_line.py

+10
Original file line numberDiff line numberDiff line change
@@ -495,3 +495,13 @@ def post_partial_dismiss_asset(self):
495495
)
496496
if to_create_move:
497497
to_create_move.generate_account_move()
498+
499+
def post_partial_recharge_asset(self):
500+
dep = self.mapped("depreciation_id")
501+
dep.ensure_one()
502+
types = ("depreciated", "gain", "loss")
503+
to_create_move = self.filtered(
504+
lambda line: line.needs_account_move() and line.move_type in types
505+
)
506+
if to_create_move:
507+
to_create_move.generate_account_move()

l10n_it_asset_management/tests/common.py

+67-12
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from datetime import date
77

88
from odoo.fields import Command, first
9-
from odoo.tests.common import TransactionCase, Form
9+
from odoo.tests.common import Form, TransactionCase
1010

1111

1212
class Common(TransactionCase):
@@ -65,6 +65,12 @@ def setUpClass(cls):
6565
],
6666
limit=1,
6767
)
68+
cls.sale_journal = cls.env["account.journal"].search(
69+
[
70+
("type", "=", "sale"),
71+
],
72+
limit=1,
73+
)
6874

6975
cls.civilistico_asset_dep_type = cls.env.ref(
7076
"l10n_it_asset_management.ad_type_civilistico"
@@ -208,6 +214,49 @@ def _create_purchase_invoice(self, invoice_date, tax_ids=False, amount=7000):
208214
self.assertEqual(purchase_invoice.state, "posted")
209215
return purchase_invoice
210216

217+
def _create_sale_invoice(self, asset, amount=7000, invoice_date=None, post=True):
218+
sale_invoice = self.env["account.move"].create(
219+
{
220+
"move_type": "out_invoice",
221+
"invoice_date": invoice_date,
222+
"partner_id": self.env.ref("base.partner_demo").id,
223+
"journal_id": self.sale_journal.id,
224+
"invoice_line_ids": [
225+
Command.create(
226+
{
227+
"account_id": asset.category_id.asset_account_id.id,
228+
"quantity": 1,
229+
"price_unit": amount,
230+
},
231+
)
232+
],
233+
}
234+
)
235+
if post:
236+
sale_invoice.action_post()
237+
return sale_invoice
238+
239+
def _refund_move(self, move, method="cancel", ref_date=None):
240+
reverse_context = {
241+
"active_model": move._name,
242+
"active_ids": move.ids,
243+
}
244+
refund_wizard_form = Form(
245+
self.env["account.move.reversal"].with_context(**reverse_context)
246+
)
247+
refund_wizard_form.reason = "test"
248+
if ref_date:
249+
refund_wizard_form.date_mode = "custom"
250+
refund_wizard_form.date = ref_date
251+
refund_wizard_form.refund_method = method
252+
refund_wizard = refund_wizard_form.save()
253+
254+
refund_action = refund_wizard.reverse_moves()
255+
refund_move = self.env[refund_action["res_model"]].browse(
256+
refund_action["res_id"]
257+
)
258+
return refund_move
259+
211260
def _civil_depreciate_asset(self, asset):
212261
# Keep only one civil depreciation
213262
civil_depreciation_type = self.env.ref(
@@ -298,15 +347,21 @@ def _create_entry(self, account, amount, post=True):
298347
self.assertEqual(entry.move_type, "entry")
299348
return entry
300349

301-
def _update_asset(self, entry, asset):
302-
"""Execute the wizard on `entry` to update `asset`."""
303-
wizard_action = entry.open_wizard_manage_asset()
304-
wizard_model = self.env[wizard_action["res_model"]]
305-
wizard_context = wizard_action["context"]
350+
def _link_asset_move(self, move, link_management_type, wiz_values=None):
351+
"""Link `move` to an asset with mode `link_management_type`.
352+
`wiz_values` are values to be set in the wizard.
353+
"""
354+
if wiz_values is None:
355+
wiz_values = {}
306356

307-
wizard_form = Form(wizard_model.with_context(**wizard_context))
308-
wizard_form.management_type = "update"
309-
wizard_form.asset_id = asset
310-
wizard = wizard_form.save()
311-
312-
return wizard.link_asset()
357+
wiz_action_values = move.open_wizard_manage_asset()
358+
wiz_form = Form(
359+
self.env["wizard.account.move.manage.asset"].with_context(
360+
**wiz_action_values["context"]
361+
)
362+
)
363+
wiz_form.management_type = link_management_type
364+
for field_name, field_value in wiz_values.items():
365+
setattr(wiz_form, field_name, field_value)
366+
wiz = wiz_form.save()
367+
return wiz.link_asset()

l10n_it_asset_management/tests/test_assets_management.py

+74-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Copyright 2023 Simone Rubino - Aion Tech
44
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
55

6+
import datetime
67
from datetime import date
78

89
from odoo import fields
@@ -456,7 +457,13 @@ def test_entry_in_update_asset(self):
456457
self.assertFalse(asset.asset_accounting_info_ids)
457458

458459
# Act
459-
self._update_asset(entry, asset)
460+
self._link_asset_move(
461+
entry,
462+
"update",
463+
wiz_values={
464+
"asset_id": asset,
465+
},
466+
)
460467

461468
# Assert
462469
accounting_info = asset.asset_accounting_info_ids
@@ -476,7 +483,13 @@ def test_entry_out_update_asset(self):
476483
self.assertFalse(asset.asset_accounting_info_ids)
477484

478485
# Act
479-
self._update_asset(entry, asset)
486+
self._link_asset_move(
487+
entry,
488+
"update",
489+
wiz_values={
490+
"asset_id": asset,
491+
},
492+
)
480493

481494
# Assert
482495
accounting_info = asset.asset_accounting_info_ids
@@ -508,3 +521,62 @@ def test_journal_prev_year(self):
508521
total = report.report_total_ids
509522
self.assertEqual(total.amount_depreciation_fund_curr_year, 1000)
510523
self.assertEqual(total.amount_depreciation_fund_prev_year, 1000)
524+
525+
def test_purchase_sale_refund_recharge(self):
526+
"""A sale refund can be used to restore asset value."""
527+
# Create with purchase
528+
purchase_amount = 2500
529+
purchase_invoice = self._create_purchase_invoice(
530+
datetime.date(2020, month=1, day=1), amount=purchase_amount
531+
)
532+
asset = self._link_asset_move(
533+
purchase_invoice,
534+
"create",
535+
{
536+
"category_id": self.asset_category_1,
537+
"name": "Test recharge asset",
538+
},
539+
)
540+
civ_depreciation = asset.depreciation_ids.filtered(
541+
lambda x: x.type_id
542+
== self.env.ref("l10n_it_asset_management.ad_type_civilistico")
543+
)
544+
self.assertEqual(civ_depreciation.amount_depreciable_updated, purchase_amount)
545+
546+
# Partial dismiss with sale
547+
asset_account_amount = asset_fund_amount = 1000
548+
sale_invoice = self._create_sale_invoice(
549+
asset, amount=8000, invoice_date=datetime.date(2020, month=3, day=1)
550+
)
551+
self._link_asset_move(
552+
sale_invoice,
553+
"partial_dismiss",
554+
wiz_values={
555+
"asset_id": asset,
556+
"depreciated_fund_amount": asset_account_amount,
557+
"asset_purchase_amount": asset_fund_amount,
558+
},
559+
)
560+
self.assertEqual(
561+
civ_depreciation.amount_depreciable_updated,
562+
purchase_amount - asset_account_amount,
563+
)
564+
565+
# Refund and recharge
566+
sale_refund = self._refund_move(
567+
sale_invoice, ref_date=datetime.date(2020, month=7, day=1)
568+
)
569+
recharge_purchase_amount = recharge_fund_amount = 1000
570+
self._link_asset_move(
571+
sale_refund,
572+
"partial_recharge",
573+
wiz_values={
574+
"asset_id": asset,
575+
"recharge_purchase_amount": recharge_purchase_amount,
576+
"recharge_fund_amount": recharge_fund_amount,
577+
},
578+
)
579+
self.assertEqual(
580+
civ_depreciation.amount_depreciable_updated,
581+
purchase_amount,
582+
)

l10n_it_asset_management/wizard/account_move_manage_asset.py

+139
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def get_default_move_ids(self):
6666
management_type = fields.Selection(
6767
[
6868
("create", "Create New"),
69+
("partial_recharge", "Partial Recharge"),
6970
("update", "Update Existing"),
7071
("partial_dismiss", "Partial Dismiss"),
7172
("dismiss", "Dismiss Asset"),
@@ -104,6 +105,12 @@ def get_default_move_ids(self):
104105

105106
used = fields.Boolean()
106107

108+
recharge_date = fields.Date(
109+
default=fields.Date.today(),
110+
)
111+
recharge_purchase_amount = fields.Monetary()
112+
recharge_fund_amount = fields.Monetary()
113+
107114
# Mapping between move journal type and depreciation line type
108115
_move_journal_type_2_dep_line_type = {
109116
"purchase": "in",
@@ -117,6 +124,7 @@ def get_default_move_ids(self):
117124
# Every method used in here must return an asset
118125
_management_type_2_method = {
119126
"create": lambda w: w.create_asset(),
127+
"partial_recharge": lambda w: w.partial_recharge_asset(),
120128
"dismiss": lambda w: w.dismiss_asset(),
121129
"partial_dismiss": lambda w: w.partial_dismiss_asset(),
122130
"update": lambda w: w.update_asset(),
@@ -735,3 +743,134 @@ def update_asset(self):
735743
self.check_pre_update_asset()
736744
self.asset_id.write(self.get_update_asset_vals())
737745
return self.asset_id
746+
747+
def check_pre_partial_recharge_asset(self):
748+
self.ensure_one()
749+
asset = self.asset_id
750+
if not asset:
751+
raise ValidationError(_("Please choose an asset before continuing!"))
752+
753+
move_lines = self.move_line_ids
754+
if not move_lines:
755+
raise ValidationError(
756+
_(
757+
"At least one move line is needed"
758+
" to partial recharge asset %(asset)s!",
759+
asset=asset,
760+
)
761+
)
762+
763+
asset_account = asset.category_id.asset_account_id
764+
if not all(line.account_id == asset_account for line in move_lines):
765+
raise ValidationError(
766+
_(
767+
"You need to choose move lines with account `%(ass_acc)s`"
768+
" if you need them to partial recharge asset `%(ass_name)s`!",
769+
ass_acc=asset_account.display_name,
770+
ass_name=asset.display_name,
771+
)
772+
)
773+
774+
def get_partial_recharge_asset_vals(self):
775+
self.ensure_one()
776+
asset = self.asset_id
777+
currency = self.asset_id.currency_id
778+
recharge_date = self.recharge_date
779+
digits = self.env["decimal.precision"].precision_get("Account")
780+
fund_amt = self.recharge_fund_amount
781+
purchase_amt = self.recharge_purchase_amount
782+
783+
move = self.move_line_ids.mapped("move_id")
784+
move_nums = move.name
785+
786+
writeoff = 0
787+
for line in self.move_line_ids:
788+
writeoff += line.currency_id._convert(
789+
line.credit - line.debit, currency, line.company_id, line.date
790+
)
791+
writeoff = round(writeoff, digits)
792+
793+
vals = {"depreciation_ids": []}
794+
for dep in asset.depreciation_ids:
795+
dep_writeoff = writeoff
796+
if dep.pro_rata_temporis:
797+
dep_writeoff *= dep.get_pro_rata_temporis_multiplier(
798+
recharge_date, "std"
799+
)
800+
801+
name = _(
802+
"Partial recharge from move(s) %(move_nums)s",
803+
move_nums=move_nums,
804+
)
805+
806+
out_line_vals = {
807+
"asset_accounting_info_ids": [
808+
Command.create(
809+
{
810+
"move_line_id": line.id,
811+
"relation_type": self.management_type,
812+
},
813+
)
814+
for line in self.move_line_ids
815+
],
816+
"amount": purchase_amt,
817+
"date": recharge_date,
818+
"move_type": "in",
819+
"name": name,
820+
}
821+
dep_line_vals = {
822+
"asset_accounting_info_ids": [
823+
Command.create(
824+
{
825+
"move_line_id": line.id,
826+
"relation_type": self.management_type,
827+
},
828+
)
829+
for line in self.move_line_ids
830+
],
831+
"amount": -fund_amt,
832+
"date": recharge_date,
833+
"move_type": "depreciated",
834+
"name": name,
835+
}
836+
837+
dep_vals = {
838+
"line_ids": [
839+
Command.create(out_line_vals),
840+
Command.create(dep_line_vals),
841+
]
842+
}
843+
844+
balance = purchase_amt + dep_writeoff - fund_amt
845+
if not float_is_zero(balance, digits):
846+
loss_gain_vals = {
847+
"asset_accounting_info_ids": [
848+
Command.create(
849+
{
850+
"move_line_id": line.id,
851+
"relation_type": self.management_type,
852+
},
853+
)
854+
for line in self.move_line_ids
855+
],
856+
"amount": abs(balance),
857+
"date": recharge_date,
858+
"move_type": "gain" if balance > 0 else "loss",
859+
"name": name,
860+
}
861+
dep_vals["line_ids"].append(Command.create(loss_gain_vals))
862+
863+
vals["depreciation_ids"].append(Command.update(dep.id, dep_vals))
864+
return vals
865+
866+
def partial_recharge_asset(self):
867+
"""Recharge asset partially and return it."""
868+
self.ensure_one()
869+
self.check_pre_partial_recharge_asset()
870+
old_dep_lines = self.asset_id.mapped("depreciation_ids.line_ids")
871+
self.asset_id.write(self.get_partial_recharge_asset_vals())
872+
873+
for dep in self.asset_id.depreciation_ids:
874+
(dep.line_ids - old_dep_lines).post_partial_dismiss_asset()
875+
876+
return self.asset_id

0 commit comments

Comments
 (0)