mirror of
https://github.com/bringout/odoomates.git
synced 2026-04-18 04:11:59 +02:00
Initial commit: Odoomates Odoo packages (12 packages)
This commit is contained in:
commit
3b38c49bf0
526 changed files with 34983 additions and 0 deletions
47
odoo-bringout-odoomates-om_account_asset/README.md
Normal file
47
odoo-bringout-odoomates-om_account_asset/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# Odoo 16 Assets Management
|
||||
|
||||
Manage assets owned by a company or a person.
|
||||
Keeps track of depreciation's, and creates corresponding journal entries
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-odoomates-om_account_asset
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- account
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Odoo 16 Assets Management
|
||||
- **Version**: 16.0.1.3.0
|
||||
- **Category**: Accounting
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: False
|
||||
|
||||
## Source
|
||||
|
||||
Custom addon from bringout-odoomates vendor, addon `om_account_asset`.
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original LGPL-3 license from the addon.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Reports: doc/REPORTS.md
|
||||
- Security: doc/SECURITY.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
32
odoo-bringout-odoomates-om_account_asset/doc/ARCHITECTURE.md
Normal file
32
odoo-bringout-odoomates-om_account_asset/doc/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
U[Users] -->|HTTP| V[Views and QWeb Templates]
|
||||
V --> C[Controllers]
|
||||
V --> W[Wizards – Transient Models]
|
||||
C --> M[Models and ORM]
|
||||
W --> M
|
||||
M --> R[Reports]
|
||||
DX[Data XML] --> M
|
||||
S[Security – ACLs and Groups] -. enforces .-> M
|
||||
|
||||
subgraph Om_account_asset Module - om_account_asset
|
||||
direction LR
|
||||
M:::layer
|
||||
W:::layer
|
||||
C:::layer
|
||||
V:::layer
|
||||
R:::layer
|
||||
S:::layer
|
||||
DX:::layer
|
||||
end
|
||||
|
||||
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
|
||||
```
|
||||
|
||||
Notes
|
||||
- Views include tree/form/kanban templates and report templates.
|
||||
- Controllers provide website/portal routes when present.
|
||||
- Wizards are UI flows implemented with `models.TransientModel`.
|
||||
- Data XML loads data/demo records; Security defines groups and access.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Configuration
|
||||
|
||||
Refer to Odoo settings for om_account_asset. Configure related models, access rights, and options as needed.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Controllers
|
||||
|
||||
This module does not define custom HTTP controllers.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Dependencies
|
||||
|
||||
This addon depends on:
|
||||
|
||||
- [account](../../odoo-bringout-oca-ocb-account)
|
||||
4
odoo-bringout-odoomates-om_account_asset/doc/FAQ.md
Normal file
4
odoo-bringout-odoomates-om_account_asset/doc/FAQ.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# FAQ
|
||||
|
||||
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
|
||||
- Q: How to enable? A: Start server with --addon om_account_asset or install in UI.
|
||||
7
odoo-bringout-odoomates-om_account_asset/doc/INSTALL.md
Normal file
7
odoo-bringout-odoomates-om_account_asset/doc/INSTALL.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Install
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-odoomates-om_account_asset"
|
||||
# or
|
||||
uv pip install odoo-bringout-odoomates-om_account_asset"
|
||||
```
|
||||
17
odoo-bringout-odoomates-om_account_asset/doc/MODELS.md
Normal file
17
odoo-bringout-odoomates-om_account_asset/doc/MODELS.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Models
|
||||
|
||||
Detected core models and extensions in om_account_asset.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class account_asset_asset
|
||||
class account_asset_category
|
||||
class account_asset_depreciation_line
|
||||
class account_move
|
||||
class account_move_line
|
||||
class product_template
|
||||
```
|
||||
|
||||
Notes
|
||||
- Classes show model technical names; fields omitted for brevity.
|
||||
- Items listed under _inherit are extensions of existing models.
|
||||
6
odoo-bringout-odoomates-om_account_asset/doc/OVERVIEW.md
Normal file
6
odoo-bringout-odoomates-om_account_asset/doc/OVERVIEW.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Overview
|
||||
|
||||
Packaged Odoo addon: om_account_asset. Provides features documented in upstream Odoo 16 under this addon.
|
||||
|
||||
- Source: OCA/OCB 16.0, addon om_account_asset
|
||||
- License: LGPL-3
|
||||
27
odoo-bringout-odoomates-om_account_asset/doc/REPORTS.md
Normal file
27
odoo-bringout-odoomates-om_account_asset/doc/REPORTS.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Reports
|
||||
|
||||
Report definitions and templates in om_account_asset.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class AssetAssetReport
|
||||
Model <|-- AssetAssetReport
|
||||
```
|
||||
|
||||
## Available Reports
|
||||
|
||||
### Analytical/Dashboard Reports
|
||||
- **Assets Analysis** (Analysis/Dashboard)
|
||||
|
||||
|
||||
## Report Files
|
||||
|
||||
- **account_asset_report.py** (Python logic)
|
||||
- **account_asset_report_views.xml** (XML template/definition)
|
||||
- **__init__.py** (Python logic)
|
||||
|
||||
## Notes
|
||||
- Named reports above are accessible through Odoo's reporting menu
|
||||
- Python files define report logic and data processing
|
||||
- XML files contain report templates, definitions, and formatting
|
||||
- Reports are integrated with Odoo's printing and email systems
|
||||
41
odoo-bringout-odoomates-om_account_asset/doc/SECURITY.md
Normal file
41
odoo-bringout-odoomates-om_account_asset/doc/SECURITY.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Security
|
||||
|
||||
Access control and security definitions in om_account_asset.
|
||||
|
||||
## Access Control Lists (ACLs)
|
||||
|
||||
Model access permissions defined in:
|
||||
- **[ir.model.access.csv](../om_account_asset/security/ir.model.access.csv)**
|
||||
- 13 model access rules
|
||||
|
||||
## Record Rules
|
||||
|
||||
Row-level security rules defined in:
|
||||
|
||||
## Security Groups & Configuration
|
||||
|
||||
Security groups and permissions defined in:
|
||||
- **[account_asset_security.xml](../om_account_asset/security/account_asset_security.xml)**
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Security Layers"
|
||||
A[Users] --> B[Groups]
|
||||
B --> C[Access Control Lists]
|
||||
C --> D[Models]
|
||||
B --> E[Record Rules]
|
||||
E --> F[Individual Records]
|
||||
end
|
||||
```
|
||||
|
||||
Security files overview:
|
||||
- **[account_asset_security.xml](../om_account_asset/security/account_asset_security.xml)**
|
||||
- Security groups, categories, and XML-based rules
|
||||
- **[ir.model.access.csv](../om_account_asset/security/ir.model.access.csv)**
|
||||
- Model access permissions (CRUD rights)
|
||||
|
||||
Notes
|
||||
- Access Control Lists define which groups can access which models
|
||||
- Record Rules provide row-level security (filter records by user/group)
|
||||
- Security groups organize users and define permission sets
|
||||
- All security is enforced at the ORM level by Odoo
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Troubleshooting
|
||||
|
||||
- Ensure Python and Odoo environment matches repo guidance.
|
||||
- Check database connectivity and logs if startup fails.
|
||||
- Validate that dependent addons listed in DEPENDENCIES.md are installed.
|
||||
7
odoo-bringout-odoomates-om_account_asset/doc/USAGE.md
Normal file
7
odoo-bringout-odoomates-om_account_asset/doc/USAGE.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Usage
|
||||
|
||||
Start Odoo including this addon (from repo root):
|
||||
|
||||
```bash
|
||||
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon om_account_asset
|
||||
```
|
||||
9
odoo-bringout-odoomates-om_account_asset/doc/WIZARDS.md
Normal file
9
odoo-bringout-odoomates-om_account_asset/doc/WIZARDS.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Wizards
|
||||
|
||||
Transient models exposed as UI wizards in om_account_asset.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class AssetDepreciationConfirmationWizard
|
||||
class AssetModify
|
||||
```
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import wizard
|
||||
from . import models
|
||||
from . import report
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Odoo 16 Assets Management',
|
||||
'version': '16.0.1.3.0',
|
||||
'author': 'Odoo Mates, Odoo SA',
|
||||
'depends': ['account'],
|
||||
'description': """Manage assets owned by a company or a person.
|
||||
Keeps track of depreciation's, and creates corresponding journal entries""",
|
||||
'summary': 'Odoo 16 Assets Management',
|
||||
'category': 'Accounting',
|
||||
'sequence': 10,
|
||||
'website': 'https://www.odoomates.tech',
|
||||
'license': 'LGPL-3',
|
||||
'images': ['static/description/assets.gif'],
|
||||
'data': [
|
||||
'data/account_asset_data.xml',
|
||||
'security/account_asset_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'wizard/asset_depreciation_confirmation_wizard_views.xml',
|
||||
'wizard/asset_modify_views.xml',
|
||||
'views/account_asset_views.xml',
|
||||
'views/account_move_views.xml',
|
||||
'views/account_asset_templates.xml',
|
||||
'views/asset_category_views.xml',
|
||||
'views/product_views.xml',
|
||||
'report/account_asset_report_views.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'om_account_asset/static/src/scss/account_asset.scss',
|
||||
'om_account_asset/static/src/js/account_asset.js',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
('after', 'web/static/tests/legacy/views/kanban_tests.js', '/om_account_asset/static/tests/account_asset_tests.js'),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding='UTF-8'?>
|
||||
<odoo>
|
||||
|
||||
<record id="account_asset_cron" model="ir.cron">
|
||||
<field name="name">Account Asset: Generate asset entries</field>
|
||||
<field name="model_id" ref="model_account_asset_asset"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_generate_entries()</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">months</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
## Module <om_account_asset>
|
||||
|
||||
#### 24.10.2022
|
||||
#### Version 16.0.1.1.0
|
||||
##### ADD
|
||||
- asset category
|
||||
|
||||
#### 22.07.2022
|
||||
#### Version 16.0.1.0.0
|
||||
##### ADD
|
||||
- initial release
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
1357
odoo-bringout-odoomates-om_account_asset/om_account_asset/i18n/fr.po
Normal file
1357
odoo-bringout-odoomates-om_account_asset/om_account_asset/i18n/fr.po
Normal file
File diff suppressed because it is too large
Load diff
1319
odoo-bringout-odoomates-om_account_asset/om_account_asset/i18n/tr.po
Normal file
1319
odoo-bringout-odoomates-om_account_asset/om_account_asset/i18n/tr.po
Normal file
File diff suppressed because it is too large
Load diff
1288
odoo-bringout-odoomates-om_account_asset/om_account_asset/i18n/uk.po
Normal file
1288
odoo-bringout-odoomates-om_account_asset/om_account_asset/i18n/uk.po
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import account
|
||||
from . import account_asset
|
||||
from . import account_move
|
||||
from . import product
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
asset_depreciation_ids = fields.One2many('account.asset.depreciation.line', 'move_id',
|
||||
string='Assets Depreciation Lines')
|
||||
|
||||
def button_cancel(self):
|
||||
for move in self:
|
||||
for line in move.asset_depreciation_ids:
|
||||
line.move_posted_check = False
|
||||
return super(AccountMove, self).button_cancel()
|
||||
|
||||
def action_post(self):
|
||||
for move in self:
|
||||
for depreciation_line in move.asset_depreciation_ids:
|
||||
depreciation_line.post_lines_and_close_asset()
|
||||
return super(AccountMove, self).action_post()
|
||||
|
|
@ -0,0 +1,714 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import calendar
|
||||
from datetime import date, datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools import float_compare, float_is_zero
|
||||
|
||||
|
||||
class AccountAssetCategory(models.Model):
|
||||
_name = 'account.asset.category'
|
||||
_description = 'Asset category'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin', 'analytic.mixin']
|
||||
|
||||
exclude_types = ['asset_receivable', 'asset_cash', 'liability_payable',
|
||||
'liability_credit_card', 'equity', 'equity_unaffected']
|
||||
|
||||
active = fields.Boolean(default=True)
|
||||
name = fields.Char(required=True, index=True, string="Asset Type")
|
||||
account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account')
|
||||
# analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tag')
|
||||
account_asset_id = fields.Many2one('account.account', string='Asset Account',
|
||||
required=True,
|
||||
domain=[('account_type', 'not in', exclude_types), ('deprecated', '=', False)],
|
||||
help="Account used to record the purchase of the asset at its original price.")
|
||||
account_depreciation_id = fields.Many2one('account.account',
|
||||
string='Depreciation Entries: Asset Account',
|
||||
required=True, domain=[('account_type', 'not in', exclude_types), ('deprecated', '=', False)],
|
||||
help="Account used in the depreciation entries, to decrease the asset value.")
|
||||
account_depreciation_expense_id = fields.Many2one('account.account',
|
||||
string='Depreciation Entries: Expense Account',
|
||||
required=True,
|
||||
domain=[('account_type', 'not in', exclude_types), ('deprecated', '=', False)],
|
||||
help="Account used in the periodical entries,"
|
||||
" to record a part of the asset as expense.")
|
||||
journal_id = fields.Many2one('account.journal', string='Journal', required=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', required=True,
|
||||
default=lambda self: self.env.company)
|
||||
method = fields.Selection([('linear', 'Linear'), ('degressive', 'Degressive')],
|
||||
string='Computation Method', required=True, default='linear',
|
||||
help="Choose the method to use to compute the amount of depreciation lines.\n"
|
||||
" * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n"
|
||||
" * Degressive: Calculated on basis of: Residual Value * Degressive Factor")
|
||||
method_number = fields.Integer(string='Number of Depreciations', default=5,
|
||||
help="The number of depreciations needed to depreciate your asset")
|
||||
method_period = fields.Integer(string='Period Length', default=1,
|
||||
help="State here the time between 2 depreciations, in months", required=True)
|
||||
method_progress_factor = fields.Float('Degressive Factor', default=0.3)
|
||||
method_time = fields.Selection([('number', 'Number of Entries'), ('end', 'Ending Date')],
|
||||
string='Time Method', required=True, default='number',
|
||||
help="Choose the method to use to compute the dates and number of entries.\n"
|
||||
" * Number of Entries: Fix the number of entries and the time between 2 depreciations.\n"
|
||||
" * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.")
|
||||
method_end = fields.Date('Ending date')
|
||||
prorata = fields.Boolean(string='Prorata Temporis',
|
||||
help='Indicates that the first depreciation entry for this asset have to be done from the '
|
||||
'purchase date instead of the first of January')
|
||||
open_asset = fields.Boolean(string='Auto-Confirm Assets',
|
||||
help="Check this if you want to automatically confirm the assets "
|
||||
"of this category when created by invoices.")
|
||||
group_entries = fields.Boolean(string='Group Journal Entries',
|
||||
help="Check this if you want to group the generated entries by categories.")
|
||||
type = fields.Selection([('sale', 'Sale: Revenue Recognition'), ('purchase', 'Purchase: Asset')],
|
||||
required=True, index=True, default='purchase')
|
||||
date_first_depreciation = fields.Selection([
|
||||
('last_day_period', 'Based on Last Day of Purchase Period'),
|
||||
('manual', 'Manual (Defaulted on Purchase Date)')],
|
||||
string='Depreciation Dates', default='manual', required=True,
|
||||
help='The way to compute the date of the first depreciation.\n'
|
||||
' * Based on last day of purchase period: The depreciation dates will'
|
||||
' be based on the last day of the purchase month or the purchase'
|
||||
' year (depending on the periodicity of the depreciations).\n'
|
||||
' * Based on purchase date: The depreciation dates will be based on the purchase date.')
|
||||
|
||||
@api.onchange('account_asset_id')
|
||||
def onchange_account_asset(self):
|
||||
if self.type == "purchase":
|
||||
self.account_depreciation_id = self.account_asset_id
|
||||
elif self.type == "sale":
|
||||
self.account_depreciation_expense_id = self.account_asset_id
|
||||
|
||||
@api.onchange('type')
|
||||
def onchange_type(self):
|
||||
if self.type == 'sale':
|
||||
self.prorata = True
|
||||
self.method_period = 1
|
||||
else:
|
||||
self.method_period = 12
|
||||
|
||||
@api.onchange('method_time')
|
||||
def _onchange_method_time(self):
|
||||
if self.method_time != 'number':
|
||||
self.prorata = False
|
||||
|
||||
|
||||
class AccountAssetAsset(models.Model):
|
||||
_name = 'account.asset.asset'
|
||||
_description = 'Asset/Revenue Recognition'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin', 'analytic.mixin']
|
||||
|
||||
entry_count = fields.Integer(compute='_entry_count', string='# Asset Entries')
|
||||
name = fields.Char(string='Asset Name', required=True,
|
||||
readonly=True, states={'draft': [('readonly', False)]})
|
||||
code = fields.Char(string='Reference', size=32, readonly=True,
|
||||
states={'draft': [('readonly', False)]})
|
||||
value = fields.Monetary(string='Gross Value', required=True, readonly=True,
|
||||
states={'draft': [('readonly', False)]})
|
||||
currency_id = fields.Many2one('res.currency', string='Currency', required=True,
|
||||
readonly=True, states={'draft': [('readonly', False)]},
|
||||
default=lambda self: self.env.user.company_id.currency_id.id)
|
||||
company_id = fields.Many2one('res.company', string='Company', required=True,
|
||||
readonly=True, states={'draft': [('readonly', False)]},
|
||||
default=lambda self: self.env.company)
|
||||
note = fields.Text()
|
||||
category_id = fields.Many2one('account.asset.category', string='Category',
|
||||
required=True, change_default=True,
|
||||
readonly=True, states={'draft': [('readonly', False)]})
|
||||
date = fields.Date(string='Date', required=True, readonly=True,
|
||||
states={'draft': [('readonly', False)]}, default=fields.Date.context_today)
|
||||
state = fields.Selection([('draft', 'Draft'), ('open', 'Running'), ('close', 'Close')],
|
||||
'Status', required=True, copy=False, default='draft',
|
||||
help="When an asset is created, the status is 'Draft'.\n"
|
||||
"If the asset is confirmed, the status goes in 'Running' and the depreciation "
|
||||
"lines can be posted in the accounting.\n"
|
||||
"You can manually close an asset when the depreciation is over. If the last line"
|
||||
" of depreciation is posted, the asset automatically goes in that status.")
|
||||
active = fields.Boolean(default=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Partner',
|
||||
readonly=True, states={'draft': [('readonly', False)]})
|
||||
method = fields.Selection([('linear', 'Linear'), ('degressive', 'Degressive')],
|
||||
string='Computation Method', required=True, readonly=True,
|
||||
states={'draft': [('readonly', False)]}, default='linear',
|
||||
help="Choose the method to use to compute the amount of depreciation lines.\n * Linear:"
|
||||
" Calculated on basis of: Gross Value / Number of Depreciations\n"
|
||||
" * Degressive: Calculated on basis of: Residual Value * Degressive Factor")
|
||||
method_number = fields.Integer(string='Number of Depreciations', readonly=True,
|
||||
states={'draft': [('readonly', False)]}, default=5,
|
||||
help="The number of depreciations needed to depreciate your asset")
|
||||
method_period = fields.Integer(string='Number of Months in a Period', required=True,
|
||||
readonly=True, default=12, states={'draft': [('readonly', False)]},
|
||||
help="The amount of time between two depreciations, in months")
|
||||
method_end = fields.Date(string='Ending Date', readonly=True, states={'draft': [('readonly', False)]})
|
||||
method_progress_factor = fields.Float(string='Degressive Factor',
|
||||
readonly=True, default=0.3, states={'draft': [('readonly', False)]})
|
||||
value_residual = fields.Monetary(compute='_amount_residual', string='Residual Value')
|
||||
method_time = fields.Selection([('number', 'Number of Entries'), ('end', 'Ending Date')],
|
||||
string='Time Method', required=True, readonly=True, default='number',
|
||||
states={'draft': [('readonly', False)]},
|
||||
help="Choose the method to use to compute the dates and number of entries.\n"
|
||||
" * Number of Entries: Fix the number of entries and the time between 2 depreciations.\n"
|
||||
" * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.")
|
||||
prorata = fields.Boolean(string='Prorata Temporis', readonly=True, states={'draft': [('readonly', False)]},
|
||||
help='Indicates that the first depreciation entry for this asset'
|
||||
' have to be done from the asset date (purchase date) '
|
||||
'instead of the first January / Start date of fiscal year')
|
||||
depreciation_line_ids = fields.One2many('account.asset.depreciation.line', 'asset_id',
|
||||
string='Depreciation Lines', readonly=True,
|
||||
states={'draft': [('readonly', False)], 'open': [('readonly', False)]})
|
||||
salvage_value = fields.Monetary(string='Salvage Value', readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
help="It is the amount you plan to have that you cannot depreciate.")
|
||||
invoice_id = fields.Many2one('account.move', string='Invoice', states={'draft': [('readonly', False)]}, copy=False)
|
||||
type = fields.Selection(related="category_id.type", string='Type', required=True)
|
||||
account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account')
|
||||
# analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tag')
|
||||
date_first_depreciation = fields.Selection([
|
||||
('last_day_period', 'Based on Last Day of Purchase Period'),
|
||||
('manual', 'Manual')],
|
||||
string='Depreciation Dates', default='manual',
|
||||
readonly=True, states={'draft': [('readonly', False)]}, required=True,
|
||||
help='The way to compute the date of the first depreciation.\n'
|
||||
' * Based on last day of purchase period: The depreciation'
|
||||
' dates will be based on the last day of the purchase month or the '
|
||||
'purchase year (depending on the periodicity of the depreciations).\n'
|
||||
' * Based on purchase date: The depreciation dates will be based on the purchase date.\n')
|
||||
first_depreciation_manual_date = fields.Date(
|
||||
string='First Depreciation Date',
|
||||
readonly=True, states={'draft': [('readonly', False)]},
|
||||
help='Note that this date does not alter the computation of the first '
|
||||
'journal entry in case of prorata temporis assets. It simply changes its accounting date'
|
||||
)
|
||||
|
||||
def unlink(self):
|
||||
for asset in self:
|
||||
if asset.state in ['open', 'close']:
|
||||
raise UserError(_('You cannot delete a document that is in %s state.') % (asset.state,))
|
||||
for depreciation_line in asset.depreciation_line_ids:
|
||||
if depreciation_line.move_id:
|
||||
raise UserError(_('You cannot delete a document that contains posted entries.'))
|
||||
return super(AccountAssetAsset, self).unlink()
|
||||
|
||||
@api.model
|
||||
def _cron_generate_entries(self):
|
||||
self.compute_generated_entries(datetime.today())
|
||||
|
||||
@api.model
|
||||
def compute_generated_entries(self, date, asset_type=None):
|
||||
# Entries generated : one by grouped category and one by asset from ungrouped category
|
||||
created_move_ids = []
|
||||
type_domain = []
|
||||
if asset_type:
|
||||
type_domain = [('type', '=', asset_type)]
|
||||
|
||||
ungrouped_assets = self.env['account.asset.asset'].search(type_domain + [('state', '=', 'open'), ('category_id.group_entries', '=', False)])
|
||||
created_move_ids += ungrouped_assets._compute_entries(date, group_entries=False)
|
||||
|
||||
for grouped_category in self.env['account.asset.category'].search(type_domain + [('group_entries', '=', True)]):
|
||||
assets = self.env['account.asset.asset'].search([('state', '=', 'open'), ('category_id', '=', grouped_category.id)])
|
||||
created_move_ids += assets._compute_entries(date, group_entries=True)
|
||||
return created_move_ids
|
||||
|
||||
def _compute_board_amount(self, sequence, residual_amount, amount_to_depr,
|
||||
undone_dotation_number, posted_depreciation_line_ids,
|
||||
total_days, depreciation_date):
|
||||
amount = 0
|
||||
if sequence == undone_dotation_number:
|
||||
amount = residual_amount
|
||||
else:
|
||||
if self.method == 'linear':
|
||||
amount = amount_to_depr / (undone_dotation_number - len(posted_depreciation_line_ids))
|
||||
if self.prorata:
|
||||
amount = amount_to_depr / self.method_number
|
||||
if sequence == 1:
|
||||
date = self.date
|
||||
if self.method_period % 12 != 0:
|
||||
month_days = calendar.monthrange(date.year, date.month)[1]
|
||||
days = month_days - date.day + 1
|
||||
amount = (amount_to_depr / self.method_number) / month_days * days
|
||||
else:
|
||||
days = (self.company_id.compute_fiscalyear_dates(date)['date_to'] - date).days + 1
|
||||
amount = (amount_to_depr / self.method_number) / total_days * days
|
||||
elif self.method == 'degressive':
|
||||
amount = residual_amount * self.method_progress_factor
|
||||
if self.prorata:
|
||||
if sequence == 1:
|
||||
date = self.date
|
||||
if self.method_period % 12 != 0:
|
||||
month_days = calendar.monthrange(date.year, date.month)[1]
|
||||
days = month_days - date.day + 1
|
||||
amount = (residual_amount * self.method_progress_factor) / month_days * days
|
||||
else:
|
||||
days = (self.company_id.compute_fiscalyear_dates(date)['date_to'] - date).days + 1
|
||||
amount = (residual_amount * self.method_progress_factor) / total_days * days
|
||||
return amount
|
||||
|
||||
def _compute_board_undone_dotation_nb(self, depreciation_date, total_days):
|
||||
undone_dotation_number = self.method_number
|
||||
if self.method_time == 'end':
|
||||
end_date = self.method_end
|
||||
undone_dotation_number = 0
|
||||
while depreciation_date <= end_date:
|
||||
depreciation_date = date(depreciation_date.year, depreciation_date.month,
|
||||
depreciation_date.day) + relativedelta(months=+self.method_period)
|
||||
undone_dotation_number += 1
|
||||
if self.prorata:
|
||||
undone_dotation_number += 1
|
||||
return undone_dotation_number
|
||||
|
||||
def compute_depreciation_board(self):
|
||||
self.ensure_one()
|
||||
|
||||
posted_depreciation_line_ids = self.depreciation_line_ids.filtered(lambda x: x.move_check).sorted(key=lambda l: l.depreciation_date)
|
||||
unposted_depreciation_line_ids = self.depreciation_line_ids.filtered(lambda x: not x.move_check)
|
||||
|
||||
# Remove old unposted depreciation lines. We cannot use unlink() with One2many field
|
||||
commands = [(2, line_id.id, False) for line_id in unposted_depreciation_line_ids]
|
||||
|
||||
if self.value_residual != 0.0:
|
||||
amount_to_depr = residual_amount = self.value_residual
|
||||
|
||||
# if we already have some previous validated entries, starting date is last entry + method period
|
||||
if posted_depreciation_line_ids and posted_depreciation_line_ids[-1].depreciation_date:
|
||||
last_depreciation_date = fields.Date.from_string(posted_depreciation_line_ids[-1].depreciation_date)
|
||||
depreciation_date = last_depreciation_date + relativedelta(months=+self.method_period)
|
||||
else:
|
||||
# depreciation_date computed from the purchase date
|
||||
depreciation_date = self.date
|
||||
if self.date_first_depreciation == 'last_day_period':
|
||||
# depreciation_date = the last day of the month
|
||||
depreciation_date = depreciation_date + relativedelta(day=31)
|
||||
# ... or fiscalyear depending the number of period
|
||||
if self.method_period == 12:
|
||||
depreciation_date = depreciation_date + relativedelta(month=int(self.company_id.fiscalyear_last_month))
|
||||
depreciation_date = depreciation_date + relativedelta(day=int(self.company_id.fiscalyear_last_day))
|
||||
if depreciation_date < self.date:
|
||||
depreciation_date = depreciation_date + relativedelta(years=1)
|
||||
elif self.first_depreciation_manual_date and self.first_depreciation_manual_date != self.date:
|
||||
# depreciation_date set manually from the 'first_depreciation_manual_date' field
|
||||
depreciation_date = self.first_depreciation_manual_date
|
||||
total_days = (depreciation_date.year % 4) and 365 or 366
|
||||
month_day = depreciation_date.day
|
||||
undone_dotation_number = self._compute_board_undone_dotation_nb(depreciation_date, total_days)
|
||||
|
||||
for x in range(len(posted_depreciation_line_ids), undone_dotation_number):
|
||||
sequence = x + 1
|
||||
amount = self._compute_board_amount(sequence, residual_amount, amount_to_depr,
|
||||
undone_dotation_number, posted_depreciation_line_ids,
|
||||
total_days, depreciation_date)
|
||||
amount = self.currency_id.round(amount)
|
||||
if float_is_zero(amount, precision_rounding=self.currency_id.rounding):
|
||||
continue
|
||||
residual_amount -= amount
|
||||
vals = {
|
||||
'amount': amount,
|
||||
'asset_id': self.id,
|
||||
'sequence': sequence,
|
||||
'name': (self.code or '') + '/' + str(sequence),
|
||||
'remaining_value': residual_amount,
|
||||
'depreciated_value': self.value - (self.salvage_value + residual_amount),
|
||||
'depreciation_date': depreciation_date,
|
||||
}
|
||||
commands.append((0, False, vals))
|
||||
|
||||
depreciation_date = depreciation_date + relativedelta(months=+self.method_period)
|
||||
|
||||
if month_day > 28 and self.date_first_depreciation == 'manual':
|
||||
max_day_in_month = calendar.monthrange(depreciation_date.year, depreciation_date.month)[1]
|
||||
depreciation_date = depreciation_date.replace(day=min(max_day_in_month, month_day))
|
||||
|
||||
# datetime doesn't take into account that the number of days is not the same for each month
|
||||
if not self.prorata and self.method_period % 12 != 0 and self.date_first_depreciation == 'last_day_period':
|
||||
max_day_in_month = calendar.monthrange(depreciation_date.year, depreciation_date.month)[1]
|
||||
depreciation_date = depreciation_date.replace(day=max_day_in_month)
|
||||
|
||||
self.write({'depreciation_line_ids': commands})
|
||||
|
||||
return True
|
||||
|
||||
def validate(self):
|
||||
self.write({'state': 'open'})
|
||||
fields = [
|
||||
'method',
|
||||
'method_number',
|
||||
'method_period',
|
||||
'method_end',
|
||||
'method_progress_factor',
|
||||
'method_time',
|
||||
'salvage_value',
|
||||
'invoice_id',
|
||||
]
|
||||
ref_tracked_fields = self.env['account.asset.asset'].fields_get(fields)
|
||||
for asset in self:
|
||||
tracked_fields = ref_tracked_fields.copy()
|
||||
if asset.method == 'linear':
|
||||
del(tracked_fields['method_progress_factor'])
|
||||
if asset.method_time != 'end':
|
||||
del(tracked_fields['method_end'])
|
||||
else:
|
||||
del(tracked_fields['method_number'])
|
||||
dummy, tracking_value_ids = asset._mail_track(tracked_fields, dict.fromkeys(fields))
|
||||
asset.message_post(subject=_('Asset created'), tracking_value_ids=tracking_value_ids)
|
||||
|
||||
def _return_disposal_view(self, move_ids):
|
||||
name = _('Disposal Move')
|
||||
view_mode = 'form'
|
||||
if len(move_ids) > 1:
|
||||
name = _('Disposal Moves')
|
||||
view_mode = 'tree,form'
|
||||
return {
|
||||
'name': name,
|
||||
'view_type': 'form',
|
||||
'view_mode': view_mode,
|
||||
'res_model': 'account.move',
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'current',
|
||||
'res_id': move_ids[0],
|
||||
}
|
||||
|
||||
def _get_disposal_moves(self):
|
||||
move_ids = []
|
||||
for asset in self:
|
||||
unposted_depreciation_line_ids = asset.depreciation_line_ids.filtered(lambda x: not x.move_check)
|
||||
if unposted_depreciation_line_ids:
|
||||
old_values = {
|
||||
'method_end': asset.method_end,
|
||||
'method_number': asset.method_number,
|
||||
}
|
||||
|
||||
# Remove all unposted depr. lines
|
||||
commands = [(2, line_id.id, False) for line_id in unposted_depreciation_line_ids]
|
||||
|
||||
# Create a new depr. line with the residual amount and post it
|
||||
sequence = len(asset.depreciation_line_ids) - len(unposted_depreciation_line_ids) + 1
|
||||
today = fields.Datetime.today()
|
||||
vals = {
|
||||
'amount': asset.value_residual,
|
||||
'asset_id': asset.id,
|
||||
'sequence': sequence,
|
||||
'name': (asset.code or '') + '/' + str(sequence),
|
||||
'remaining_value': 0,
|
||||
'depreciated_value': asset.value - asset.salvage_value, # the asset is completely depreciated
|
||||
'depreciation_date': today,
|
||||
}
|
||||
commands.append((0, False, vals))
|
||||
asset.write({'depreciation_line_ids': commands, 'method_end': today, 'method_number': sequence})
|
||||
tracked_fields = self.env['account.asset.asset'].fields_get(['method_number', 'method_end'])
|
||||
changes, tracking_value_ids = asset._mail_track(tracked_fields, old_values)
|
||||
if changes:
|
||||
asset.message_post(subject=_('Asset sold or disposed. Accounting entry awaiting for validation.'), tracking_value_ids=tracking_value_ids)
|
||||
move_ids += asset.depreciation_line_ids[-1].create_move(post_move=False)
|
||||
|
||||
return move_ids
|
||||
|
||||
def set_to_close(self):
|
||||
move_ids = self._get_disposal_moves()
|
||||
if move_ids:
|
||||
return self._return_disposal_view(move_ids)
|
||||
# Fallback, as if we just clicked on the smartbutton
|
||||
return self.open_entries()
|
||||
|
||||
def set_to_draft(self):
|
||||
self.write({'state': 'draft'})
|
||||
|
||||
@api.depends('value', 'salvage_value', 'depreciation_line_ids.move_check', 'depreciation_line_ids.amount')
|
||||
def _amount_residual(self):
|
||||
for rec in self:
|
||||
total_amount = 0.0
|
||||
for line in rec.depreciation_line_ids:
|
||||
if line.move_check:
|
||||
total_amount += line.amount
|
||||
rec.value_residual = rec.value - total_amount - rec.salvage_value
|
||||
|
||||
@api.onchange('company_id')
|
||||
def onchange_company_id(self):
|
||||
self.currency_id = self.company_id.currency_id.id
|
||||
|
||||
@api.onchange('date_first_depreciation')
|
||||
def onchange_date_first_depreciation(self):
|
||||
for record in self:
|
||||
if record.date_first_depreciation == 'manual':
|
||||
record.first_depreciation_manual_date = record.date
|
||||
|
||||
@api.depends('depreciation_line_ids.move_id')
|
||||
def _entry_count(self):
|
||||
for asset in self:
|
||||
res = self.env['account.asset.depreciation.line'].search_count([('asset_id', '=', asset.id), ('move_id', '!=', False)])
|
||||
asset.entry_count = res or 0
|
||||
|
||||
@api.constrains('prorata', 'method_time')
|
||||
def _check_prorata(self):
|
||||
if self.prorata and self.method_time != 'number':
|
||||
raise ValidationError(_('Prorata temporis can be applied only for the "number of depreciations" time method.'))
|
||||
|
||||
@api.onchange('category_id')
|
||||
def onchange_category_id(self):
|
||||
vals = self.onchange_category_id_values(self.category_id.id)
|
||||
# We cannot use 'write' on an object that doesn't exist yet
|
||||
if vals:
|
||||
for k, v in vals['value'].items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def onchange_category_id_values(self, category_id):
|
||||
if category_id:
|
||||
category = self.env['account.asset.category'].browse(category_id)
|
||||
return {
|
||||
'value': {
|
||||
'method': category.method,
|
||||
'method_number': category.method_number,
|
||||
'method_time': category.method_time,
|
||||
'method_period': category.method_period,
|
||||
'method_progress_factor': category.method_progress_factor,
|
||||
'method_end': category.method_end,
|
||||
'prorata': category.prorata,
|
||||
'date_first_depreciation': category.date_first_depreciation,
|
||||
'account_analytic_id': category.account_analytic_id.id,
|
||||
'analytic_distribution': category.analytic_distribution,
|
||||
# 'analytic_tag_ids': [(6, 0, category.analytic_tag_ids.ids)],
|
||||
}
|
||||
}
|
||||
|
||||
@api.onchange('method_time')
|
||||
def onchange_method_time(self):
|
||||
if self.method_time != 'number':
|
||||
self.prorata = False
|
||||
|
||||
def copy_data(self, default=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
default['name'] = self.name + _(' (copy)')
|
||||
return super(AccountAssetAsset, self).copy_data(default)
|
||||
|
||||
def _compute_entries(self, date, group_entries=False):
|
||||
depreciation_ids = self.env['account.asset.depreciation.line'].search([
|
||||
('asset_id', 'in', self.ids), ('depreciation_date', '<=', date),
|
||||
('move_check', '=', False)])
|
||||
if group_entries:
|
||||
return depreciation_ids.create_grouped_move()
|
||||
return depreciation_ids.create_move()
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
assets = super(AccountAssetAsset, self.with_context(mail_create_nolog=True)).create(vals_list)
|
||||
for asset in assets:
|
||||
asset.sudo().compute_depreciation_board()
|
||||
return assets
|
||||
|
||||
def write(self, vals):
|
||||
res = super(AccountAssetAsset, self).write(vals)
|
||||
if 'depreciation_line_ids' not in vals and 'state' not in vals:
|
||||
for rec in self:
|
||||
rec.compute_depreciation_board()
|
||||
return res
|
||||
|
||||
def open_entries(self):
|
||||
move_ids = []
|
||||
for asset in self:
|
||||
for depreciation_line in asset.depreciation_line_ids:
|
||||
if depreciation_line.move_id:
|
||||
move_ids.append(depreciation_line.move_id.id)
|
||||
return {
|
||||
'name': _('Journal Entries'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'account.move',
|
||||
'view_id': False,
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': [('id', 'in', move_ids)],
|
||||
}
|
||||
|
||||
|
||||
class AccountAssetDepreciationLine(models.Model):
|
||||
_name = 'account.asset.depreciation.line'
|
||||
_description = 'Asset depreciation line'
|
||||
|
||||
name = fields.Char(string='Depreciation Name', required=True, index=True)
|
||||
sequence = fields.Integer(required=True)
|
||||
asset_id = fields.Many2one('account.asset.asset', string='Asset',
|
||||
required=True, ondelete='cascade')
|
||||
parent_state = fields.Selection(related='asset_id.state',
|
||||
string='State of Asset')
|
||||
amount = fields.Monetary(string='Current Depreciation',
|
||||
required=True)
|
||||
remaining_value = fields.Monetary(string='Next Period Depreciation',
|
||||
required=True)
|
||||
depreciated_value = fields.Monetary(string='Cumulative Depreciation',
|
||||
required=True)
|
||||
depreciation_date = fields.Date('Depreciation Date', index=True)
|
||||
move_id = fields.Many2one('account.move', string='Depreciation Entry')
|
||||
move_check = fields.Boolean(compute='_get_move_check', string='Linked',
|
||||
store=True)
|
||||
move_posted_check = fields.Boolean(compute='_get_move_posted_check',
|
||||
string='Posted', store=True)
|
||||
currency_id = fields.Many2one('res.currency', string='Currency',
|
||||
related='asset_id.currency_id',
|
||||
readonly=True)
|
||||
|
||||
@api.depends('move_id')
|
||||
def _get_move_check(self):
|
||||
for line in self:
|
||||
line.move_check = bool(line.move_id)
|
||||
|
||||
@api.depends('move_id.state')
|
||||
def _get_move_posted_check(self):
|
||||
for line in self:
|
||||
line.move_posted_check = True if line.move_id and line.move_id.state == 'posted' else False
|
||||
|
||||
def create_move(self, post_move=True):
|
||||
created_moves = self.env['account.move']
|
||||
for line in self:
|
||||
if line.move_id:
|
||||
raise UserError(_('This depreciation is already linked to a journal entry. Please post or delete it.'))
|
||||
move_vals = self._prepare_move(line)
|
||||
move = self.env['account.move'].create(move_vals)
|
||||
line.write({'move_id': move.id, 'move_check': True})
|
||||
created_moves |= move
|
||||
|
||||
if post_move and created_moves:
|
||||
created_moves.filtered(lambda m: any(m.asset_depreciation_ids.mapped('asset_id.category_id.open_asset'))).action_post()
|
||||
return [x.id for x in created_moves]
|
||||
|
||||
def _prepare_move(self, line):
|
||||
category_id = line.asset_id.category_id
|
||||
account_analytic_id = line.asset_id.account_analytic_id
|
||||
# analytic_tag_ids = line.asset_id.analytic_tag_ids
|
||||
analytic_distribution = line.asset_id.analytic_distribution
|
||||
depreciation_date = self.env.context.get('depreciation_date') or line.depreciation_date or fields.Date.context_today(self)
|
||||
company_currency = line.asset_id.company_id.currency_id
|
||||
current_currency = line.asset_id.currency_id
|
||||
prec = company_currency.decimal_places
|
||||
amount = current_currency._convert(
|
||||
line.amount, company_currency, line.asset_id.company_id, depreciation_date)
|
||||
asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, len(line.asset_id.depreciation_line_ids))
|
||||
move_line_1 = {
|
||||
'name': asset_name,
|
||||
'account_id': category_id.account_depreciation_id.id,
|
||||
'debit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount,
|
||||
'credit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0,
|
||||
'partner_id': line.asset_id.partner_id.id,
|
||||
# 'analytic_account_id': account_analytic_id.id if category_id.type == 'sale' else False,
|
||||
# 'analytic_tag_ids': [(6, 0, analytic_tag_ids.ids)] if category_id.type == 'sale' else False,
|
||||
'analytic_distribution': analytic_distribution,
|
||||
'currency_id': company_currency != current_currency and current_currency.id or company_currency.id,
|
||||
'amount_currency': - 1.0 * line.amount
|
||||
}
|
||||
move_line_2 = {
|
||||
'name': asset_name,
|
||||
'account_id': category_id.account_depreciation_expense_id.id,
|
||||
'credit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount,
|
||||
'debit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0,
|
||||
'partner_id': line.asset_id.partner_id.id,
|
||||
# 'analytic_account_id': account_analytic_id.id if category_id.type == 'purchase' else False,
|
||||
# 'analytic_tag_ids': [(6, 0, analytic_tag_ids.ids)] if category_id.type == 'purchase' else False,
|
||||
'analytic_distribution': analytic_distribution,
|
||||
'currency_id': company_currency != current_currency and current_currency.id or company_currency.id,
|
||||
'amount_currency': line.amount,
|
||||
}
|
||||
move_vals = {
|
||||
'ref': line.asset_id.code,
|
||||
'date': depreciation_date or False,
|
||||
'journal_id': category_id.journal_id.id,
|
||||
'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)],
|
||||
}
|
||||
return move_vals
|
||||
|
||||
def _prepare_move_grouped(self):
|
||||
asset_id = self[0].asset_id
|
||||
category_id = asset_id.category_id # we can suppose that all lines have the same category
|
||||
account_analytic_id = asset_id.account_analytic_id
|
||||
# analytic_tag_ids = asset_id.analytic_tag_ids
|
||||
analytic_distribution = asset_id.analytic_distribution
|
||||
|
||||
depreciation_date = self.env.context.get('depreciation_date') or fields.Date.context_today(self)
|
||||
amount = 0.0
|
||||
for line in self:
|
||||
# Sum amount of all depreciation lines
|
||||
company_currency = line.asset_id.company_id.currency_id
|
||||
current_currency = line.asset_id.currency_id
|
||||
company = line.asset_id.company_id
|
||||
amount += current_currency._convert(line.amount, company_currency, company, fields.Date.today())
|
||||
|
||||
name = category_id.name + _(' (grouped)')
|
||||
move_line_1 = {
|
||||
'name': name,
|
||||
'account_id': category_id.account_depreciation_id.id,
|
||||
'debit': 0.0,
|
||||
'credit': amount,
|
||||
'journal_id': category_id.journal_id.id,
|
||||
'analytic_account_id': account_analytic_id.id if category_id.type == 'sale' else False,
|
||||
'analytic_distribution': analytic_distribution,
|
||||
# 'analytic_tag_ids': [(6, 0, analytic_tag_ids.ids)] if category_id.type == 'sale' else False,
|
||||
}
|
||||
move_line_2 = {
|
||||
'name': name,
|
||||
'account_id': category_id.account_depreciation_expense_id.id,
|
||||
'credit': 0.0,
|
||||
'debit': amount,
|
||||
'journal_id': category_id.journal_id.id,
|
||||
'analytic_account_id': account_analytic_id.id if category_id.type == 'purchase' else False,
|
||||
'analytic_distribution': analytic_distribution,
|
||||
# 'analytic_tag_ids': [(6, 0, analytic_tag_ids.ids)] if category_id.type == 'purchase' else False,
|
||||
}
|
||||
move_vals = {
|
||||
'ref': category_id.name,
|
||||
'date': depreciation_date or False,
|
||||
'journal_id': category_id.journal_id.id,
|
||||
'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)],
|
||||
}
|
||||
|
||||
return move_vals
|
||||
|
||||
def create_grouped_move(self, post_move=True):
|
||||
if not self.exists():
|
||||
return []
|
||||
|
||||
created_moves = self.env['account.move']
|
||||
move = self.env['account.move'].create(self._prepare_move_grouped())
|
||||
self.write({'move_id': move.id, 'move_check': True})
|
||||
created_moves |= move
|
||||
|
||||
if post_move and created_moves:
|
||||
created_moves.action_post()
|
||||
return [x.id for x in created_moves]
|
||||
|
||||
def post_lines_and_close_asset(self):
|
||||
# we re-evaluate the assets to determine whether we can close them
|
||||
for line in self:
|
||||
line.log_message_when_posted()
|
||||
asset = line.asset_id
|
||||
if asset.currency_id.is_zero(asset.value_residual):
|
||||
asset.message_post(body=_("Document closed."))
|
||||
asset.write({'state': 'close'})
|
||||
|
||||
def log_message_when_posted(self):
|
||||
def _format_message(message_description, tracked_values):
|
||||
message = ''
|
||||
if message_description:
|
||||
message = '<span>%s</span>' % message_description
|
||||
for name, values in tracked_values.items():
|
||||
message += '<div> • <b>%s</b>: ' % name
|
||||
message += '%s</div>' % values
|
||||
return message
|
||||
|
||||
for line in self:
|
||||
if line.move_id and line.move_id.state == 'draft':
|
||||
partner_name = line.asset_id.partner_id.name
|
||||
currency_name = line.asset_id.currency_id.name
|
||||
msg_values = {_('Currency'): currency_name, _('Amount'): line.amount}
|
||||
if partner_name:
|
||||
msg_values[_('Partner')] = partner_name
|
||||
msg = _format_message(_('Depreciation line posted.'), msg_values)
|
||||
line.asset_id.message_post(body=msg)
|
||||
|
||||
def unlink(self):
|
||||
for record in self:
|
||||
if record.move_check:
|
||||
if record.asset_id.category_id.type == 'purchase':
|
||||
msg = _("You cannot delete posted depreciation lines.")
|
||||
else:
|
||||
msg = _("You cannot delete posted installment lines.")
|
||||
raise UserError(msg)
|
||||
return super(AccountAssetDepreciationLine, self).unlink()
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
asset_ids = fields.One2many('account.asset.asset', 'invoice_id',
|
||||
string="Assets")
|
||||
|
||||
def button_draft(self):
|
||||
res = super(AccountMove, self).button_draft()
|
||||
for move in self:
|
||||
if any(asset_id.state != 'draft' for asset_id in move.asset_ids):
|
||||
raise ValidationError(_(
|
||||
'You cannot reset to draft for an entry having a posted asset'))
|
||||
if move.asset_ids:
|
||||
move.asset_ids.sudo().write({'active': False})
|
||||
for asset in move.asset_ids:
|
||||
asset.sudo().message_post(body=_("Vendor bill cancelled."))
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def _refund_cleanup_lines(self, lines):
|
||||
result = super(AccountMove, self)._refund_cleanup_lines(lines)
|
||||
for i, line in enumerate(lines):
|
||||
for name, field in line._fields.items():
|
||||
if name == 'asset_category_id':
|
||||
result[i][2][name] = False
|
||||
break
|
||||
return result
|
||||
|
||||
def action_cancel(self):
|
||||
res = super(AccountMove, self).action_cancel()
|
||||
assets = self.env['account.asset.asset'].sudo().search(
|
||||
[('invoice_id', 'in', self.ids)])
|
||||
if assets:
|
||||
assets.sudo().write({'active': False})
|
||||
for asset in assets:
|
||||
asset.sudo().message_post(body=_("Vendor bill cancelled."))
|
||||
return res
|
||||
|
||||
def action_post(self):
|
||||
result = super(AccountMove, self).action_post()
|
||||
for inv in self:
|
||||
context = dict(self.env.context)
|
||||
context.pop('default_type', None)
|
||||
for mv_line in inv.invoice_line_ids:
|
||||
mv_line.with_context(context).asset_create()
|
||||
return result
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = 'account.move.line'
|
||||
|
||||
asset_category_id = fields.Many2one('account.asset.category', string='Asset Category')
|
||||
asset_start_date = fields.Date(string='Asset Start Date', compute='_get_asset_date', readonly=True, store=True)
|
||||
asset_end_date = fields.Date(string='Asset End Date', compute='_get_asset_date', readonly=True, store=True)
|
||||
asset_mrr = fields.Float(string='Monthly Recurring Revenue', compute='_get_asset_date', readonly=True,
|
||||
store=True)
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super(AccountMoveLine, self).default_get(fields)
|
||||
if self.env.context.get('create_bill') and not self.asset_category_id:
|
||||
if self.product_id and self.move_id.move_type == 'out_invoice' and \
|
||||
self.product_id.product_tmpl_id.deferred_revenue_category_id:
|
||||
self.asset_category_id = self.product_id.product_tmpl_id.deferred_revenue_category_id.id
|
||||
elif self.product_id and self.product_id.product_tmpl_id.asset_category_id and \
|
||||
self.move_id.move_type == 'in_invoice':
|
||||
self.asset_category_id = self.product_id.product_tmpl_id.asset_category_id.id
|
||||
self.onchange_asset_category_id()
|
||||
return res
|
||||
|
||||
@api.depends('asset_category_id', 'move_id.invoice_date')
|
||||
def _get_asset_date(self):
|
||||
for rec in self:
|
||||
rec.asset_mrr = 0
|
||||
rec.asset_start_date = False
|
||||
rec.asset_end_date = False
|
||||
cat = rec.asset_category_id
|
||||
if cat:
|
||||
if cat.method_number == 0 or cat.method_period == 0:
|
||||
raise UserError(_('The number of depreciations or the period length of '
|
||||
'your asset category cannot be 0.'))
|
||||
months = cat.method_number * cat.method_period
|
||||
if rec.move_id.move_type in ['out_invoice', 'out_refund']:
|
||||
price_subtotal = self.currency_id._convert(
|
||||
self.price_subtotal,
|
||||
self.company_currency_id,
|
||||
self.company_id,
|
||||
self.move_id.invoice_date or fields.Date.context_today(
|
||||
self))
|
||||
|
||||
rec.asset_mrr = price_subtotal / months
|
||||
if rec.move_id.invoice_date:
|
||||
start_date = rec.move_id.invoice_date.replace(day=1)
|
||||
end_date = (start_date + relativedelta(months=months, days=-1))
|
||||
rec.asset_start_date = start_date
|
||||
rec.asset_end_date = end_date
|
||||
|
||||
def asset_create(self):
|
||||
if self.asset_category_id:
|
||||
price_subtotal = self.currency_id._convert(
|
||||
self.price_subtotal,
|
||||
self.company_currency_id,
|
||||
self.company_id,
|
||||
self.move_id.invoice_date or fields.Date.context_today(
|
||||
self))
|
||||
vals = {
|
||||
'name': self.name,
|
||||
'code': self.name or False,
|
||||
'category_id': self.asset_category_id.id,
|
||||
'value': price_subtotal,
|
||||
'partner_id': self.move_id.partner_id.id,
|
||||
'company_id': self.move_id.company_id.id,
|
||||
'currency_id': self.move_id.company_currency_id.id,
|
||||
'date': self.move_id.invoice_date or self.move_id.date,
|
||||
'invoice_id': self.move_id.id,
|
||||
}
|
||||
changed_vals = self.env['account.asset.asset'].onchange_category_id_values(vals['category_id'])
|
||||
vals.update(changed_vals['value'])
|
||||
asset = self.env['account.asset.asset'].create(vals)
|
||||
if self.asset_category_id.open_asset:
|
||||
if asset.date_first_depreciation == 'manual':
|
||||
asset.first_depreciation_manual_date = asset.date
|
||||
asset.validate()
|
||||
return True
|
||||
|
||||
@api.onchange('asset_category_id', 'product_uom_id')
|
||||
def onchange_asset_category_id(self):
|
||||
if self.move_id.move_type == 'out_invoice' and self.asset_category_id:
|
||||
self.account_id = self.asset_category_id.account_asset_id.id
|
||||
elif self.move_id.move_type == 'in_invoice' and self.asset_category_id:
|
||||
self.account_id = self.asset_category_id.account_asset_id.id
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _inverse_product_id(self):
|
||||
res = super(AccountMoveLine, self)._inverse_product_id()
|
||||
for rec in self:
|
||||
if rec.product_id:
|
||||
if rec.move_id.move_type == 'out_invoice':
|
||||
rec.asset_category_id = rec.product_id.product_tmpl_id.deferred_revenue_category_id.id
|
||||
elif rec.move_id.move_type == 'in_invoice':
|
||||
rec.asset_category_id = rec.product_id.product_tmpl_id.asset_category_id.id
|
||||
|
||||
def get_invoice_line_account(self, type, product, fpos, company):
|
||||
return product.asset_category_id.account_asset_id or super(AccountMoveLine, self).get_invoice_line_account(type, product, fpos, company)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
|
||||
asset_category_id = fields.Many2one('account.asset.category', string='Asset Type',
|
||||
company_dependent=True, ondelete="restrict")
|
||||
deferred_revenue_category_id = fields.Many2one('account.asset.category', string='Deferred Revenue Type',
|
||||
company_dependent=True, ondelete="restrict")
|
||||
|
||||
def _get_asset_accounts(self):
|
||||
res = super(ProductTemplate, self)._get_asset_accounts()
|
||||
if self.asset_category_id:
|
||||
res['stock_input'] = self.property_account_expense_id
|
||||
if self.deferred_revenue_category_id:
|
||||
res['stock_output'] = self.property_account_income_id
|
||||
return res
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import account_asset_report
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, tools
|
||||
|
||||
|
||||
class AssetAssetReport(models.Model):
|
||||
_name = "asset.asset.report"
|
||||
_description = "Assets Analysis"
|
||||
_auto = False
|
||||
|
||||
name = fields.Char(string='Year', required=False, readonly=True)
|
||||
date = fields.Date(readonly=True)
|
||||
depreciation_date = fields.Date(string='Depreciation Date', readonly=True)
|
||||
asset_id = fields.Many2one('account.asset.asset', string='Asset', readonly=True)
|
||||
asset_category_id = fields.Many2one('account.asset.category', string='Asset category', readonly=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Partner', readonly=True)
|
||||
state = fields.Selection([('draft', 'Draft'), ('open', 'Running'), ('close', 'Close')], string='Status', readonly=True)
|
||||
depreciation_value = fields.Float(string='Amount of Depreciation Lines', readonly=True)
|
||||
installment_value = fields.Float(string='Amount of Installment Lines', readonly=True)
|
||||
move_check = fields.Boolean(string='Posted', readonly=True)
|
||||
installment_nbr = fields.Integer(string='Installment Count', readonly=True)
|
||||
depreciation_nbr = fields.Integer(string='Depreciation Count', readonly=True)
|
||||
gross_value = fields.Float(string='Gross Amount', readonly=True)
|
||||
posted_value = fields.Float(string='Posted Amount', readonly=True)
|
||||
unposted_value = fields.Float(string='Unposted Amount', readonly=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', readonly=True)
|
||||
|
||||
def init(self):
|
||||
tools.drop_view_if_exists(self._cr, 'asset_asset_report')
|
||||
self._cr.execute("""
|
||||
create or replace view asset_asset_report as (
|
||||
select
|
||||
min(dl.id) as id,
|
||||
dl.name as name,
|
||||
dl.depreciation_date as depreciation_date,
|
||||
a.date as date,
|
||||
(CASE WHEN dlmin.id = min(dl.id)
|
||||
THEN a.value
|
||||
ELSE 0
|
||||
END) as gross_value,
|
||||
dl.amount as depreciation_value,
|
||||
dl.amount as installment_value,
|
||||
(CASE WHEN dl.move_check
|
||||
THEN dl.amount
|
||||
ELSE 0
|
||||
END) as posted_value,
|
||||
(CASE WHEN NOT dl.move_check
|
||||
THEN dl.amount
|
||||
ELSE 0
|
||||
END) as unposted_value,
|
||||
dl.asset_id as asset_id,
|
||||
dl.move_check as move_check,
|
||||
a.category_id as asset_category_id,
|
||||
a.partner_id as partner_id,
|
||||
a.state as state,
|
||||
count(dl.*) as installment_nbr,
|
||||
count(dl.*) as depreciation_nbr,
|
||||
a.company_id as company_id
|
||||
from account_asset_depreciation_line dl
|
||||
left join account_asset_asset a on (dl.asset_id=a.id)
|
||||
left join (select min(d.id) as id,ac.id as ac_id from account_asset_depreciation_line as d inner join account_asset_asset as ac ON (ac.id=d.asset_id) group by ac_id) as dlmin on dlmin.ac_id=a.id
|
||||
where a.active is true
|
||||
group by
|
||||
dl.amount,dl.asset_id,dl.depreciation_date,dl.name,
|
||||
a.date, dl.move_check, a.state, a.category_id, a.partner_id, a.company_id,
|
||||
a.value, a.id, a.salvage_value, dlmin.id
|
||||
)""")
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="action_account_asset_report_pivot" model="ir.ui.view">
|
||||
<field name="name">asset.asset.report.pivot</field>
|
||||
<field name="model">asset.asset.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Assets Analysis" disable_linking="True">
|
||||
<field name="asset_category_id" type="row"/>
|
||||
<field name="gross_value" type="measure"/>
|
||||
<field name="unposted_value" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_asset_report_graph" model="ir.ui.view">
|
||||
<field name="name">asset.asset.report.graph</field>
|
||||
<field name="model">asset.asset.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Assets Analysis">
|
||||
<field name="asset_category_id" type="row"/>
|
||||
<field name="gross_value" type="measure"/>
|
||||
<field name="unposted_value" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_asset_asset_report_search" model="ir.ui.view">
|
||||
<field name="name">asset.asset.report.search</field>
|
||||
<field name="model">asset.asset.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Assets Analysis">
|
||||
<field name="date"/>
|
||||
<field name="depreciation_date"/>
|
||||
<filter string="Draft" name="draft" domain="[('state','=','draft')]"
|
||||
help="Assets in draft state"/>
|
||||
<filter string="Running" name="running" domain="[('state','=','open')]"
|
||||
help="Assets in running state"/>
|
||||
<separator/>
|
||||
<filter string="Posted" name="posted" domain="[('move_check','=',True)]"
|
||||
help="Posted depreciation lines" context="{'unposted_value_visible': 0}"/>
|
||||
<field name="asset_id"/>
|
||||
<field name="asset_category_id"/>
|
||||
<group expand="0" string="Extended Filters...">
|
||||
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<group expand="1" string="Group By">
|
||||
<filter string="Asset" name="asset" context="{'group_by':'asset_id'}"/>
|
||||
<filter string="Asset Category" name="asset_category"
|
||||
context="{'group_by':'asset_category_id'}"/>
|
||||
<filter string="Company" name="company" context="{'group_by':'company_id'}"
|
||||
groups="base.group_multi_company"/>
|
||||
<separator/>
|
||||
<filter string="Purchase Month" name="purchase_month" help="Date of asset purchase"
|
||||
context="{'group_by':'date:month'}"/>
|
||||
<filter string="Depreciation Month" name="deprecation_month" help="Date of depreciation"
|
||||
context="{'group_by':'depreciation_date:month'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_asset_asset_report" model="ir.actions.act_window">
|
||||
<field name="name">Assets Analysis</field>
|
||||
<field name="res_model">asset.asset.report</field>
|
||||
<field name="view_mode">graph,pivot</field>
|
||||
<field name="search_view_id" ref="view_asset_asset_report_search"/>
|
||||
<field name="domain">[('asset_category_id.type', '=', 'purchase')]</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
No content
|
||||
</p><p>
|
||||
From this report, you can have an overview on all depreciations. The
|
||||
search bar can also be used to personalize your assets depreciation reporting.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_action_asset_asset_report"
|
||||
name="Assets"
|
||||
action="action_asset_asset_report"
|
||||
parent="account.account_reports_management_menu"
|
||||
sequence="21"/>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="account_asset_category_multi_company_rule" model="ir.rule">
|
||||
<field name="name">Account Asset Category multi-company</field>
|
||||
<field ref="model_account_asset_category" name="model_id"/>
|
||||
<field eval="True" name="global"/>
|
||||
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
|
||||
</record>
|
||||
|
||||
<record id="account_asset_asset_multi_company_rule" model="ir.rule">
|
||||
<field name="name">Account Asset multi-company</field>
|
||||
<field ref="model_account_asset_asset" name="model_id"/>
|
||||
<field eval="True" name="global"/>
|
||||
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_account_asset_category,account.asset.category,model_account_asset_category,account.group_account_user,1,0,0,0
|
||||
access_asset_depreciation_confirmation_wizard,access_asset_depreciation_confirmation_wizard,model_asset_depreciation_confirmation_wizard,,1,1,1,0
|
||||
access_asset_modify,access_asset_modify,model_asset_modify,,1,1,1,0
|
||||
access_account_asset_asset,account.asset.asset,model_account_asset_asset,account.group_account_user,1,0,0,0
|
||||
access_account_asset_category_manager,account.asset.category,model_account_asset_category,account.group_account_manager,1,1,1,1
|
||||
access_account_asset_asset_manager,account.asset.asset,model_account_asset_asset,account.group_account_manager,1,1,1,1
|
||||
access_account_asset_depreciation_line,account.asset.depreciation.line,model_account_asset_depreciation_line,account.group_account_user,1,0,0,0
|
||||
access_account_asset_depreciation_line_manager,account.asset.depreciation.line,model_account_asset_depreciation_line,account.group_account_manager,1,1,1,1
|
||||
access_asset_asset_report,asset.asset.report,model_asset_asset_report,account.group_account_user,1,0,0,0
|
||||
access_asset_asset_report_manager,asset.asset.report,model_asset_asset_report,account.group_account_manager,1,1,1,1
|
||||
access_account_asset_category_invoicing_payment,account.asset.category,model_account_asset_category,account.group_account_invoice,1,0,0,0
|
||||
access_account_asset_asset_invoicing_payment,account.asset.asset,model_account_asset_asset,account.group_account_invoice,1,0,1,0
|
||||
access_account_asset_depreciation_line_invoicing_payment,account.asset.depreciation.line,model_account_asset_depreciation_line,account.group_account_invoice,1,0,1,0
|
||||
|
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1,023 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70"><defs><path id="a" d="M4 0h61c4 0 5 1 5 5v60c0 4-1 5-5 5H4c-3 0-4-1-4-5V5c0-4 1-5 4-5z"/><linearGradient id="c" x1="100%" x2="0%" y1="0%" y2="100%"><stop offset="0%" stop-color="#DA956B"/><stop offset="100%" stop-color="#CC7039"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><path fill="url(#c)" d="M0 0H70V70H0z"/><path fill="#FFF" fill-opacity=".383" d="M4 1h61c2.667 0 4.333.667 5 2V0H0v3c.667-1.333 2-2 4-2z"/><path fill="#393939" d="M4 69c-2 0-4-.146-4-4.09V40.738L18.16 24H52l1 2.045v6.137l-10.585 11.3 10.05 4.09L37.071 69H4z" opacity=".324"/><path fill="#000" fill-opacity=".383" d="M4 69h61c2.667 0 4.333-1 5-3v4H0v-4c.667 2 2 3 4 3z"/><path fill="#000" d="M53 42.084v5.66c0 1.837-1.111 3.34-3.556 3.34h-28c-2.466 0-4.444-1.503-4.444-3.34V28.34c0-1.837 1.978-3.34 4.444-3.34H49c2.444 0 4 1.368 4 3.205v5.88H37c-2.667 0-4 1.857-4 3.957 0 2.1 1.333 4.042 4 4.042h16zm-15-1.39a2.656 2.656 0 0 1-2.667-2.652A2.656 2.656 0 0 1 38 35.39a2.656 2.656 0 0 1 2.667 2.653A2.656 2.656 0 0 1 38 40.695z" opacity=".3"/><path fill="#FFF" d="M53 40.084v5.66c0 1.837-1.111 3.34-3.556 3.34h-28c-2.466 0-4.444-1.503-4.444-3.34V26.34c0-1.837 1.978-3.34 4.444-3.34H49c2.444 0 4 1.368 4 3.205v5.88H37c-2.667 0-4 1.857-4 3.957 0 2.1 1.333 4.042 4 4.042h16zm-15-1.39a2.656 2.656 0 0 1-2.667-2.652A2.656 2.656 0 0 1 38 33.39a2.656 2.656 0 0 1 2.667 2.653A2.656 2.656 0 0 1 38 38.695z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -0,0 +1,84 @@
|
|||
<section class="oe_container oe_dark">
|
||||
<div class="col-md-12">
|
||||
<h2 class="oe_slogan" style="font-size: 35px;color:#2C0091"><b>Odoo 16 Asset Management</b></h2>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<div style="text-align:center;">
|
||||
<p class="fa fa-hand-o-right" style="color:CRIMSON;font-size: 25px;">
|
||||
<span style="color:#2dd280;font-size: 15px;">Manage assets owned by a company or a person.</span>
|
||||
</p><br/>
|
||||
<p class="fa fa-hand-o-right" style="color:CRIMSON;font-size: 25px;">
|
||||
<span style="color:#2dd280;font-size: 15px;">Keeps track of depreciation's, and creates corresponding journal entries</span>
|
||||
</p><br/>
|
||||
|
||||
</div>
|
||||
<br/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<div class="oe_centeralign oe_websiteonly">
|
||||
<h4 class="oe_slogan"><a href="https://www.youtube.com/watch?v=KudvDOTvx2I" target="_blank" style="color: #FFFFFF !important; border-radius: 0; background-color: #9c676e; border-color: #005ca7; padding: 15px; font-weight: bold;">
|
||||
<i class="fa fa-youtube"></i>
|
||||
Watch on YouTube
|
||||
</a></h4>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h3 class="oe_slogan" style="color:#332c3c;font-size: 28px;">Asset Category</h3>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="asset_types.png">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h3 class="oe_slogan" style="color:#332c3c;font-size: 28px;">Assets</h3>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="assets.png">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<hr style="width: 100%;height: 4px;background: #2C0091;margin: 0px 0px;">
|
||||
<hr style="width: 100%;height: 4px;background: #148963;margin: 0px 0px;">
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row ">
|
||||
<div class="oe_slogan text-center">
|
||||
<img src="odoo_mates.png"/>
|
||||
<div style="color:#269900;">
|
||||
<h3 style="color:#2C0091;font-size: 25px;">If you need any help or want more features, just contact us:</h3><br>
|
||||
<h3 style="color:#2C0091;font-size: 20px;">Email: <a href="odoomates@gmail.com">odoomates@gmail.com</a> <br></h3>
|
||||
</div>
|
||||
<div class="oe_slogan">
|
||||
<h2>
|
||||
<a target="_blank" href="https://www.facebook.com/odoomate/" target="new">
|
||||
<i class="fa fa-facebook-square" style="font-size:38px;"></i>
|
||||
</a>
|
||||
<a target="_blank" href="https://twitter.com/odoomates/" target="new">
|
||||
<i class="fa fa-twitter" style="font-size:38px;"></i>
|
||||
</a>
|
||||
<a href="#" target="_blank">
|
||||
<i class="fa fa-linkedin" style="font-size:38px;"></i>
|
||||
</a>
|
||||
<a target="_blank" href="https://www.youtube.com/channel/UCVKlUZP7HAhdQgs-9iTJklQ">
|
||||
<i class="fa fa-youtube-play" style="font-size:38px;"></i>
|
||||
</a>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<hr style="width: 100%;height: 4px;background: #148963;margin: 0px 0px;">
|
||||
<hr style="width: 100%;height: 4px;background: #2C0091;margin: 0px 0px;">
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
|
|
@ -0,0 +1,87 @@
|
|||
odoo.define('om_account_asset.widget', function(require) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* The purpose of this widget is to shows a toggle button on depreciation and
|
||||
* installment lines for posted/unposted line. When clicked, it calls the method
|
||||
* create_move on the object account.asset.depreciation.line.
|
||||
* Note that this widget can only work on the account.asset.depreciation.line
|
||||
* model as some of its fields are harcoded.
|
||||
*/
|
||||
|
||||
var AbstractField = require('web.AbstractField');
|
||||
var core = require('web.core');
|
||||
var registry = require('web.field_registry');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
var AccountAssetWidget = AbstractField.extend({
|
||||
events: _.extend({}, AbstractField.prototype.events, {
|
||||
'click': '_onClick',
|
||||
}),
|
||||
description: "",
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
isSet: function () {
|
||||
return true; // it should always be displayed, whatever its value
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_render: function () {
|
||||
var className = '';
|
||||
var disabled = true;
|
||||
var title;
|
||||
if (this.recordData.move_posted_check) {
|
||||
className = 'o_is_posted';
|
||||
title = _t('Posted');
|
||||
} else if (this.recordData.move_check) {
|
||||
className = 'o_unposted';
|
||||
title = _t('Accounting entries waiting for manual verification');
|
||||
} else {
|
||||
disabled = false;
|
||||
title = _t('Unposted');
|
||||
}
|
||||
var $button = $('<button/>', {
|
||||
type: 'button',
|
||||
title: title,
|
||||
disabled: disabled,
|
||||
}).addClass('btn btn-link fa fa-circle o_deprec_lines_toggler ' + className);
|
||||
this.$el.html($button);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
_onClick: function (event) {
|
||||
event.stopPropagation();
|
||||
this.trigger_up('button_clicked', {
|
||||
attrs: {
|
||||
name: 'create_move',
|
||||
type: 'object',
|
||||
},
|
||||
record: this.record,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
registry.add("deprec_lines_toggler", AccountAssetWidget);
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
.o_web_client .o_deprec_lines_toggler {
|
||||
color: theme-color('danger');
|
||||
&.o_is_posted {
|
||||
color: theme-color('success');
|
||||
}
|
||||
&.o_unposted {
|
||||
color: theme-color('warning');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
odoo.define('om_account_asset.widget_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var FormView = require('web.FormView');
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
var createView = testUtils.createView;
|
||||
|
||||
QUnit.module('fields', {}, function () {
|
||||
|
||||
QUnit.module('om_account_asset', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
asset: {
|
||||
fields: {
|
||||
display_name: { string: "Displayed name", type: "char" },
|
||||
line_ids: {
|
||||
string: "Lines",
|
||||
type: "one2many",
|
||||
relation: 'line',
|
||||
relation_field: 'asset_id',
|
||||
},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
display_name: "asset name",
|
||||
line_ids: [1, 2, 3, 4],
|
||||
}],
|
||||
},
|
||||
line: {
|
||||
fields: {
|
||||
move_check: {string: "Move Check", type: 'boolean'},
|
||||
move_posted_check: {string: "Move Posted Check", type: 'boolean'},
|
||||
asset_id: {string: "Asset", type: 'many2one', relation: 'asset'},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
move_check: true,
|
||||
move_posted_check: true,
|
||||
}, {
|
||||
id: 2,
|
||||
move_check: false,
|
||||
move_posted_check: true,
|
||||
}, {
|
||||
id: 3,
|
||||
move_check: true,
|
||||
move_posted_check: false,
|
||||
}, {
|
||||
id: 4,
|
||||
move_check: false,
|
||||
move_posted_check: false,
|
||||
}],
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('basic rendering', function (assert) {
|
||||
assert.expect(18);
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'asset',
|
||||
data: this.data,
|
||||
arch: '<form string="Asset">' +
|
||||
'<sheet>' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="line_ids">' +
|
||||
'<tree>' +
|
||||
'<field name="move_check" widget="deprec_lines_toggler"/>' +
|
||||
'<field name="move_posted_check" invisible="1"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
// check the header
|
||||
assert.strictEqual(form.$('thead th').text(), "", "toggler column should have no title");
|
||||
|
||||
// check the classnames
|
||||
assert.ok(form.$('.o_deprec_lines_toggler_cell:nth(0) button').hasClass('o_is_posted'),
|
||||
"first line toggler should have classname 'o_is_posted'");
|
||||
assert.ok(!form.$('.o_deprec_lines_toggler_cell:nth(0) button').hasClass('o_unposted'),
|
||||
"first line toggler should not have classname 'o_unposted'");
|
||||
|
||||
assert.ok(form.$('.o_deprec_lines_toggler_cell:nth(1) button').hasClass('o_is_posted'),
|
||||
"second line toggler should have classname 'o_is_posted'");
|
||||
assert.ok(!form.$('.o_deprec_lines_toggler_cell:nth(1) button').hasClass('o_unposted'),
|
||||
"second line toggler should not have classname 'o_unposted'");
|
||||
|
||||
assert.ok(!form.$('.o_deprec_lines_toggler_cell:nth(2) button').hasClass('o_is_posted'),
|
||||
"third line toggler should not have classname 'o_is_posted'");
|
||||
assert.ok(form.$('.o_deprec_lines_toggler_cell:nth(2) button').hasClass('o_unposted'),
|
||||
"third line toggler should have classname 'o_unposted'");
|
||||
|
||||
assert.ok(!form.$('.o_deprec_lines_toggler_cell:nth(3) button').hasClass('o_is_posted'),
|
||||
"fourth line toggler should not have classname 'o_is_posted'");
|
||||
assert.ok(!form.$('.o_deprec_lines_toggler_cell:nth(3) button').hasClass('o_unposted'),
|
||||
"fourth line toggler should not have classname 'o_unposted'");
|
||||
|
||||
// check the titles
|
||||
assert.strictEqual(form.$('.o_deprec_lines_toggler_cell:nth(0) button').attr('title'),
|
||||
'Posted', "first line toggler should have correct title");
|
||||
assert.strictEqual(form.$('.o_deprec_lines_toggler_cell:nth(1) button').attr('title'),
|
||||
'Posted', "second line toggler should have correct title");
|
||||
assert.strictEqual(form.$('.o_deprec_lines_toggler_cell:nth(2) button').attr('title'),
|
||||
'Accounting entries waiting for manual verification',
|
||||
"third line toggler should have correct title");
|
||||
assert.strictEqual(form.$('.o_deprec_lines_toggler_cell:nth(3) button').attr('title'),
|
||||
'Unposted', "fourth line toggler should have correct title");
|
||||
|
||||
// check disabled property
|
||||
assert.ok(form.$('.o_deprec_lines_toggler_cell:nth(0) button').attr('disabled'),
|
||||
"first line toggle should be disabled");
|
||||
assert.ok(form.$('.o_deprec_lines_toggler_cell:nth(1) button').attr('disabled'),
|
||||
"second line toggle should be disabled");
|
||||
assert.ok(form.$('.o_deprec_lines_toggler_cell:nth(2) button').attr('disabled'),
|
||||
"third line toggle should be disabled");
|
||||
assert.ok(!form.$('.o_deprec_lines_toggler_cell:nth(3) button').attr('disabled'),
|
||||
"fourth line toggle should not be disabled");
|
||||
|
||||
// check the visibility: the widget should always be visible, regardless its value
|
||||
assert.strictEqual(form.$('.o_deprec_lines_toggler:visible').length, 4,
|
||||
"all togglers should be visible");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('click events are correctly triggered', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'asset',
|
||||
data: this.data,
|
||||
arch: '<form string="Asset">' +
|
||||
'<sheet>' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="line_ids">' +
|
||||
'<tree>' +
|
||||
'<field name="move_check" widget="deprec_lines_toggler"/>' +
|
||||
'<field name="move_posted_check" invisible="1"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
intercepts: {
|
||||
execute_action: function (event) {
|
||||
var data = event.data;
|
||||
assert.strictEqual(data.env.model, 'line', "should have correct model");
|
||||
assert.strictEqual(data.action_data.name, 'create_move',
|
||||
"should call correct method");
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
// click on last row toggler
|
||||
form.$('.o_deprec_lines_toggler_cell:nth(3) button').click();
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="account_asset_category_fixedassets_test0" model="account.asset.category">
|
||||
<field name="account_depreciation_id" ref="xfa"/>
|
||||
<field name="account_depreciation_expense_id" ref="a_expense"/>
|
||||
<field name="account_asset_id" ref="xfa"/>
|
||||
<field name="journal_id" ref="expenses_journal"/>
|
||||
<field name="name">Hardware - 3 Years</field>
|
||||
<field name="method_number">3</field>
|
||||
<field name="method_period">12</field>
|
||||
<field name="open_asset">True</field>
|
||||
</record>
|
||||
|
||||
<record id="account_asset_category_fixedassets_test1" model="account.asset.category">
|
||||
<field name="account_depreciation_id" ref="xfa"/>
|
||||
<field name="account_depreciation_expense_id" ref="a_expense"/>
|
||||
<field name="account_asset_id" ref="xfa"/>
|
||||
<field name="journal_id" ref="expenses_journal"/>
|
||||
<field name="name">Cars - 5 Years</field>
|
||||
<field name="method_number">5</field>
|
||||
<field name="method_period">12</field>
|
||||
</record>
|
||||
|
||||
<record id="account_asset_asset_vehicles_test0" model="account.asset.asset">
|
||||
<field eval="2000.0" name="salvage_value"/>
|
||||
<field name="state">open</field>
|
||||
<field eval="12" name="method_period"/>
|
||||
<field eval="5" name="method_number"/>
|
||||
<field name="name">CEO's Car</field>
|
||||
<field eval="12000.0" name="value"/>
|
||||
<field name="category_id" ref="account_asset_category_fixedassets_test0"/>
|
||||
</record>
|
||||
|
||||
<record id="account_asset_asset_cab_test0" model="account.asset.asset">
|
||||
<field name="method_end" eval="(DateTime.now().replace(month=8, day=11) + timedelta(days=3*365)).strftime('%Y-%m-%d')"/>
|
||||
<field eval="0.0" name="salvage_value"/>
|
||||
<field name="method_time">end</field>
|
||||
<field name="name">V6 Engine and 10 inches tires</field>
|
||||
<field eval="2800.0" name="value"/>
|
||||
<field name="category_id" ref="account_asset_category_fixedassets_test0"/>
|
||||
</record>
|
||||
|
||||
<record id="account_asset_asset_office_test0" model="account.asset.asset">
|
||||
<field eval="1" name="prorata"/>
|
||||
<field eval="100000.0" name="salvage_value"/>
|
||||
<field name="state">open</field>
|
||||
<field eval="12" name="method_period"/>
|
||||
<field eval="3" name="method_number"/>
|
||||
<field name="date" eval="time.strftime('%Y-01-01')"/>
|
||||
<field name="name">Office</field>
|
||||
<field eval="500000.0" name="value"/>
|
||||
<field name="category_id" ref="account_asset_category_fixedassets_test0"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_account_asset
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import tools
|
||||
from odoo.tests import common
|
||||
from odoo.modules.module import get_resource_path
|
||||
|
||||
|
||||
class TestAccountAsset(common.TransactionCase):
|
||||
|
||||
def _load(self, module, *args):
|
||||
tools.convert_file(self.cr, 'account_asset',
|
||||
get_resource_path(module, *args),
|
||||
{}, 'init', False, 'test', self.registry._assertion_report)
|
||||
|
||||
def test_00_account_asset_asset(self):
|
||||
self._load('account', 'test', 'account_minimal_test.xml')
|
||||
self._load('account_asset', 'test', 'account_asset_demo_test.xml')
|
||||
|
||||
# In order to test the process of Account Asset, I perform a action to confirm Account Asset.
|
||||
self.browse_ref("account_asset.account_asset_asset_vehicles_test0").validate()
|
||||
|
||||
# I check Asset is now in Open state.
|
||||
self.assertEqual(self.browse_ref("account_asset.account_asset_asset_vehicles_test0").state, 'open',
|
||||
'Asset should be in Open state')
|
||||
|
||||
# I compute depreciation lines for asset of CEOs Car.
|
||||
self.browse_ref("account_asset.account_asset_asset_vehicles_test0").compute_depreciation_board()
|
||||
value = self.browse_ref("account_asset.account_asset_asset_vehicles_test0")
|
||||
self.assertEqual(value.method_number, len(value.depreciation_line_ids),
|
||||
'Depreciation lines not created correctly')
|
||||
|
||||
# I create account move for all depreciation lines.
|
||||
ids = self.env['account.asset.depreciation.line'].search([('asset_id', '=', self.ref('account_asset.account_asset_asset_vehicles_test0'))])
|
||||
for line in ids:
|
||||
line.create_move()
|
||||
|
||||
# I check the move line is created.
|
||||
asset = self.env['account.asset.asset'].browse([self.ref("account_asset.account_asset_asset_vehicles_test0")])[0]
|
||||
self.assertEqual(len(asset.depreciation_line_ids), asset.entry_count,
|
||||
'Move lines not created correctly')
|
||||
|
||||
# I Check that After creating all the moves of depreciation lines the state "Close".
|
||||
self.assertEqual(self.browse_ref("account_asset.account_asset_asset_vehicles_test0").state, 'close',
|
||||
'State of asset should be close')
|
||||
|
||||
# WIZARD
|
||||
# I create a record to change the duration of asset for calculating depreciation.
|
||||
account_asset_asset_office0 = self.browse_ref('account_asset.account_asset_asset_office_test0')
|
||||
asset_modify_number_0 = self.env['asset.modify'].create({
|
||||
'name': 'Test reason',
|
||||
'method_number': 10.0,
|
||||
}).with_context({'active_id': account_asset_asset_office0.id})
|
||||
# I change the duration.
|
||||
asset_modify_number_0.with_context({'active_id': account_asset_asset_office0.id}).modify()
|
||||
|
||||
# I check the proper depreciation lines created.
|
||||
self.assertEqual(account_asset_asset_office0.method_number, len(account_asset_asset_office0.depreciation_line_ids))
|
||||
# I compute a asset on period.
|
||||
context = {
|
||||
"active_ids": [self.ref("account_asset.menu_asset_depreciation_confirmation_wizard")],
|
||||
"active_id": self.ref('account_asset.menu_asset_depreciation_confirmation_wizard'),
|
||||
'type': 'sale'
|
||||
}
|
||||
asset_compute_period_0 = self.env['asset.depreciation.confirmation.wizard'].create({})
|
||||
asset_compute_period_0.with_context(context).asset_compute()
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="account_assets_js_backend" model="ir.asset">
|
||||
<field name="name">Account Assets</field>
|
||||
<field name="bundle">web.assets_backend</field>
|
||||
<field name="path">/om_account_asset/static/src/js/account_asset.js</field>
|
||||
<field name="sequence" eval="17"/>
|
||||
</record>
|
||||
|
||||
<record id="account_assets_scss_backend" model="ir.asset">
|
||||
<field name="name">aAccount Assets SCSS</field>
|
||||
<field name="bundle">web.assets_backend</field>
|
||||
<field name="path">/om_account_asset/static/src/scss/account_asset.scss</field>
|
||||
<field name="sequence" eval="17"/>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- <template id="qunit_suite" name="account_asset_tests" inherit_id="web.qunit_suite">-->
|
||||
<!-- <xpath expr="//t[@t-set='head']" position="inside">-->
|
||||
<!-- <script type="text/javascript" src="/om_account_asset/static/tests/account_asset_tests.js"></script>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- </template>-->
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_account_asset_asset_form" model="ir.ui.view">
|
||||
<field name="name">account.asset.asset.form</field>
|
||||
<field name="model">account.asset.asset</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Asset">
|
||||
<header>
|
||||
<button name="validate" states="draft" string="Confirm" type="object" class="oe_highlight"/>
|
||||
<button type="object" name="compute_depreciation_board" string="Compute Depreciation"
|
||||
states="draft"/>
|
||||
<button name="set_to_close" states="open" string="Sell or Dispose" type="object"
|
||||
class="oe_highlight"/>
|
||||
<button name="set_to_draft" string="Set to Draft" type="object"
|
||||
attrs="{'invisible': ['|', ('entry_count', '!=', 0), ('state', '!=', 'open')]}"/>
|
||||
<button name="%(action_asset_modify)d" states="open" string="Modify Depreciation" type="action"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,open"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" name="open_entries" type="object" icon="fa-pencil">
|
||||
<field string="Items" name="entry_count" widget="statinfo"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="e.g. Laptop iBook"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="category_id" string="Asset Category" domain="[('type', '=', 'purchase')]"
|
||||
context="{'default_type': 'purchase'}" help="Category of asset"/>
|
||||
<field name="code"/>
|
||||
<field name="date" help="Date of asset"/>
|
||||
<field name="date_first_depreciation"/>
|
||||
<field name="first_depreciation_manual_date"
|
||||
attrs="{'invisible': [('date_first_depreciation', '!=', 'manual')],
|
||||
'required': [('date_first_depreciation', '=', 'manual')]}"/>
|
||||
<field name="type" invisible="1"/>
|
||||
<field name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="currency_id" groups="base.group_multi_currency"/>
|
||||
<field name="company_id" options="{'no_create': True}"
|
||||
groups="base.group_multi_company"/>
|
||||
<field name="value" widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}" help="Gross value of asset"/>
|
||||
<field name="salvage_value" widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
attrs="{'invisible': [('type','=','sale')]}"/>
|
||||
<field name="value_residual" widget="monetary" options="{'currency_field': 'currency_id'}"/>
|
||||
<field name="partner_id" string="Vendor" widget="res_partner_many2one"
|
||||
context="{'res_partner_search_mode': 'supplier'}"/>
|
||||
<field name="invoice_id" string="Invoice" options="{'no_create': True}"/>
|
||||
<field name="analytic_distribution" widget="analytic_distribution"/>
|
||||
<!-- <field name="analytic_tag_ids"-->
|
||||
<!-- groups="analytic.group_analytic_accounting" widget="many2many_tags"/>-->
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Depreciation Board">
|
||||
<field name="depreciation_line_ids" mode="tree" options="{'reload_whole_on_button': true}">
|
||||
<tree string="Depreciation Lines" decoration-info="(move_check == False)"
|
||||
create="false">
|
||||
<field name="depreciation_date"/>
|
||||
<field name="amount" widget="monetary" string="Depreciation"/>
|
||||
<field name="depreciated_value" readonly="1"/>
|
||||
<field name="remaining_value" readonly="1" widget="monetary" string="Residual"/>
|
||||
<field name="move_check" widget="deprec_lines_toggler"
|
||||
attrs="{'invisible': [('parent_state', '!=', 'open')]}"/>
|
||||
<field name="move_posted_check" invisible="1"/>
|
||||
<field name="parent_state" invisible="1"/>
|
||||
</tree>
|
||||
<form string="Depreciation Lines" create="false">
|
||||
<group>
|
||||
<group>
|
||||
<field name="parent_state" invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="sequence"/>
|
||||
<field name="move_id"/>
|
||||
<field name="move_check"/>
|
||||
<field name="parent_state" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="amount" widget="monetary"/>
|
||||
<field name="depreciation_date"/>
|
||||
<field name="depreciated_value"/>
|
||||
<field name="remaining_value"/>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Depreciation Information">
|
||||
<group>
|
||||
<field name="method" widget="radio" attrs="{'invisible': [('type','=','sale')]}"/>
|
||||
<field name="method_progress_factor"
|
||||
attrs="{'invisible':[('method','=','linear')], 'required':[('method','=','degressive')]}"/>
|
||||
<field name="method_time" string="Time Method Based On"
|
||||
widget="radio" attrs="{'invisible': [('type','!=','purchase')]}"/>
|
||||
<field name="prorata" attrs="{'invisible': [('method_time','=','end')]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="method_number"
|
||||
attrs="{'invisible':[('method_time','=','end')],
|
||||
'required':[('method_time','=','number')]}"/>
|
||||
<field name="method_period"/>
|
||||
<field name="method_end"
|
||||
attrs="{'required': [('method_time','=','end')],
|
||||
'invisible':[('method_time','=','number')]}"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_asset_asset_kanban" model="ir.ui.view">
|
||||
<field name="name">account.asset.asset.kanban</field>
|
||||
<field name="model">account.asset.asset</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_mobile">
|
||||
<field name="name"/>
|
||||
<field name="category_id"/>
|
||||
<field name="date"/>
|
||||
<field name="state"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_global_click">
|
||||
<div class="row mb4">
|
||||
<div class="col-6">
|
||||
<strong>
|
||||
<span>
|
||||
<t t-esc="record.name.value"/>
|
||||
</span>
|
||||
</strong>
|
||||
</div>
|
||||
<div class="col-6 text-end">
|
||||
<strong>
|
||||
<t t-esc="record.date.value"/>
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 text-muted">
|
||||
<span>
|
||||
<t t-esc="record.category_id.value"/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<span class="float-right text-end">
|
||||
<field name="state" widget="kanban_label_selection"
|
||||
options="{'classes': {'draft': 'primary', 'open': 'success', 'close': 'default'}}"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_asset_asset_purchase_tree" model="ir.ui.view">
|
||||
<field name="name">account.asset.asset.purchase.tree</field>
|
||||
<field name="model">account.asset.asset</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Assets" decoration-info="(state == 'draft')" decoration-muted="(state == 'close')">
|
||||
<field name="name"/>
|
||||
<field name="category_id" string="Asset Category"/>
|
||||
<field name="date"/>
|
||||
<field name="partner_id" string="Vendor"/>
|
||||
<field name="value"/>
|
||||
<field name="value_residual" widget="monetary"/>
|
||||
<field name="currency_id" groups="base.group_multi_currency"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_asset_search" model="ir.ui.view">
|
||||
<field name="name">account.asset.asset.search</field>
|
||||
<field name="model">account.asset.asset</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Asset Account">
|
||||
<field name="name" string="Asset"/>
|
||||
<field name="date"/>
|
||||
<filter string="Current" name="current" domain="[('state','in', ('draft','open'))]"
|
||||
help="Assets in draft and open states"/>
|
||||
<filter string="Closed" name="closed" domain="[('state','=', 'close')]"
|
||||
help="Assets in closed state"/>
|
||||
<field name="category_id" string="Asset Category"/>
|
||||
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Date" name="month" domain="[]" context="{'group_by':'date'}"/>
|
||||
<filter string="Asset Category" name="category" domain="[]" context="{'group_by':'category_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_account_asset_asset_form">
|
||||
<field name="name">Assets</field>
|
||||
<field name="res_model">account.asset.asset</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
<field name="view_id" ref="view_account_asset_asset_purchase_tree"/>
|
||||
<field name="domain">[('category_id.type', '=', 'purchase')]</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_action_account_asset_asset_form"
|
||||
parent="account.menu_finance_entries_management"
|
||||
action="action_account_asset_asset_form"
|
||||
sequence="101"
|
||||
groups="account.group_account_manager"/>
|
||||
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_invoice_asset_category" model="ir.ui.view">
|
||||
<field name="name">account.move.supplier.form</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='account_id']" position="before">
|
||||
<field string="Asset Category" name="asset_category_id" force_save="1"
|
||||
attrs="{'column_invisible': [('parent.move_type', 'not in', ['in_invoice'])]}"
|
||||
domain="[('type','=','purchase')]" context="{'default_type':'purchase'}"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='line_ids']/tree/field[@name='account_id']" position="before">
|
||||
<field string="Asset Category" name="asset_category_id" invisible="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_account_asset_category_form" model="ir.ui.view">
|
||||
<field name="name">account.asset.category.form</field>
|
||||
<field name="model">account.asset.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Asset category">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name" string="Asset Type" class="oe_edit_only"
|
||||
attrs="{'invisible': [('type','!=','purchase')]}"/>
|
||||
<label for="name" string="Deferred Revenue Type" class="oe_edit_only"
|
||||
attrs="{'invisible': [('type','==','purchase')]}"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="e.g. Computers"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group string="Journal Entries">
|
||||
<group>
|
||||
<field name="journal_id"/>
|
||||
<div>
|
||||
<label for="account_asset_id" attrs="{'invisible': [('type','!=','purchase')]}"
|
||||
style="font-weight: bold" class="o_light_label"/>
|
||||
<label for="account_asset_id" string="Deferred Revenue Account"
|
||||
attrs="{'invisible': [('type','!=','sale')]}" style="font-weight: bold"
|
||||
class="o_light_label"/>
|
||||
</div>
|
||||
<field name="account_asset_id" nolabel="1"
|
||||
domain="[('company_id', '=', company_id)]"/>
|
||||
<div>
|
||||
<label for="account_depreciation_id" attrs="{'invisible': [('type','!=','purchase')]}"
|
||||
style="font-weight: bold" class="o_light_label"/>
|
||||
<label for="account_depreciation_id" string="Recognition Income Account"
|
||||
attrs="{'invisible': [('type','!=','sale')]}"
|
||||
style="font-weight: bold" class="o_light_label"/>
|
||||
</div>
|
||||
<field name="account_depreciation_id" nolabel="1"
|
||||
domain="[('company_id', '=', company_id)]"/>
|
||||
</group>
|
||||
<group>
|
||||
<div>
|
||||
<label for="account_depreciation_expense_id"
|
||||
attrs="{'invisible': [('type','!=','purchase')]}"
|
||||
style="font-weight: bold" class="o_light_label"/>
|
||||
<label for="account_depreciation_expense_id" string="Recognition Account"
|
||||
attrs="{'invisible': [('type','!=','sale')]}"
|
||||
style="font-weight: bold" class="o_light_label"/>
|
||||
</div>
|
||||
<field name="account_depreciation_expense_id" nolabel="1"
|
||||
domain="[('company_id', '=', company_id)]"/>
|
||||
<field name="account_analytic_id" domain="[('company_id', '=', company_id)]"
|
||||
groups="analytic.group_analytic_accounting"/>
|
||||
<field name="analytic_distribution" widget="analytic_distribution"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Periodicity">
|
||||
<field name="method_time" string="Time Method Based On" widget="radio"
|
||||
attrs="{'invisible': [('type','!=','purchase')]}"/>
|
||||
<field name="method_number" string="Number of Entries"
|
||||
attrs="{'invisible':['|',('method_time','!=','number'), ('type','=', False)],
|
||||
'required':[('method_time','=','number')]}"/>
|
||||
<label for="method_period" string="One Entry Every"/>
|
||||
<div>
|
||||
<field name="method_period" nolabel="1"
|
||||
attrs="{'invisible': [('type','=', False)]}" class="oe_inline"/>
|
||||
months
|
||||
</div>
|
||||
<field name="method_end"
|
||||
attrs="{'required': [('method_time','=','end')], 'invisible':[('method_time','!=','end')]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="type" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="company_id" options="{'no_create': True}"
|
||||
groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<group string="Additional Options">
|
||||
<field name="open_asset"/>
|
||||
<field name="group_entries"/>
|
||||
<field name="date_first_depreciation"/>
|
||||
</group>
|
||||
<group attrs="{'invisible': [('type','=','sale')]}" string="Depreciation Method">
|
||||
<field name="method" widget="radio"/>
|
||||
<field name="method_progress_factor"
|
||||
attrs="{'invisible':[('method','=','linear')], 'required':[('method','=','degressive')]}"/>
|
||||
<field name="prorata" attrs="{'invisible': [('method_time','=','end')]}"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_asset_asset_category_kanban" model="ir.ui.view">
|
||||
<field name="name">account.asset.category.kanban</field>
|
||||
<field name="model">account.asset.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_mobile">
|
||||
<field name="name"/>
|
||||
<field name="journal_id"/>
|
||||
<field name="method"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
|
||||
<div class="row mb4">
|
||||
<div class="col-6">
|
||||
<strong>
|
||||
<span>
|
||||
<t t-esc="record.name.value"/>
|
||||
</span>
|
||||
</strong>
|
||||
</div>
|
||||
<div class="col-6 text-end">
|
||||
<span class="badge badge-pill">
|
||||
<strong>
|
||||
<t t-esc="record.method.value"/>
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<t t-esc="record.journal_id.value"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_asset_category_tree" model="ir.ui.view">
|
||||
<field name="name">account.asset.category.tree</field>
|
||||
<field name="model">account.asset.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Asset category">
|
||||
<field name="name"/>
|
||||
<field name="journal_id"/>
|
||||
<field name="method"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_asset_category_search" model="ir.ui.view">
|
||||
<field name="name">account.asset.category.search</field>
|
||||
<field name="model">account.asset.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Asset Category">
|
||||
<filter string="Sales" name="sales" domain="[('type','=', 'sale')]" help="Deferred Revenues"/>
|
||||
<filter string="Purchase" name="purchase" domain="[('type','=', 'purchase')]" help="Assets"/>
|
||||
<field name="name" string="Category"/>
|
||||
<field name="journal_id"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Type" name="type" domain="[]" context="{'group_by':'type'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_asset_asset_list_normal_purchase" model="ir.actions.act_window">
|
||||
<field name="name">Asset Category</field>
|
||||
<field name="res_model">account.asset.category</field>
|
||||
<field name="domain">[('type', '=', 'purchase')]</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
<field name="context">{'default_type': 'purchase'}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_action_account_asset_asset_list_normal_purchase"
|
||||
parent="account.account_management_menu"
|
||||
action="action_account_asset_asset_list_normal_purchase"
|
||||
sequence="6"/>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_product_template_form_inherit" model="ir.ui.view">
|
||||
<field name="name">Product Template (form)</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="account.product_template_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="property_account_expense_id" position="after">
|
||||
<field name="asset_category_id"
|
||||
domain="[('type', '=', 'purchase')]"
|
||||
context="{'default_type': 'purchase'}"
|
||||
groups="account.group_account_user"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import asset_depreciation_confirmation_wizard
|
||||
from . import asset_modify
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class AssetDepreciationConfirmationWizard(models.TransientModel):
|
||||
_name = "asset.depreciation.confirmation.wizard"
|
||||
_description = "asset.depreciation.confirmation.wizard"
|
||||
|
||||
date = fields.Date('Account Date', required=True,
|
||||
help="Choose the period for which you want to automatically post the depreciation "
|
||||
"lines of running assets", default=fields.Date.context_today)
|
||||
|
||||
def asset_compute(self):
|
||||
self.ensure_one()
|
||||
context = self._context
|
||||
created_move_ids = self.env['account.asset.asset'].compute_generated_entries(self.date, asset_type=context.get('asset_type'))
|
||||
|
||||
return {
|
||||
'name': _('Created Asset Moves') if context.get('asset_type') == 'purchase' else _('Created Revenue Moves'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'account.move',
|
||||
'view_id': False,
|
||||
'domain': "[('id','in',[" + ','.join(str(id) for id in created_move_ids) + "])]",
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_asset_depreciation_confirmation_wizard" model="ir.ui.view">
|
||||
<field name="name">asset.depreciation.confirmation.wizard</field>
|
||||
<field name="model">asset.depreciation.confirmation.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Compute Asset">
|
||||
<div>
|
||||
<p>
|
||||
This wizard will post installment/depreciation lines for the selected month.<br/>
|
||||
This will generate journal entries for all related installment lines on this period
|
||||
of asset/revenue recognition as well.
|
||||
</p>
|
||||
</div>
|
||||
<group>
|
||||
<field name="date"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Generate Entries" name="asset_compute" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_asset_depreciation_confirmation_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Post Depreciation Lines</field>
|
||||
<field name="res_model">asset.depreciation.confirmation.wizard</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="view_asset_depreciation_confirmation_wizard"/>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'asset_type': 'purchase'}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_asset_depreciation_confirmation_wizard"
|
||||
name="Generate Assets Entries"
|
||||
action="action_asset_depreciation_confirmation_wizard"
|
||||
parent="account.menu_finance_entries_generate_entries"
|
||||
sequence="111"
|
||||
groups="account.group_account_manager"/>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AssetModify(models.TransientModel):
|
||||
_name = 'asset.modify'
|
||||
_description = 'Modify Asset'
|
||||
|
||||
name = fields.Text(string='Reason', required=True)
|
||||
method_number = fields.Integer(string='Number of Depreciations', required=True)
|
||||
method_period = fields.Integer(string='Period Length')
|
||||
method_end = fields.Date(string='Ending date')
|
||||
asset_method_time = fields.Char(compute='_get_asset_method_time', string='Asset Method Time', readonly=True)
|
||||
|
||||
def _get_asset_method_time(self):
|
||||
if self.env.context.get('active_id'):
|
||||
asset = self.env['account.asset.asset'].browse(self.env.context.get('active_id'))
|
||||
self.asset_method_time = asset.method_time
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super(AssetModify, self).default_get(fields)
|
||||
asset_id = self.env.context.get('active_id')
|
||||
asset = self.env['account.asset.asset'].browse(asset_id)
|
||||
if 'name' in fields:
|
||||
res.update({'name': asset.name})
|
||||
if 'method_number' in fields and asset.method_time == 'number':
|
||||
res.update({'method_number': asset.method_number})
|
||||
if 'method_period' in fields:
|
||||
res.update({'method_period': asset.method_period})
|
||||
if 'method_end' in fields and asset.method_time == 'end':
|
||||
res.update({'method_end': asset.method_end})
|
||||
if self.env.context.get('active_id'):
|
||||
active_asset = self.env['account.asset.asset'].browse(self.env.context.get('active_id'))
|
||||
res['asset_method_time'] = active_asset.method_time
|
||||
return res
|
||||
|
||||
def modify(self):
|
||||
""" Modifies the duration of asset for calculating depreciation
|
||||
and maintains the history of old values, in the chatter.
|
||||
"""
|
||||
asset_id = self.env.context.get('active_id', False)
|
||||
asset = self.env['account.asset.asset'].browse(asset_id)
|
||||
old_values = {
|
||||
'method_number': asset.method_number,
|
||||
'method_period': asset.method_period,
|
||||
'method_end': asset.method_end,
|
||||
}
|
||||
asset_vals = {
|
||||
'method_number': self.method_number,
|
||||
'method_period': self.method_period,
|
||||
'method_end': self.method_end,
|
||||
}
|
||||
if asset_vals['method_number'] <= asset.entry_count:
|
||||
raise UserError(_('The number of depreciations must be greater than the number of posted or draft entries '
|
||||
'to allow for complete depreciation of the asset.'))
|
||||
asset.write(asset_vals)
|
||||
asset.compute_depreciation_board()
|
||||
tracked_fields = self.env['account.asset.asset'].fields_get(['method_number', 'method_period', 'method_end'])
|
||||
changes, tracking_value_ids = asset._mail_track(tracked_fields, old_values)
|
||||
if changes:
|
||||
asset.message_post(subject=_('Depreciation board modified'), body=self.name, tracking_value_ids=tracking_value_ids)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="asset_modify_form" model="ir.ui.view">
|
||||
<field name="name">wizard.asset.modify.form</field>
|
||||
<field name="model">asset.modify</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Modify Asset">
|
||||
<field name="asset_method_time" invisible="1"/>
|
||||
<group string="Asset Durations to Modify" col="4">
|
||||
<group colspan="2" col="2">
|
||||
<field name="name"/>
|
||||
<field name="method_number" attrs="{'invisible': [('asset_method_time', '=', 'end')]}"/>
|
||||
</group>
|
||||
<group colspan="2" col="2">
|
||||
<field name="method_end" attrs="{'invisible': [('asset_method_time', '=', 'number')]}"/>
|
||||
<label for="method_period"/>
|
||||
<div>
|
||||
<field name="method_period" class="oe_inline"/> months
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="modify" string="Modify" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_asset_modify" model="ir.actions.act_window">
|
||||
<field name="name">Modify Asset</field>
|
||||
<field name="res_model">asset.modify</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="asset_modify_form"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
42
odoo-bringout-odoomates-om_account_asset/pyproject.toml
Normal file
42
odoo-bringout-odoomates-om_account_asset/pyproject.toml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
[project]
|
||||
name = "odoo-bringout-odoomates-om_account_asset"
|
||||
version = "16.0.0"
|
||||
description = "Odoo 16 Assets Management - Odoo 16 Assets Management"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-ocb-account>=16.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">= 3.11"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
homepage = "https://github.com/bringout/0"
|
||||
repository = "https://github.com/bringout/0"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.metadata]
|
||||
allow-direct-references = true
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["om_account_asset"]
|
||||
|
||||
[tool.rye]
|
||||
managed = true
|
||||
dev-dependencies = [
|
||||
"pytest>=8.4.1",
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue