From 0439d81675ce7d3a9fb30412aa9479a71f53ad29 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 19 Apr 2026 16:56:47 -0400 Subject: [PATCH] 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 --- fusion_accounting_assets/__manifest__.py | 2 +- .../models/fusion_asset_depreciation_line.py | 39 +++++++++--- fusion_accounting_assets/tests/__init__.py | 1 + .../test_fusion_asset_depreciation_line.py | 62 +++++++++++++++++++ 4 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 fusion_accounting_assets/tests/test_fusion_asset_depreciation_line.py diff --git a/fusion_accounting_assets/__manifest__.py b/fusion_accounting_assets/__manifest__.py index 5bfa05d0..8ed58fc1 100644 --- a/fusion_accounting_assets/__manifest__.py +++ b/fusion_accounting_assets/__manifest__.py @@ -1,6 +1,6 @@ { 'name': 'Fusion Accounting Assets', - 'version': '19.0.1.0.6', + 'version': '19.0.1.0.7', 'category': 'Accounting/Accounting', 'summary': 'AI-augmented asset management with depreciation schedules.', 'description': """ diff --git a/fusion_accounting_assets/models/fusion_asset_depreciation_line.py b/fusion_accounting_assets/models/fusion_asset_depreciation_line.py index 2656d898..e8fd0166 100644 --- a/fusion_accounting_assets/models/fusion_asset_depreciation_line.py +++ b/fusion_accounting_assets/models/fusion_asset_depreciation_line.py @@ -1,8 +1,4 @@ -"""Per-period depreciation board lines for an asset. - -Stub created with Task 8 (so fusion.asset One2many resolves). Fully -elaborated in Task 9. -""" +"""Per-period depreciation board lines for an asset.""" from odoo import fields, models @@ -13,7 +9,34 @@ class FusionAssetDepreciationLine(models.Model): _order = "asset_id, scheduled_date" asset_id = fields.Many2one('fusion.asset', required=True, ondelete='cascade') - scheduled_date = fields.Date(required=True) - amount = fields.Monetary() + company_id = fields.Many2one(related='asset_id.company_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.', + ) diff --git a/fusion_accounting_assets/tests/__init__.py b/fusion_accounting_assets/tests/__init__.py index 18ba4251..5104fc18 100644 --- a/fusion_accounting_assets/tests/__init__.py +++ b/fusion_accounting_assets/tests/__init__.py @@ -4,3 +4,4 @@ from . import test_salvage_value from . import test_asset_anomaly_detection from . import test_useful_life_predictor from . import test_fusion_asset +from . import test_fusion_asset_depreciation_line diff --git a/fusion_accounting_assets/tests/test_fusion_asset_depreciation_line.py b/fusion_accounting_assets/tests/test_fusion_asset_depreciation_line.py new file mode 100644 index 00000000..bf401510 --- /dev/null +++ b/fusion_accounting_assets/tests/test_fusion_asset_depreciation_line.py @@ -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)