[14.0][ADD] account_asset_transfer, AUC -> Asset

This commit is contained in:
Kitti U
2022-01-17 15:14:41 +07:00
committed by Saran440
parent d9260bf421
commit 3d9a478729
18 changed files with 601 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models
from . import wizard

View File

@@ -0,0 +1,19 @@
# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Asset Transfer from AUC to Asset",
"version": "14.0.1.0.0",
"license": "AGPL-3",
"depends": ["account_asset_management"],
"author": "Ecosoft, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/account-financial-tools",
"category": "Accounting & Finance",
"data": [
"security/ir.model.access.csv",
"views/account_asset.xml",
"views/account_asset_profile.xml",
"wizard/account_asset_transfer.xml",
],
"maintainers": ["kittiu"],
}

View File

@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import account_asset
from . import account_asset_profile

View File

@@ -0,0 +1,61 @@
# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, fields, models
from odoo.exceptions import ValidationError
class AccountAsset(models.Model):
_inherit = "account.asset"
can_transfer = fields.Boolean(
compute="_compute_can_transfer",
search="_search_can_transfer",
help="Allow transfer AUC (running) to Asset",
)
def _compute_can_transfer(self):
for asset in self:
asset.can_transfer = (
not asset.method_number
and asset.value_residual
and asset.state == "open"
and asset.account_move_line_ids
)
def _search_can_transfer(self, operator, value):
if operator == "=":
return [
("method_number", "=", 0),
("value_residual", ">", 0),
("state", "=", "open"),
("account_move_line_ids", "!=", False),
]
if operator == "!=":
return [
"|",
"|",
"|",
("method_number", ">", 0),
("value_residual", "=", 0),
("state", "!=", "open"),
("account_move_line_ids", "=", False),
]
def _check_can_transfer(self):
if not all(self.mapped("can_transfer")):
raise ValidationError(
_("Only running assets without depreciation (AUC) can transfer")
)
def transfer(self):
ctx = dict(self.env.context, active_ids=self.ids)
self._check_can_transfer()
return {
"name": _("Transfer AUC to Asset & Create Transfer Journal Entry"),
"view_mode": "form",
"res_model": "account.asset.transfer",
"target": "new",
"type": "ir.actions.act_window",
"context": ctx,
}

View File

@@ -0,0 +1,15 @@
# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class AccountAssetProfile(models.Model):
_inherit = "account.asset.profile"
transfer_journal_id = fields.Many2one(
comodel_name="account.journal",
domain="[('type', '=', 'general'), ('company_id', '=', company_id)]",
string="Transfer Journal",
check_company=True,
)

View File

@@ -0,0 +1 @@
On asset profile, user can setup default Transfer Journal, to be used during transfer.

View File

@@ -0,0 +1,3 @@
* `Ecosoft <http://ecosoft.co.th>`__:
* Kitti U. <kittiu@ecosoft.co.th>

View File

@@ -0,0 +1,5 @@
This module allow transferring assets under construction (AUC) to normal assets.
AUC is Asset under construction where some assets are in construction phase and
cost needs to be captured for the time being. Once asset is fully completed then
cost would be transferred to final asset

View File

@@ -0,0 +1,8 @@
Given asset under construction has been created, i.e., by vendor bill.
- Go to asset menu
- Filter "Transferrable" assets and look for desired assets to transfer
- Select assets to transfer, and click actoin "Transfer Asset"
- On asset transfer wizard, on the "To New Asset" tab, choose new profile(s)
- Click "Transfer" button
- Odoo will create journal entry as well as new asset(s)

View File

@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_asset_transfer_user,account.asset.transfer,model_account_asset_transfer,account.group_account_user,1,1,1,1
access_account_asset_transfer_line_user,account.asset.transfer.line,model_account_asset_transfer_line,account.group_account_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_asset_transfer_user account.asset.transfer model_account_asset_transfer account.group_account_user 1 1 1 1
3 access_account_asset_transfer_line_user account.asset.transfer.line model_account_asset_transfer_line account.group_account_user 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1 @@
from . import test_account_asset_transfer

View File

