feat(fusion_accounting_assets): partial sale wizard
Made-with: Cursor
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting Assets',
|
||||
'version': '19.0.1.0.28',
|
||||
'version': '19.0.1.0.29',
|
||||
'category': 'Accounting/Accounting',
|
||||
'summary': 'AI-augmented asset management with depreciation schedules.',
|
||||
'description': """
|
||||
@@ -36,6 +36,7 @@ menu hides; the engine + AI tools remain available for the chat.
|
||||
'data/cron.xml',
|
||||
'wizards/create_asset_wizard_views.xml',
|
||||
'wizards/disposal_wizard_views.xml',
|
||||
'wizards/partial_sale_wizard_views.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
|
||||
@@ -11,3 +11,4 @@ access_fusion_asset_anomaly_user,fusion.asset.anomaly.user,model_fusion_asset_an
|
||||
access_fusion_asset_anomaly_admin,fusion.asset.anomaly.admin,model_fusion_asset_anomaly,fusion_accounting_core.group_fusion_accounting_admin,1,1,1,1
|
||||
access_fusion_create_asset_wizard_user,fusion.create.asset.wizard.user,model_fusion_create_asset_wizard,base.group_user,1,1,1,0
|
||||
access_fusion_disposal_wizard_user,fusion.disposal.wizard.user,model_fusion_disposal_wizard,base.group_user,1,1,1,0
|
||||
access_fusion_partial_sale_wizard_user,fusion.partial.sale.wizard.user,model_fusion_partial_sale_wizard,base.group_user,1,1,1,0
|
||||
|
||||
|
@@ -21,3 +21,4 @@ from . import test_asset_book_values_mv
|
||||
from . import test_performance_benchmarks
|
||||
from . import test_create_asset_wizard
|
||||
from . import test_disposal_wizard
|
||||
from . import test_partial_sale_wizard
|
||||
|
||||
48
fusion_accounting_assets/tests/test_partial_sale_wizard.py
Normal file
48
fusion_accounting_assets/tests/test_partial_sale_wizard.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from datetime import date
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestPartialSaleWizard(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.asset = self.env['fusion.asset'].create({
|
||||
'name': 'Partial Sale Test',
|
||||
'cost': 10000,
|
||||
'acquisition_date': date(2026, 1, 1),
|
||||
'in_service_date': date(2026, 1, 1),
|
||||
'method': 'straight_line', 'useful_life_years': 5,
|
||||
})
|
||||
self.env['fusion.asset.engine'].compute_depreciation_schedule(self.asset)
|
||||
self.asset.action_set_running()
|
||||
|
||||
def test_partial_sell_30pct_creates_child(self):
|
||||
wizard = self.env['fusion.partial.sale.wizard'].create({
|
||||
'asset_id': self.asset.id,
|
||||
'sold_pct': 30.0, 'sold_amount': 4000,
|
||||
'sale_date': date(2026, 6, 1),
|
||||
})
|
||||
wizard.action_partial_sell()
|
||||
self.asset.invalidate_recordset(['cost'])
|
||||
self.assertAlmostEqual(self.asset.cost, 7000, places=2)
|
||||
|
||||
def test_invalid_pct_raises(self):
|
||||
wizard = self.env['fusion.partial.sale.wizard'].create({
|
||||
'asset_id': self.asset.id,
|
||||
'sold_pct': 0, 'sold_amount': 100,
|
||||
})
|
||||
with self.assertRaises(UserError):
|
||||
wizard.action_partial_sell()
|
||||
|
||||
def test_compute_estimated_gain_loss(self):
|
||||
wizard = self.env['fusion.partial.sale.wizard'].new({
|
||||
'asset_id': self.asset.id,
|
||||
'sold_pct': 30.0, 'sold_amount': 4000,
|
||||
})
|
||||
wizard._compute_sold_cost()
|
||||
self.assertAlmostEqual(wizard.estimated_sold_cost, 3000, places=2)
|
||||
self.assertAlmostEqual(wizard.estimated_gain_loss, 1000, places=2)
|
||||
@@ -1,2 +1,3 @@
|
||||
from . import create_asset_wizard
|
||||
from . import disposal_wizard
|
||||
from . import partial_sale_wizard
|
||||
|
||||
67
fusion_accounting_assets/wizards/partial_sale_wizard.py
Normal file
67
fusion_accounting_assets/wizards/partial_sale_wizard.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""Partial sale wizard (sell a portion of an asset).
|
||||
|
||||
Splits the asset into a child (the sold portion) and disposes the child;
|
||||
parent retains remaining cost + salvage."""
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class FusionPartialSaleWizard(models.TransientModel):
|
||||
_name = "fusion.partial.sale.wizard"
|
||||
_description = "Asset Partial Sale Wizard"
|
||||
|
||||
asset_id = fields.Many2one(
|
||||
'fusion.asset', required=True,
|
||||
default=lambda self: self._default_asset(),
|
||||
)
|
||||
company_id = fields.Many2one(related='asset_id.company_id')
|
||||
currency_id = fields.Many2one(related='asset_id.currency_id')
|
||||
cost = fields.Monetary(related='asset_id.cost', readonly=True)
|
||||
book_value = fields.Monetary(related='asset_id.book_value', readonly=True)
|
||||
|
||||
sold_pct = fields.Float(
|
||||
string='% of cost being sold', default=30.0,
|
||||
help="Percentage of original cost attributed to the sold portion.",
|
||||
)
|
||||
sold_amount = fields.Monetary(string='Sale Amount', required=True)
|
||||
sale_date = fields.Date(required=True, default=fields.Date.today)
|
||||
sale_partner_id = fields.Many2one('res.partner')
|
||||
|
||||
estimated_sold_cost = fields.Monetary(compute='_compute_sold_cost')
|
||||
estimated_gain_loss = fields.Monetary(compute='_compute_sold_cost')
|
||||
|
||||
@api.model
|
||||
def _default_asset(self):
|
||||
ctx = self.env.context
|
||||
if ctx.get('active_model') == 'fusion.asset':
|
||||
return ctx.get('active_id')
|
||||
return False
|
||||
|
||||
@api.depends('sold_pct', 'sold_amount', 'cost')
|
||||
def _compute_sold_cost(self):
|
||||
for w in self:
|
||||
w.estimated_sold_cost = round(w.cost * (w.sold_pct or 0) / 100, 2)
|
||||
w.estimated_gain_loss = w.sold_amount - w.estimated_sold_cost
|
||||
|
||||
def action_partial_sell(self):
|
||||
self.ensure_one()
|
||||
if not (0 < self.sold_pct < 100):
|
||||
raise UserError(_("sold_pct must be strictly between 0 and 100."))
|
||||
if self.asset_id.state == 'disposed':
|
||||
raise UserError(_("Asset already disposed."))
|
||||
|
||||
result = self.env['fusion.asset.engine'].partial_sale(
|
||||
self.asset_id,
|
||||
sold_amount=self.sold_amount,
|
||||
sold_qty=self.sold_pct / 100,
|
||||
sale_date=self.sale_date,
|
||||
sale_partner=self.sale_partner_id,
|
||||
)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'fusion.asset',
|
||||
'res_id': result['parent_asset_id'],
|
||||
'view_mode': 'form',
|
||||
'target': 'current',
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_fusion_partial_sale_wizard_form" model="ir.ui.view">
|
||||
<field name="name">fusion.partial.sale.wizard.form</field>
|
||||
<field name="model">fusion.partial.sale.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Partial Sale">
|
||||
<group>
|
||||
<field name="asset_id" readonly="1" options="{'no_create': True}"/>
|
||||
<field name="cost" readonly="1"/>
|
||||
<field name="book_value" readonly="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="sold_pct"/>
|
||||
<field name="estimated_sold_cost" readonly="1"/>
|
||||
<field name="sold_amount"/>
|
||||
<field name="estimated_gain_loss" readonly="1"/>
|
||||
<field name="sale_date"/>
|
||||
<field name="sale_partner_id"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="action_partial_sell" type="object"
|
||||
string="Confirm Partial Sale" class="btn-primary"/>
|
||||
<button special="cancel" string="Cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fusion_partial_sale_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Partial Sale</field>
|
||||
<field name="res_model">fusion.partial.sale.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user