diff --git a/__manifest__.py b/__manifest__.py index 1edb029..e319fe1 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -11,7 +11,7 @@ - Asset Transfer Wizard """, 'author': 'Antigravity', - 'depends': ['base', 'account_asset', 'asset_code_field', 'product', 'stock', 'purchase'], + 'depends': ['base', 'account_asset', 'asset_code_field', 'product', 'stock', 'purchase', 'hr'], 'data': [ 'security/ga_asset_security.xml', 'security/ir.model.access.csv', @@ -19,6 +19,7 @@ 'views/account_asset_views.xml', 'views/stock_picking_views.xml', 'views/product_template_views.xml', + 'views/asset_transfer_log_views.xml', 'wizard/asset_transfer_views.xml', ], 'installable': True, diff --git a/models/__init__.py b/models/__init__.py index 521c357..fdde4c3 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -3,3 +3,4 @@ from . import stock_move from . import stock_picking from . import account_move from . import product_template +from . import ga_asset_transfer_log diff --git a/models/__pycache__/__init__.cpython-312.pyc b/models/__pycache__/__init__.cpython-312.pyc index 389667e..21ba335 100644 Binary files a/models/__pycache__/__init__.cpython-312.pyc and b/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/models/__pycache__/account_asset.cpython-312.pyc b/models/__pycache__/account_asset.cpython-312.pyc index cfcf250..50b8c43 100644 Binary files a/models/__pycache__/account_asset.cpython-312.pyc and b/models/__pycache__/account_asset.cpython-312.pyc differ diff --git a/models/account_asset.py b/models/account_asset.py index ad7c410..8e3184f 100644 --- a/models/account_asset.py +++ b/models/account_asset.py @@ -7,6 +7,10 @@ class AccountAsset(models.Model): product_id = fields.Many2one('product.product', string='Product') description = fields.Text(string='Description') + location_id = fields.Many2one('stock.location', string='Location', domain="[('company_id', '=', company_id)]") + employee_id = fields.Many2one('hr.employee', string='Employee', domain="[('company_id', '=', company_id)]") + in_transit = fields.Boolean(string='In Transit', default=False, help="Asset is currently being transferred between companies.") + @api.model_create_multi def create(self, vals_list): for vals in vals_list: @@ -45,6 +49,34 @@ class AccountAsset(models.Model): vals['asset_code'] = f"{prefix}/{year}{sequence}" + # Remove original_value if it is 0.0 to allow computation from lines later + if 'original_value' in vals and vals['original_value'] == 0.0: + del vals['original_value'] + + # Populate accounting fields from Asset Model if not already set + if vals.get('model_id'): + model = self.env['account.asset'].browse(vals['model_id']) + if model: + if not vals.get('method'): + vals['method'] = model.method + if not vals.get('method_number'): + vals['method_number'] = model.method_number + if not vals.get('method_period'): + vals['method_period'] = model.method_period + if not vals.get('prorata_computation_type'): + vals['prorata_computation_type'] = model.prorata_computation_type + + if not vals.get('account_asset_id'): + vals['account_asset_id'] = model.account_asset_id.id + if not vals.get('account_depreciation_id'): + vals['account_depreciation_id'] = model.account_depreciation_id.id + if not vals.get('account_depreciation_expense_id'): + vals['account_depreciation_expense_id'] = model.account_depreciation_expense_id.id + if not vals.get('journal_id'): + vals['journal_id'] = model.journal_id.id + if not vals.get('analytic_distribution') and model.analytic_distribution: + vals['analytic_distribution'] = model.analytic_distribution + return super(AccountAsset, self).create(vals_list) def action_open_transfer_wizard(self): diff --git a/models/ga_asset_transfer_log.py b/models/ga_asset_transfer_log.py new file mode 100644 index 0000000..43eab9a --- /dev/null +++ b/models/ga_asset_transfer_log.py @@ -0,0 +1,119 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError + +class GaAssetTransferLog(models.Model): + _name = 'ga.asset.transfer.log' + _description = 'Asset Transfer Log' + _order = 'create_date desc' + _inherit = ['mail.thread', 'mail.activity.mixin'] + + name = fields.Char(string='Transfer Reference', required=True, copy=False, readonly=True, default=lambda self: _('New')) + asset_id = fields.Many2one('account.asset', string='Asset', required=True, readonly=True, help="Asset being transferred.") + source_company_id = fields.Many2one('res.company', string='Source Company', required=True, readonly=True) + target_company_id = fields.Many2one('res.company', string='Target Company', required=True, readonly=True) + user_id = fields.Many2one('res.users', string='Requested By', required=True, readonly=True, default=lambda self: self.env.user) + transfer_date = fields.Date(string='Request Date', default=fields.Date.context_today, readonly=True) + + note = fields.Text(string='Transfer Note', readonly=True) + + state = fields.Selection([ + ('draft', 'Draft'), + ('transit', 'In Transit'), + ('done', 'Done'), + ('cancel', 'Cancelled') + ], string='Status', default='draft', tracking=True, copy=False) + + @api.model + def create(self, vals): + if vals.get('name', _('New')) == _('New'): + vals['name'] = self.env['ir.sequence'].next_by_code('ga.asset.transfer.log') or _('New') + return super(GaAssetTransferLog, self).create(vals) + + def action_validate(self): + self.ensure_one() + if self.state != 'transit': + raise UserError(_("You can only validate transfers that are currently In Transit.")) + + # Check if user is allowed to validate (must be in target company or have access) + # Assuming if they can see the button and access the record rules allow it, but let's double check company context + if self.env.company != self.target_company_id: + raise UserError(_("You must be logged into the Target Company (%s) to validate this transfer.", self.target_company_id.name)) + + # Perform the logic that was previously in the wizard + old_asset = self.asset_id + target_company = self.target_company_id + + # Logic adapted from original wizard + if old_asset.state in ('draft', 'model'): + old_asset.sudo().write({'company_id': target_company.id}) + old_asset.sudo().message_post(body=f"Asset transfer validated by {self.env.user.name}. Received in {target_company.name}.") + else: + # Running Asset -> Close and Clone + vals = { + 'name': old_asset.name, + 'product_id': old_asset.product_id.id, + 'asset_code': old_asset.asset_code, + 'original_value': old_asset.original_value, + 'already_depreciated_amount_import': old_asset.original_value - old_asset.value_residual, + 'acquisition_date': old_asset.acquisition_date, + 'prorata_date': old_asset.prorata_date, + 'method': old_asset.method, + 'method_number': old_asset.method_number, + 'method_period': old_asset.method_period, + 'method_progress_factor': old_asset.method_progress_factor, + 'prorata_computation_type': old_asset.prorata_computation_type, + 'company_id': target_company.id, + 'location_id': old_asset.location_id.id, # Copy location? Maybe not if it's new company. But user surely wants to update it later. + 'employee_id': old_asset.employee_id.id, # Keep employee if moving with them? + 'state': 'draft', + } + + # Find Asset Model in Target Company + if old_asset.product_id: + product = old_asset.product_id.with_company(target_company) + account = product.property_account_expense_id or product.categ_id.property_account_expense_categ_id + + if account and account.asset_model: + model = account.asset_model + vals['model_id'] = model.id + vals['account_asset_id'] = model.account_asset_id.id + vals['account_depreciation_id'] = model.account_depreciation_id.id + vals['account_depreciation_expense_id'] = model.account_depreciation_expense_id.id + vals['journal_id'] = model.journal_id.id + + # Close Old Asset + old_asset.depreciation_move_ids.filtered(lambda m: m.state == 'draft').with_context(force_delete=True).sudo().unlink() + old_asset.sudo().write({ + 'state': 'close', + 'active': False, + 'in_transit': False # No longer in transit + }) + old_asset.sudo().message_post(body=f"Asset transfer validated. Closed and moved to {target_company.name}.") + + # Create New Asset + new_asset = self.env['account.asset'].sudo().create(vals) + new_asset.sudo().message_post(body=f"Asset received from {self.source_company_id.name}. Transfer Ref: {self.name}") + + self.write({'state': 'done'}) + + # Unlock old asset just in case (if it was simple move) + if old_asset.id: # If simple move, old_asset is still the active one + old_asset.sudo().write({'in_transit': False}) + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('Success'), + 'message': _('Asset has been successfully transferred.'), + 'sticky': False, + } + } + + def action_cancel(self): + self.ensure_one() + if self.state == 'done': + raise UserError(_("You cannot cancel a completed transfer.")) + + self.asset_id.sudo().write({'in_transit': False}) + self.write({'state': 'cancel'}) diff --git a/security/ga_asset_security.xml b/security/ga_asset_security.xml index 394ec80..af4a7fa 100644 --- a/security/ga_asset_security.xml +++ b/security/ga_asset_security.xml @@ -20,4 +20,10 @@ + + Asset Transfer Log Multi Company + + ['|', ('source_company_id', 'in', company_ids), ('target_company_id', 'in', company_ids)] + + diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv index 7dffc28..3f96d43 100644 --- a/security/ir.model.access.csv +++ b/security/ir.model.access.csv @@ -1,3 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_ga_asset_transfer_wizard_user,ga.asset.transfer.wizard.user,model_ga_asset_transfer_wizard,group_ga_asset_user,1,1,1,0 access_ga_asset_transfer_wizard_manager,ga.asset.transfer.wizard.manager,model_ga_asset_transfer_wizard,group_ga_asset_manager,1,1,1,1 +access_ga_asset_transfer_log_user,ga.asset.transfer.log.user,model_ga_asset_transfer_log,group_ga_asset_user,1,1,1,0 +access_ga_asset_transfer_log_manager,ga.asset.transfer.log.manager,model_ga_asset_transfer_log,group_ga_asset_manager,1,1,1,1 diff --git a/views/account_asset_views.xml b/views/account_asset_views.xml index 7dc5434..e3df2f1 100644 --- a/views/account_asset_views.xml +++ b/views/account_asset_views.xml @@ -26,7 +26,7 @@ - + @@ -47,8 +47,23 @@ + + + + + + + + + + + + + + + @@ -67,25 +82,15 @@ Assets account.asset tree,form - + [('state', '!=', 'model')] {'default_state': 'draft'} - - - tree - - - - - - - form - - - + @@ -96,4 +101,49 @@ action="action_ga_asset_management" sequence="1"/> + + + account.asset.form.inherit.ga + account.asset + + + + 1 + 1 + + + 1 + 1 + + + 1 + 1 + + + 1 + 1 + + + 1 + 1 + + + 1 + 1 + + + 1 + 1 + + + 1 + 1 + + + 1 + 1 + + + + diff --git a/views/asset_transfer_log_views.xml b/views/asset_transfer_log_views.xml new file mode 100644 index 0000000..2d42699 --- /dev/null +++ b/views/asset_transfer_log_views.xml @@ -0,0 +1,83 @@ + + + + ga.asset.transfer.log.tree + ga.asset.transfer.log + + + + + + + + + + + + + + + ga.asset.transfer.log.form + ga.asset.transfer.log + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Pending Asset Receipts + ga.asset.transfer.log + tree,form + [('state', '=', 'transit')] + {'search_default_transit': 1} + + + No pending asset transfers found + + When an asset is transferred from another company, it will appear here for validation. + + + + + + + + + diff --git a/wizard/__pycache__/asset_transfer_wizard.cpython-312.pyc b/wizard/__pycache__/asset_transfer_wizard.cpython-312.pyc index 318a4ea..71096fe 100644 Binary files a/wizard/__pycache__/asset_transfer_wizard.cpython-312.pyc and b/wizard/__pycache__/asset_transfer_wizard.cpython-312.pyc differ diff --git a/wizard/asset_transfer_wizard.py b/wizard/asset_transfer_wizard.py index 46fd2b0..fcc6952 100644 --- a/wizard/asset_transfer_wizard.py +++ b/wizard/asset_transfer_wizard.py @@ -12,86 +12,27 @@ class GaAssetTransferWizard(models.TransientModel): def action_transfer(self): self.ensure_one() if self.target_company_id and self.target_company_id != self.current_company_id: - old_asset = self.asset_id - - # CASE 1: Asset is Draft or Model -> Simple Move - # No depreciation history to preserve, so we just move it. - if old_asset.state in ('draft', 'model'): - old_asset.sudo().write({'company_id': self.target_company_id.id}) - if self.note: - old_asset.sudo().message_post(body=f"Asset transferred to {self.target_company_id.name}. Note: {self.note}") - - return { - 'name': 'Transferred Asset', - 'type': 'ir.actions.act_window', - 'res_model': 'account.asset', - 'view_mode': 'form', - 'res_id': old_asset.id, - 'target': 'current', - } - - # CASE 2: Asset is Running (Open/Parsed) -> Close and Clone - # We want to stop the old one and start fresh in new company - vals = { - 'name': old_asset.name, - 'product_id': old_asset.product_id.id, - 'asset_code': old_asset.asset_code, - 'original_value': old_asset.original_value, - 'already_depreciated_amount_import': old_asset.original_value - old_asset.value_residual, - 'acquisition_date': old_asset.acquisition_date, - 'prorata_date': old_asset.prorata_date, - 'method': old_asset.method, - 'method_number': old_asset.method_number, - 'method_period': old_asset.method_period, - 'method_progress_factor': old_asset.method_progress_factor, - 'prorata_computation_type': old_asset.prorata_computation_type, - 'company_id': self.target_company_id.id, - 'state': 'draft', - } - - # Find Asset Model in Target Company to apply Accounts - if old_asset.product_id: - # Get Product with company context - product = old_asset.product_id.with_company(self.target_company_id) - account = product.property_account_expense_id or product.categ_id.property_account_expense_categ_id - - if account and account.asset_model: - model = account.asset_model - vals['model_id'] = model.id - # Auto-populate Accounts from Model - vals['account_asset_id'] = model.account_asset_id.id - vals['account_depreciation_id'] = model.account_depreciation_id.id - vals['account_depreciation_expense_id'] = model.account_depreciation_expense_id.id - vals['journal_id'] = model.journal_id.id - - # 2. Close the old asset WITHOUT generating Disposal Moves - # User request: "no need to use dispose method (gain/loss account)" - # We simply stop future depreciation and archive the old asset. - # NOTE: The Asset Value remains on the old company's Balance Sheet until manually adjusted/transferred. - - # Cancel future draft moves (using sudo to bypass company rules if needed) - old_asset.depreciation_move_ids.filtered(lambda m: m.state == 'draft').with_context(force_delete=True).sudo().unlink() - - # Close and Archive (using sudo to ensure we can modify the record even if we are switching context) - old_asset.sudo().write({ - 'state': 'close', - 'active': False, + # Create Transfer Log + transfer_log = self.env['ga.asset.transfer.log'].create({ + 'asset_id': self.asset_id.id, + 'source_company_id': self.current_company_id.id, + 'target_company_id': self.target_company_id.id, + 'note': self.note, + 'state': 'transit', }) - old_asset.sudo().message_post(body=f"Asset transferred to {self.target_company_id.name}. Auto-disposal skipped.") + # Lock Asset + self.asset_id.sudo().write({'in_transit': True}) + self.asset_id.message_post(body=f"Asset transfer initiated to {self.target_company_id.name}. Waiting for validation. Ref: {transfer_log.name}") - # 3. Create the new asset in target company (using sudo to create in a company we might not be active in) - new_asset = self.env['account.asset'].sudo().create(vals) - new_asset.sudo().message_post(body=f"Asset transferred from {self.current_company_id.name}. Note: {self.note}") - - # 4. Open the new asset return { - 'name': 'Transferred Asset', - 'type': 'ir.actions.act_window', - 'res_model': 'account.asset', - 'view_mode': 'form', - 'res_id': new_asset.id, - 'target': 'current', + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Transfer Initiated', + 'message': f"Asset transfer to {self.target_company_id.name} is now pending validation.", + 'sticky': False, + } } return {'type': 'ir.actions.act_window_close'}
+ No pending asset transfers found +
+ When an asset is transferred from another company, it will appear here for validation. +