@@ -0,0 +1,112 @@
# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields
from odoo.exceptions import UserError
from odoo.tests.common import Form
from odoo.addons.account_asset_management.tests.test_account_asset_management import (
TestAssetManagement,
)
class TestAccountAssetTransfer(TestAssetManagement):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Profile Under Construction
cls.profile_uac = cls.asset_profile_model.create(
{
"account_expense_depreciation_id": cls.company_data[
"default_account_expense"
].id,
"account_asset_id": cls.company_data["default_account_assets"].id,
"account_depreciation_id": cls.company_data[
"default_account_assets"
].id,
"journal_id": cls.company_data["default_journal_purchase"].id,
"transfer_journal_id": cls.company_data["default_journal_misc"].id,
"name": "Asset Under Construction",
"method_time": "year",
"method_number": 0,
"method_period": "year",
}
)
# Profile normal asset
cls.profile_asset = cls.asset_profile_model.create(
{
"account_expense_depreciation_id": cls.company_data[
"default_account_expense"
].id,
"account_asset_id": cls.company_data["default_account_assets"].id,
"account_depreciation_id": cls.company_data[
"default_account_assets"
].id,
"journal_id": cls.company_data["default_journal_purchase"].id,
"name": "Room - 5 Years",
"method_time": "year",
"method_number": 5,
"method_period": "year",
}
)
def test_01_asset_transfer_uac_to_asset(self):
"""Create UAC and then transfer to normal asset class,
I expect a new journal entry will be created"""
# Create 3 UAC assets from an invoice
move_form = Form(
self.env["account.move"].with_context(
default_move_type="in_invoice", check_move_validity=False
)
)
move_form.invoice_date = fields.Date.context_today(self.env.user)
move_form.partner_id = self.partner
with move_form.invoice_line_ids.new() as line_form:
line_form.name = "Wall"
line_form.price_unit = 2000.00
line_form.asset_profile_id = self.profile_uac
with move_form.invoice_line_ids.new() as line_form:
line_form.name = "Roof"
line_form.price_unit = 10000.00
line_form.asset_profile_id = self.profile_uac
with move_form.invoice_line_ids.new() as line_form:
line_form.name = "Floor"
line_form.price_unit = 10000.00
line_form.asset_profile_id = self.profile_uac
self.invoice_uac = move_form.save()
self.invoice_uac.invoice_line_ids.write(
{"asset_profile_id": self.profile_uac.id}
)
self.invoice_uac.action_post()
# Test can can_review status
assets = self.invoice_uac.invoice_line_ids.mapped("asset_id")
self.assertFalse(list(set(assets.mapped("can_transfer")))[0])
assets.validate()
assets.invalidate_cache()
# can_transfer = True after validate
self.assertTrue(list(set(assets.mapped("can_transfer")))[0])
# Create Asset Transfer
transfer_form = Form(
self.env["account.asset.transfer"].with_context(active_ids=assets.ids)
)
transfer_wiz = transfer_form.save()
with self.assertRaises(UserError):
transfer_wiz.transfer()
with transfer_form.to_asset_ids.new() as to_asset:
to_asset.asset_name = "Asset 1"
to_asset.asset_profile_id = self.profile_asset
to_asset.asset_value = 2000
with transfer_form.to_asset_ids.new() as to_asset:
to_asset.asset_name = "Asset 2"
to_asset.asset_profile_id = self.profile_asset
to_asset.asset_value = 20000
transfer_form.save()
res = transfer_wiz.transfer()
transfer_move = self.env["account.move"].browse(res["domain"][0][2])
assets = transfer_move.invoice_line_ids.mapped("asset_id")
# 2 new assets created, and value equal to original assets
new_assets = assets.filtered(lambda l: l.state == "draft")
self.assertEqual(sum(new_assets.mapped("purchase_value")), 22000)

View File

@@ -0,0 +1,45 @@
<odoo>
<record model="ir.ui.view" id="account_asset_view_form">
<field name="name">account.asset.form</field>
<field name="model">account.asset</field>
<field
name="inherit_id"
ref="account_asset_management.account_asset_view_form"
/>
<field name="arch" type="xml">
<button name="remove" position="after">
<field name="can_transfer" invisible="1" />
<button
name="transfer"
string="Transfer"
type="object"
groups="account.group_account_manager"
attrs="{'invisible': [('can_transfer', '=', False)]}"
help="Transfer asset from AUC to Asset"
/>
</button>
</field>
</record>
<record id="account_asset_view_search" model="ir.ui.view">
<field name="name">account.asset.search</field>
<field name="model">account.asset</field>
<field
name="inherit_id"
ref="account_asset_management.account_asset_view_search"
/>
<field name="arch" type="xml">
<xpath expr="//separator[last()]" position="before">
<separator name="transfer" />
<filter
string="Transferable"
name="can_transfer"
domain="[('can_transfer', '=', True)]"
help="Can be transferred"
/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,16 @@
<odoo>
<record id="account_asset_profile_view_form" model="ir.ui.view">
<field name="name">account.asset.profile.form</field>
<field name="model">account.asset.profile</field>
<field
name="inherit_id"
ref="account_asset_management.account_asset_profile_view_form"
/>
<field name="arch" type="xml">
<field name="company_id" position="after">
<separator />
<field name="transfer_journal_id" />
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
from . import account_asset_transfer

