"""HTTP controller: 8 JSON-RPC endpoints for the OWL asset dashboard. All endpoints route through fusion.asset.engine. V19 type='jsonrpc'. """ import logging from datetime import date, datetime from odoo import _, http from odoo.exceptions import ValidationError from odoo.http import request _logger = logging.getLogger(__name__) def _parse_date(value): if isinstance(value, date): return value if not value: return None return datetime.strptime(value, '%Y-%m-%d').date() class FusionAssetsController(http.Controller): @http.route('/fusion/assets/list', type='jsonrpc', auth='user') def list_assets(self, state=None, category_id=None, limit=50, offset=0, company_id=None): company_id = int(company_id) if company_id else request.env.company.id Asset = request.env['fusion.asset'].sudo() domain = [('company_id', '=', company_id)] if state: domain.append(('state', '=', state)) if category_id: domain.append(('category_id', '=', int(category_id))) total = Asset.search_count(domain) assets = Asset.search(domain, limit=int(limit), offset=int(offset), order='acquisition_date desc') return { 'count': len(assets), 'total': total, 'assets': [{ 'id': a.id, 'name': a.name, 'code': a.code or '', 'state': a.state, 'cost': a.cost, 'salvage_value': a.salvage_value, 'book_value': a.book_value, 'total_depreciated': a.total_depreciated, 'method': a.method, 'useful_life_years': a.useful_life_years, 'acquisition_date': str(a.acquisition_date), 'in_service_date': str(a.in_service_date) if a.in_service_date else None, 'category_id': a.category_id.id if a.category_id else None, 'category_name': a.category_id.name if a.category_id else None, 'currency_code': a.currency_id.name, } for a in assets], } @http.route('/fusion/assets/get_detail', type='jsonrpc', auth='user') def get_detail(self, asset_id): asset = request.env['fusion.asset'].browse(int(asset_id)) if not asset.exists(): raise ValidationError(_("Asset %s not found") % asset_id) return { 'asset': { 'id': asset.id, 'name': asset.name, 'code': asset.code or '', 'state': asset.state, 'cost': asset.cost, 'salvage_value': asset.salvage_value, 'book_value': asset.book_value, 'total_depreciated': asset.total_depreciated, 'method': asset.method, 'useful_life_years': asset.useful_life_years, 'declining_rate_pct': asset.declining_rate_pct, 'total_units_expected': asset.total_units_expected, 'units_used_to_date': asset.units_used_to_date, 'prorate_convention': asset.prorate_convention, 'acquisition_date': str(asset.acquisition_date), 'in_service_date': str(asset.in_service_date) if asset.in_service_date else None, 'disposed_date': str(asset.disposed_date) if asset.disposed_date else None, 'category_id': asset.category_id.id if asset.category_id else None, 'category_name': asset.category_id.name if asset.category_id else None, 'currency_id': asset.currency_id.id, 'currency_code': asset.currency_id.name, }, 'depreciation_lines': [{ 'id': l.id, 'period_index': l.period_index, 'scheduled_date': str(l.scheduled_date), 'amount': l.amount, 'accumulated': l.accumulated, 'book_value_at_end': l.book_value_at_end, 'is_posted': l.is_posted, 'posted_date': str(l.posted_date) if l.posted_date else None, } for l in asset.depreciation_line_ids.sorted('period_index')], 'anomalies': [{ 'id': a.id, 'anomaly_type': a.anomaly_type, 'severity': a.severity, 'detail': a.detail or '', 'state': a.state, } for a in request.env['fusion.asset.anomaly'].search([ ('asset_id', '=', asset.id), ('state', 'in', ('new', 'acknowledged')) ])], } @http.route('/fusion/assets/compute_schedule', type='jsonrpc', auth='user') def compute_schedule(self, asset_id, recompute=False): asset = request.env['fusion.asset'].sudo().browse(int(asset_id)) engine = request.env['fusion.asset.engine'].sudo() return engine.compute_depreciation_schedule(asset, recompute=bool(recompute)) @http.route('/fusion/assets/post_depreciation', type='jsonrpc', auth='user') def post_depreciation(self, asset_id, period_date=None): asset = request.env['fusion.asset'].sudo().browse(int(asset_id)) engine = request.env['fusion.asset.engine'].sudo() return engine.post_depreciation_entry(asset, period_date=_parse_date(period_date)) @http.route('/fusion/assets/dispose', type='jsonrpc', auth='user') def dispose(self, asset_id, sale_amount=0, sale_date=None, sale_partner_id=None, disposal_type='sale'): asset = request.env['fusion.asset'].sudo().browse(int(asset_id)) engine = request.env['fusion.asset.engine'].sudo() partner = None if sale_partner_id: partner = request.env['res.partner'].sudo().browse(int(sale_partner_id)) return engine.dispose_asset( asset, sale_amount=float(sale_amount), sale_date=_parse_date(sale_date), sale_partner=partner, disposal_type=disposal_type, ) @http.route('/fusion/assets/get_anomalies', type='jsonrpc', auth='user') def get_anomalies(self, asset_id=None, severity=None, state='new', limit=50, company_id=None): company_id = int(company_id) if company_id else request.env.company.id Anomaly = request.env['fusion.asset.anomaly'].sudo() domain = [('company_id', '=', company_id)] if asset_id: domain.append(('asset_id', '=', int(asset_id))) if severity: domain.append(('severity', '=', severity)) if state: domain.append(('state', '=', state)) anomalies = Anomaly.search(domain, limit=int(limit), order='detected_at desc') return { 'count': len(anomalies), 'anomalies': [{ 'id': a.id, 'asset_id': a.asset_id.id, 'asset_name': a.asset_id.name, 'anomaly_type': a.anomaly_type, 'severity': a.severity, 'expected': a.expected, 'actual': a.actual, 'variance_pct': a.variance_pct, 'detail': a.detail or '', 'state': a.state, 'detected_at': str(a.detected_at), } for a in anomalies], } @http.route('/fusion/assets/suggest_useful_life', type='jsonrpc', auth='user') def suggest_useful_life(self, description, amount=None, partner_name=None): from odoo.addons.fusion_accounting_assets.services.useful_life_predictor import ( predict_useful_life, ) return predict_useful_life( request.env, description=description, amount=float(amount) if amount is not None else None, partner_name=partner_name, ) @http.route('/fusion/assets/get_partner_history', type='jsonrpc', auth='user') def get_partner_history(self, partner_id, limit=20): Asset = request.env['fusion.asset'].sudo() assets = Asset.search([ ('source_invoice_line_id.partner_id', '=', int(partner_id)), ], limit=int(limit), order='acquisition_date desc') return { 'partner_id': int(partner_id), 'count': len(assets), 'assets': [{ 'id': a.id, 'name': a.name, 'cost': a.cost, 'book_value': a.book_value, 'state': a.state, 'acquisition_date': str(a.acquisition_date), } for a in assets], }