"""Fusion Asset model. Lifecycle: draft -> running -> (paused -> running)* -> disposed. - draft: created, not yet running depreciation - running: depreciation board active, periodic posts happen - paused: depreciation suspended (e.g. asset out for repair) - disposed: sold/scrapped/donated; no further depreciation """ import logging from odoo import _, api, fields, models from odoo.exceptions import ValidationError _logger = logging.getLogger(__name__) METHOD_SELECTION = [ ('straight_line', 'Straight Line'), ('declining_balance', 'Declining Balance'), ('units_of_production', 'Units of Production'), ] PRORATE_SELECTION = [ ('full_month', 'Full Month'), ('days_365', 'Days / 365'), ('days_period', 'Days in Period'), ] STATE_SELECTION = [ ('draft', 'Draft'), ('running', 'Running'), ('paused', 'Paused'), ('disposed', 'Disposed'), ] class FusionAsset(models.Model): _name = "fusion.asset" _description = "Fusion Fixed Asset" _order = "acquisition_date desc, id desc" _inherit = ['mail.thread', 'mail.activity.mixin'] name = fields.Char(required=True, tracking=True) code = fields.Char(help="Internal asset code (e.g. tag number).") company_id = fields.Many2one( 'res.company', required=True, default=lambda self: self.env.company, ) category_id = fields.Many2one('fusion.asset.category', tracking=True) state = fields.Selection( STATE_SELECTION, default='draft', required=True, tracking=True, ) cost = fields.Monetary( required=True, tracking=True, help="Original acquisition cost.", ) salvage_value = fields.Monetary( default=0.0, tracking=True, help="Estimated end-of-life value.", ) acquisition_date = fields.Date( required=True, default=fields.Date.today, tracking=True, ) in_service_date = fields.Date( tracking=True, help="Date depreciation actually begins.", ) disposed_date = fields.Date(readonly=True, tracking=True) currency_id = fields.Many2one( 'res.currency', required=True, default=lambda self: self.env.company.currency_id, ) method = fields.Selection( METHOD_SELECTION, required=True, default='straight_line', tracking=True, ) useful_life_years = fields.Integer( default=5, tracking=True, help="For straight_line / declining_balance.", ) declining_rate_pct = fields.Float( default=20.0, help="For declining_balance method, e.g. 20.0 = 20%/year.", ) total_units_expected = fields.Float( help="For units_of_production method.", ) units_used_to_date = fields.Float( default=0.0, help="For units_of_production: track usage.", ) prorate_convention = fields.Selection( PRORATE_SELECTION, default='days_period', required=True, ) source_invoice_line_id = fields.Many2one( 'account.move.line', string='Source Invoice Line', help="The invoice line that originated this asset.", ) parent_id = fields.Many2one( 'fusion.asset', help='For partial-sale child assets.', ) depreciation_line_ids = fields.One2many( 'fusion.asset.depreciation.line', 'asset_id', string='Depreciation Lines', ) book_value = fields.Monetary(compute='_compute_book_value', store=True) total_depreciated = fields.Monetary(compute='_compute_book_value', store=True) last_posted_date = fields.Date(compute='_compute_last_posted_date', store=True) @api.depends('cost', 'depreciation_line_ids.amount', 'depreciation_line_ids.is_posted') def _compute_book_value(self): for asset in self: posted = sum(l.amount for l in asset.depreciation_line_ids if l.is_posted) asset.total_depreciated = posted asset.book_value = asset.cost - posted @api.depends('depreciation_line_ids.is_posted', 'depreciation_line_ids.scheduled_date') def _compute_last_posted_date(self): for asset in self: posted_dates = [ l.scheduled_date for l in asset.depreciation_line_ids if l.is_posted ] asset.last_posted_date = max(posted_dates) if posted_dates else False def action_set_running(self): for asset in self: if asset.state != 'draft': raise ValidationError(_("Only draft assets can be set running.")) if not asset.in_service_date: asset.in_service_date = fields.Date.today() asset.state = 'running' def action_pause(self): for asset in self: if asset.state != 'running': raise ValidationError(_("Only running assets can be paused.")) asset.state = 'paused' def action_resume(self): for asset in self: if asset.state != 'paused': raise ValidationError(_("Only paused assets can be resumed.")) asset.state = 'running' def action_set_draft(self): for asset in self: if asset.state not in ('draft', 'paused'): raise ValidationError( _("Cannot reset to draft from %s.") % asset.state, ) asset.state = 'draft' _check_cost_positive = models.Constraint( 'CHECK(cost >= 0)', 'Asset cost must be non-negative.', ) _check_salvage_lte_cost = models.Constraint( 'CHECK(salvage_value >= 0 AND salvage_value <= cost)', 'Salvage value must be between 0 and cost.', )