View File

@@ -0,0 +1,216 @@
# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.tools import float_compare
class AccountAssetTransfer(models.TransientModel):
_name = "account.asset.transfer"
_description = "Transfer Asset"
_check_company_auto = True
company_id = fields.Many2one(
comodel_name="res.company",
string="Company",
readonly=True,
required=True,
)
currency_id = fields.Many2one(
comodel_name="res.currency",
related="company_id.currency_id",
)
transfer_journal_id = fields.Many2one(
comodel_name="account.journal",
domain="[('type', '=', 'general'), ('company_id', '=', company_id)]",
string="Transfer Journal",
required=True,
check_company=True,
)
date_transfer = fields.Date(
string="Transfer Date",
required=True,
default=fields.Date.today,
help="Transfer date must be after the asset journal entry",
)
note = fields.Text("Notes")
from_asset_ids = fields.Many2many(
comodel_name="account.asset",
string="From Asset(s)",
readonly=True,
)
to_asset_ids = fields.One2many(
comodel_name="account.asset.transfer.line",
inverse_name="transfer_id",
string="To Asset(s)",
)
from_asset_value = fields.Monetary(
string="From Value",
compute="_compute_asset_value",
)
to_asset_value = fields.Monetary(
string="To Value",
compute="_compute_asset_value",
)
balance = fields.Monetary(
compute="_compute_asset_value",
)
partner_id = fields.Many2one(
comodel_name="res.partner",
string="Partner",
)
analytic_account_id = fields.Many2one(
comodel_name="account.analytic.account",
string="Analytic account",
)
analytic_tag_ids = fields.Many2many(
comodel_name="account.analytic.tag",
string="Analytic tags",
)
@api.model
def default_get(self, field_list):
res = super().default_get(field_list)
from_asset_ids = self.env.context.get("active_ids")
assets = self.env["account.asset"].browse(from_asset_ids)
# Prepare default values
company = assets.mapped("company_id")
company.ensure_one()
journals = assets.mapped("profile_id.transfer_journal_id")
partners = assets.mapped("partner_id")
analytics = assets.mapped("account_analytic_id")
tags = assets[:1].analytic_tag_ids
for asset in assets:
if asset.analytic_tag_ids != tags:
# When not all tags are the same, no default
tags = self.env["account.analytic.tag"]
break
# Assign values
res["company_id"] = company.id
res["partner_id"] = partners[0].id if len(partners) == 1 else False
res["from_asset_ids"] = [(4, asset_id) for asset_id in assets.ids]
res["transfer_journal_id"] = journals[:1].id
res["analytic_account_id"] = analytics[0].id if len(analytics) == 1 else False
res["analytic_tag_ids"] = [(4, tag_id) for tag_id in tags.ids]
return res
@api.depends("from_asset_ids", "to_asset_ids")
def _compute_asset_value(self):
for rec in self:
rec.from_asset_value = sum(rec.from_asset_ids.mapped("purchase_value"))
rec.to_asset_value = sum(rec.to_asset_ids.mapped("asset_value"))
balance = rec.from_asset_value - rec.to_asset_value
rec.balance = balance if balance > 0 else 0
def _check_amount_trasnfer(self):
self.ensure_one()
if float_compare(self.from_asset_value, self.to_asset_value, 2) != 0:
raise UserError(_("Total values of new assets must equal to source assets"))
if self.to_asset_ids.filtered(lambda l: l.asset_value <= 0):
raise UserError(_("Value of new asset must greater than 0.0"))
def _get_new_move_transfer(self):
return {
"date": self.date_transfer,
"journal_id": self.transfer_journal_id.id,
"narration": self.note,
}
def transfer(self):
self.ensure_one()
self.from_asset_ids._check_can_transfer()
self._check_amount_trasnfer()
# Create transfer journal entry
move_vals = self._get_new_move_transfer()
move = self.env["account.move"].create(move_vals)
move_lines = self._get_transfer_data()
move.with_context(allow_asset=True).write({"line_ids": move_lines})
# Post move and create new assets
move.action_post()
# Set source assets as removed
self.from_asset_ids.write(
{"state": "removed", "date_remove": self.date_transfer}
)
return {
"name": _("Asset Transfer Journal Entry"),
"view_mode": "tree,form",
"res_model": "account.move",
"view_id": False,
"type": "ir.actions.act_window",
"context": self.env.context,
"domain": [("id", "=", move.id)],
}
def _get_move_line_from_asset(self, move_line):
return {
"name": move_line.name,
"account_id": move_line.account_id.id,
"analytic_account_id": move_line.analytic_account_id.id,
"analytic_tag_ids": [(4, tag.id) for tag in move_line.analytic_tag_ids],
"debit": move_line.credit,
"credit": move_line.debit,
"partner_id": move_line.partner_id.id,
"asset_id": move_line.asset_id.id, # Link to existing asset
}
def _get_move_line_to_asset(self, to_asset):
return {
"name": to_asset.asset_name,
"account_id": to_asset.asset_profile_id.account_asset_id.id,
"analytic_account_id": to_asset.analytic_account_id.id,
"analytic_tag_ids": [(4, tag.id) for tag in to_asset.analytic_tag_ids],
"debit": to_asset.asset_value,
"credit": 0.0,
"partner_id": to_asset.partner_id.id,
"asset_profile_id": to_asset.asset_profile_id.id, # To create new asset
"price_subtotal": to_asset.asset_value,
}
def _get_transfer_data(self):
move_lines = []
# Create lines from assets
for asset in self.from_asset_ids:
asset.account_move_line_ids.ensure_one()
move_line = asset.account_move_line_ids[0]
move_line_vals = self._get_move_line_from_asset(move_line)
move_lines.append((0, 0, move_line_vals))
# Create lines for new assets
move_lines += [
(0, 0, self._get_move_line_to_asset(to_asset))
for to_asset in self.to_asset_ids
]
return move_lines
class AccountAssetTransferLine(models.TransientModel):
_name = "account.asset.transfer.line"
_description = "Transfer To Asset"
transfer_id = fields.Many2one(
comodel_name="account.asset.transfer",
index=True,
)
asset_profile_id = fields.Many2one(
comodel_name="account.asset.profile",
string="Asset Profile",
required=True,
)
asset_name = fields.Char(required=True)
asset_value = fields.Float(
string="Asset Value",
required=True,
default=0.0,
)
partner_id = fields.Many2one(
comodel_name="res.partner",
string="Partner",
)
analytic_account_id = fields.Many2one(
comodel_name="account.analytic.account",
string="Analytic account",
)
analytic_tag_ids = fields.Many2many(
comodel_name="account.analytic.tag",
string="Analytic tags",
)

