feat(fusion_accounting_assets): depreciation board line model
- period_index, scheduled_date, amount, accumulated, book_value_at_end - is_posted / posted_date / move_id (set when engine posts the entry) - action_post() marks the line as posted (idempotent) - UNIQUE(asset_id, period_index) constraint via models.Constraint - 5 new tests (52 total) Made-with: Cursor
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
'name': 'Fusion Accounting Assets',
|
'name': 'Fusion Accounting Assets',
|
||||||
'version': '19.0.1.0.6',
|
'version': '19.0.1.0.7',
|
||||||
'category': 'Accounting/Accounting',
|
'category': 'Accounting/Accounting',
|
||||||
'summary': 'AI-augmented asset management with depreciation schedules.',
|
'summary': 'AI-augmented asset management with depreciation schedules.',
|
||||||
'description': """
|
'description': """
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
"""Per-period depreciation board lines for an asset.
|
"""Per-period depreciation board lines for an asset."""
|
||||||
|
|
||||||
Stub created with Task 8 (so fusion.asset One2many resolves). Fully
|
|
||||||
elaborated in Task 9.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from odoo import fields, models
|
from odoo import fields, models
|
||||||
|
|
||||||
@@ -13,7 +9,34 @@ class FusionAssetDepreciationLine(models.Model):
|
|||||||
_order = "asset_id, scheduled_date"
|
_order = "asset_id, scheduled_date"
|
||||||
|
|
||||||
asset_id = fields.Many2one('fusion.asset', required=True, ondelete='cascade')
|
asset_id = fields.Many2one('fusion.asset', required=True, ondelete='cascade')
|
||||||
scheduled_date = fields.Date(required=True)
|
company_id = fields.Many2one(related='asset_id.company_id', store=True)
|
||||||
amount = fields.Monetary()
|
|
||||||
currency_id = fields.Many2one(related='asset_id.currency_id', store=True)
|
currency_id = fields.Many2one(related='asset_id.currency_id', store=True)
|
||||||
is_posted = fields.Boolean(default=False)
|
|
||||||
|
period_index = fields.Integer(required=True)
|
||||||
|
scheduled_date = fields.Date(required=True)
|
||||||
|
amount = fields.Monetary(required=True)
|
||||||
|
accumulated = fields.Monetary()
|
||||||
|
book_value_at_end = fields.Monetary()
|
||||||
|
|
||||||
|
is_posted = fields.Boolean(default=False, copy=False)
|
||||||
|
posted_date = fields.Date(copy=False)
|
||||||
|
move_id = fields.Many2one(
|
||||||
|
'account.move', copy=False,
|
||||||
|
help="Journal entry created when this line was posted.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def action_post(self):
|
||||||
|
"""Mark this line as posted (without creating the journal entry yet —
|
||||||
|
engine method post_depreciation_entry handles the actual entry creation)."""
|
||||||
|
for line in self:
|
||||||
|
if line.is_posted:
|
||||||
|
continue
|
||||||
|
line.write({
|
||||||
|
'is_posted': True,
|
||||||
|
'posted_date': fields.Date.today(),
|
||||||
|
})
|
||||||
|
|
||||||
|
_unique_period_per_asset = models.Constraint(
|
||||||
|
'UNIQUE(asset_id, period_index)',
|
||||||
|
'A depreciation line for that period already exists.',
|
||||||
|
)
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ from . import test_salvage_value
|
|||||||
from . import test_asset_anomaly_detection
|
from . import test_asset_anomaly_detection
|
||||||
from . import test_useful_life_predictor
|
from . import test_useful_life_predictor
|
||||||
from . import test_fusion_asset
|
from . import test_fusion_asset
|
||||||
|
from . import test_fusion_asset_depreciation_line
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from odoo.tests.common import TransactionCase
|
||||||
|
from odoo.tests import tagged
|
||||||
|
|
||||||
|
|
||||||
|
@tagged('post_install', '-at_install')
|
||||||
|
class TestFusionAssetDepreciationLine(TransactionCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.asset = self.env['fusion.asset'].create({
|
||||||
|
'name': 'Asset for Lines',
|
||||||
|
'cost': 12000,
|
||||||
|
'salvage_value': 0,
|
||||||
|
'acquisition_date': date(2026, 1, 1),
|
||||||
|
'method': 'straight_line',
|
||||||
|
'useful_life_years': 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
def _make_line(self, period_index, amount=1000.0, scheduled_date=None):
|
||||||
|
return self.env['fusion.asset.depreciation.line'].create({
|
||||||
|
'asset_id': self.asset.id,
|
||||||
|
'period_index': period_index,
|
||||||
|
'scheduled_date': scheduled_date or date(2026, period_index, 28),
|
||||||
|
'amount': amount,
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_create_line_defaults_unposted(self):
|
||||||
|
line = self._make_line(1)
|
||||||
|
self.assertFalse(line.is_posted)
|
||||||
|
self.assertFalse(line.posted_date)
|
||||||
|
self.assertFalse(line.move_id)
|
||||||
|
self.assertEqual(line.company_id, self.asset.company_id)
|
||||||
|
self.assertEqual(line.currency_id, self.asset.currency_id)
|
||||||
|
|
||||||
|
def test_action_post_marks_line_posted(self):
|
||||||
|
line = self._make_line(2)
|
||||||
|
line.action_post()
|
||||||
|
self.assertTrue(line.is_posted)
|
||||||
|
self.assertTrue(line.posted_date)
|
||||||
|
|
||||||
|
def test_action_post_idempotent_keeps_first_date(self):
|
||||||
|
line = self._make_line(3)
|
||||||
|
line.action_post()
|
||||||
|
first_date = line.posted_date
|
||||||
|
line.action_post()
|
||||||
|
self.assertEqual(line.posted_date, first_date)
|
||||||
|
|
||||||
|
def test_unique_period_per_asset(self):
|
||||||
|
self._make_line(4)
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
self._make_line(4)
|
||||||
|
|
||||||
|
def test_book_value_reflects_posted_lines_only(self):
|
||||||
|
l1 = self._make_line(5, amount=1000)
|
||||||
|
self._make_line(6, amount=1500)
|
||||||
|
self.assertEqual(self.asset.book_value, 12000)
|
||||||
|
l1.action_post()
|
||||||
|
self.asset.invalidate_recordset(['book_value', 'total_depreciated'])
|
||||||
|
self.assertEqual(self.asset.total_depreciated, 1000)
|
||||||
|
self.assertEqual(self.asset.book_value, 11000)
|
||||||
Reference in New Issue
Block a user