View File

@@ -0,0 +1,88 @@
<odoo>
<record id="account_asset_transfer_view_form" model="ir.ui.view">
<field name="name">account.asset.transfer.form</field>
<field name="model">account.asset.transfer</field>
<field name="arch" type="xml">
<form string="Transfer Asset">
<group>
<group>
<field name="date_transfer" />
<field name="company_id" groups="base.group_multi_company" />
<field name="currency_id" invisible="1" />
<field name="from_asset_value" />
<field name="to_asset_value" />
<field name="balance" invisible="1" />
</group>
<group>
<field name="transfer_journal_id" />
<field name="partner_id" invisible="1" />
<field name="analytic_account_id" invisible="1" />
<field name="analytic_tag_ids" invisible="1" />
</group>
</group>
<notebook>
<page name="to_asset" string="To New Asset">
<field
name="to_asset_ids"
context="{
'default_asset_value': balance,
'default_partner_id': partner_id,
'default_analytic_account_id': analytic_account_id,
'default_analytic_tag_ids': analytic_tag_ids,
}"
>
<tree editable="bottom">
<field name="asset_profile_id" />
<field name="asset_name" />
<field name="asset_value" />
<field name="partner_id" />
<field
name="analytic_account_id"
groups="analytic.group_analytic_accounting"
/>
<field
name="analytic_tag_ids"
groups="analytic.group_analytic_accounting"
widget="many2many_tags"
/>
</tree>
</field>
</page>
<page name="from_asset" string="From">
<field name="from_asset_ids" force_save="1" />
</page>
</notebook>
<separator string="Notes" colspan="4" />
<field name="note" nolabel="1" colspan="4" />
<separator colspan="4" />
<footer>
<button
string="Transfer"
name="transfer"
type="object"
class="oe_highlight"
/>
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_asset_transfer_from_list" model="ir.actions.server">
<field name="name">Transfer Asset</field>
<field name="groups_id" eval="[(4, ref('account.group_account_manager'))]" />
<field name="model_id" ref="account_asset_management.model_account_asset" />
<field
name="binding_model_id"
ref="account_asset_management.model_account_asset"
/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">
if records:
action = records.transfer()
</field>
</record>
</odoo>