3 Commits

106 changed files with 26670 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
from . import wizard
from . import models
from . import report

View File

@@ -0,0 +1,32 @@
{
'name': 'Odoo 19 Assets Management',
'version': '19.0.1.0.0', # __odoosky_original_version__: '1.0.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 19 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',
],
},
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding='UTF-8'?>
<odoo>
<data noupdate="1">
<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>
</record>
</data>
</odoo>

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

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
# 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

View File

@@ -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()

View File

@@ -0,0 +1,728 @@
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
from markupsafe import Markup
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')
account_asset_id = fields.Many2one(
'account.account', string='Asset Account',
required=True,
domain=[('account_type', 'not in', exclude_types)],
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)],
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)],
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)
code = fields.Char(string='Reference', size=32)
value = fields.Monetary(string='Gross Value', required=True)
currency_id = fields.Many2one(
'res.currency', string='Currency', required=True,
default=lambda self: self.env.user.company_id.currency_id.id
)
company_id = fields.Many2one(
'res.company', string='Company', required=True,
default=lambda self: self.env.company)
note = fields.Text()
category_id = fields.Many2one(
'account.asset.category', string='Category',
required=True, change_default=True
)
date = fields.Date(string='Date', required=True, 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')
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='Number of Months in a Period', required=True, default=12,
help="The amount of time between two depreciations, in months"
)
method_end = fields.Date(string='Ending Date')
method_progress_factor = fields.Float(
string='Degressive Factor', default=0.3
)
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, 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."
)
prorata = fields.Boolean(
string='Prorata Temporis',
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'
)
salvage_value = fields.Monetary(
string='Salvage Value',
help="It is the amount you plan to have that you cannot depreciate."
)
invoice_id = fields.Many2one('account.move', string='Invoice', copy=False)
type = fields.Selection(related="category_id.type", string='Type', required=True)
account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account')
date_first_depreciation = fields.Selection([
('last_day_period', 'Based on Last Day of Purchase Period'),
('manual', 'Manual')],
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.\n')
first_depreciation_manual_date = fields.Date(
string='First Depreciation Date',
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,
}
}
@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': 'list,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
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_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_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_distribution': analytic_distribution,
}
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_distribution': analytic_distribution,
}
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> &nbsp; &nbsp; &bull; <b>%s</b>: ' % name
message += '%s</div>' % values
return Markup(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()

View File

@@ -0,0 +1,160 @@
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)

View File

@@ -0,0 +1,22 @@
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

View File

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

View File

@@ -0,0 +1,65 @@
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.env.cr, 'asset_asset_report')
self.env.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
)""")

View File

@@ -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>
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group>
<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>

View File

@@ -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>

View File

@@ -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,account.group_account_user,1,1,1,0
access_asset_modify,access_asset_modify,model_asset_modify,account.group_account_user,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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_asset_category account.asset.category model_account_asset_category account.group_account_user 1 0 0 0
3 access_asset_depreciation_confirmation_wizard access_asset_depreciation_confirmation_wizard model_asset_depreciation_confirmation_wizard account.group_account_user 1 1 1 0
4 access_asset_modify access_asset_modify model_asset_modify account.group_account_user 1 1 1 0
5 access_account_asset_asset account.asset.asset model_account_asset_asset account.group_account_user 1 0 0 0
6 access_account_asset_category_manager account.asset.category model_account_asset_category account.group_account_manager 1 1 1 1
7 access_account_asset_asset_manager account.asset.asset model_account_asset_asset account.group_account_manager 1 1 1 1
8 access_account_asset_depreciation_line account.asset.depreciation.line model_account_asset_depreciation_line account.group_account_user 1 0 0 0
9 access_account_asset_depreciation_line_manager account.asset.depreciation.line model_account_asset_depreciation_line account.group_account_manager 1 1 1 1
10 access_asset_asset_report asset.asset.report model_asset_asset_report account.group_account_user 1 0 0 0
11 access_asset_asset_report_manager asset.asset.report model_asset_asset_report account.group_account_manager 1 1 1 1
12 access_account_asset_category_invoicing_payment account.asset.category model_account_asset_category account.group_account_invoice 1 0 0 0
13 access_account_asset_asset_invoicing_payment account.asset.asset model_account_asset_asset account.group_account_invoice 1 0 1 0
14 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: 1023 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -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

View File

@@ -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 19 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

View File

@@ -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');
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<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="18"/>
</record>
</odoo>

View File

@@ -0,0 +1,238 @@
<?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" string="Confirm" type="object" class="oe_highlight" invisible="state != 'draft'"/>
<button type="object" name="compute_depreciation_board" string="Compute Depreciation"
invisible="state != 'draft'"/>
<button name="set_to_close" invisible="state != 'open'" string="Sell or Dispose" type="object"
class="oe_highlight"/>
<button name="set_to_draft" string="Set to Draft" type="object"
invisible="entry_count != 0 or state != 'open'"/>
<button name="%(action_asset_modify)d" invisible="state != '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" readonly="state != 'draft'"/>
</h1>
</div>
<group>
<group>
<field name="category_id" string="Asset Category"
domain="[('type', '=', 'purchase')]"
readonly="state != 'draft'"
context="{'default_type': 'purchase'}" help="Category of asset"/>
<field name="code" readonly="state != 'draft'"/>
<field name="date" help="Date of asset" readonly="state != 'draft'"/>
<field name="date_first_depreciation" readonly="state != 'draft'"/>
<field name="first_depreciation_manual_date"
readonly="state != 'draft'"
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"
readonly="state != 'draft'"/>
<field name="company_id" options="{'no_create': True}"
readonly="state != 'draft'"
groups="base.group_multi_company"/>
<field name="value" widget="monetary"
readonly="state != 'draft'" help="Gross value of asset"/>
<field name="salvage_value" widget="monetary"
readonly="state != 'draft'"
invisible="type == 'sale'"/>
<field name="value_residual" widget="monetary"/>
<field name="partner_id" string="Vendor" widget="res_partner_many2one"
readonly="state != 'draft'"
context="{'res_partner_search_mode': 'supplier'}"/>
<field name="invoice_id" string="Invoice" options="{'no_create': True}"/>
<field name="analytic_distribution" widget="analytic_distribution"/>
</group>
</group>
<notebook>
<page string="Depreciation Board">
<field name="depreciation_line_ids" mode="list"
readonly="state not in ('draft', 'open')"
options="{'reload_whole_on_button': true}">
<list 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"
invisible="parent_state != 'open'"/>
<field name="move_posted_check" invisible="1"/>
<field name="parent_state" invisible="1"/>
</list>
<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"
invisible="type == 'sale'" readonly="state != 'draft'"/>
<field name="method_progress_factor"
readonly="state != 'draft'"
invisible="method == 'linear'"
required="method == 'degressive'"/>
<field name="method_time" string="Time Method Based On"
widget="radio"
readonly="state != 'draft'"
invisible="type != 'purchase'"/>
<field name="prorata"
invisible="method_time == 'end'"
readonly="state != 'draft'"/>
</group>
<group>
<field name="method_number"
readonly="state != 'draft'"
invisible="method_time == 'end'"
required="method_time == 'number'"/>
<field name="method_period" readonly="state != 'draft'"/>
<field name="method_end"
readonly="state != 'draft'"
invisible="method_time == 'end'"
required="method_time == 'number'"/>
</group>
</page>
</notebook>
</sheet>
<chatter/>
</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.list</field>
<field name="model">account.asset.asset</field>
<field name="arch" type="xml">
<list 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"/>
</list>
</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>
<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">list,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.account_account_menu"
action="action_account_asset_asset_form"
sequence="101"
groups="account.group_account_manager"/>
</odoo>

View File

@@ -0,0 +1,21 @@
<?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']/list/field[@name='account_id']" position="before">
<field string="Asset Category" name="asset_category_id"
force_save="1" column_invisible="parent.move_type != 'in_invoice'"
domain="[('type','=','purchase')]" context="{'default_type':'purchase'}"/>
</xpath>
<xpath expr="//field[@name='line_ids']/list/field[@name='account_id']" position="before">
<field string="Asset Category" name="asset_category_id"
column_invisible="1"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,176 @@
<?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"
invisible="type != 'purchase'"/>
<label for="name" string="Deferred Revenue Type" class="oe_edit_only"
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" invisible="type != 'purchase'"
style="font-weight: bold" class="o_light_label"/>
<label for="account_asset_id" string="Deferred Revenue Account"
invisible="type != 'sale'" style="font-weight: bold"
class="o_light_label"/>
</div>
<field name="account_asset_id" nolabel="1"
domain="[('company_ids', 'in', company_id)]"/>
<div>
<label for="account_depreciation_id" invisible="type != 'purchase'"
style="font-weight: bold" class="o_light_label"/>
<label for="account_depreciation_id" string="Recognition Income Account"
invisible="type != 'sale'"
style="font-weight: bold" class="o_light_label"/>
</div>
<field name="account_depreciation_id" nolabel="1"
domain="[('company_ids', 'in', company_id)]"/>
</group>
<group>
<div>
<label for="account_depreciation_expense_id"
invisible="type != 'purchase'"
style="font-weight: bold" class="o_light_label"/>
<label for="account_depreciation_expense_id" string="Recognition Account"
invisible="type != 'sale'"
style="font-weight: bold" class="o_light_label"/>
</div>
<field name="account_depreciation_expense_id" nolabel="1"
domain="[('company_ids', 'in', company_id)]"/>
<field name="account_analytic_id" domain="[('company_id', 'in', 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"
invisible="type != 'purchase'"/>
<field name="method_number" string="Number of Entries"
required="method_time == 'number'"
invisible="method_time != 'number' or type == False"/>
<label for="method_period" string="One Entry Every"/>
<div>
<field name="method_period" nolabel="1"
invisible="type == False" class="oe_inline"/>
months
</div>
<field name="method_end"
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 invisible="type == 'sale'" string="Depreciation Method">
<field name="method" widget="radio"/>
<field name="method_progress_factor"
invisible="method_time == 'linear'" required="method == 'degressive'"/>
<field name="prorata" 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.list</field>
<field name="model">account.asset.category</field>
<field name="arch" type="xml">
<list string="Asset category">
<field name="name"/>
<field name="journal_id"/>
<field name="method"/>
<field name="company_id" groups="base.group_multi_company"/>
</list>
</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>
<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">list,kanban,form</field>
<field name="context">{'default_type': 'purchase'}</field>
</record>
<menuitem id="menu_action_account_asset_asset_list_normal_purchase"
parent="account.account_account_menu"
action="action_account_asset_asset_list_normal_purchase"
sequence="6"/>
</odoo>

View File

@@ -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>

View File

@@ -0,0 +1,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import asset_depreciation_confirmation_wizard
from . import asset_modify

View File

@@ -0,0 +1,29 @@
# 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.env.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': 'list,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',
}

View File

@@ -0,0 +1,48 @@
<?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">list,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_finance_entries_generate_entries"
parent="account.menu_finance_entries"
name="Generate Entries"/>
<menuitem id="menu_asset_depreciation_confirmation_wizard"
name="Generate Assets Entries"
action="action_asset_depreciation_confirmation_wizard"
parent="om_account_asset.menu_finance_entries_generate_entries"
sequence="111"
groups="account.group_account_manager"/>
</odoo>

View File

@@ -0,0 +1,67 @@
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 Depreciation', 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'}

View File

@@ -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" invisible="asset_method_time == 'end'"/>
</group>
<group colspan="2" col="2">
<field name="method_end" 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">list,form</field>
<field name="view_id" ref="asset_modify_form"/>
<field name="target">new</field>
</record>
</odoo>

View File

@@ -0,0 +1,45 @@
=============================
Customer Follow Up Management
=============================
This Module will add customer follow up management in Odoo 19 Community Edition
Installation
============
To install this module, you need to:
Download the module and add it to your Odoo addons folder. Afterward, log on to
your Odoo server and go to the Apps menu. Trigger the debug mode and update the
list by clicking on the "Update Apps List" link. Now install the module by
clicking on the install button.
Upgrade
============
To upgrade this module, you need to:
Download the module and add it to your Odoo addons folder. Restart the server
and log on to your Odoo server. Select the Apps menu and upgrade the module by
clicking on the upgrade button.
Configuration
=============
Configure follow up levels
Credits
=======
Contributors
------------
* Odoo Mates <odoomates@gmail.com>
Author & Maintainer
-------------------
This module is maintained by the Odoo Mates

View File

@@ -0,0 +1,3 @@
from . import wizard
from . import models
from . import report

View File

@@ -0,0 +1,27 @@
{
'name': 'Customer Follow Up Management',
'version': '19.0.1.0.2', # __odoosky_original_version__: '1.0.2'
'category': 'Accounting',
'description': """Customer FollowUp Management""",
'summary': """Customer FollowUp Management""",
'author': 'Odoo Mates, Odoo S.A',
'license': 'LGPL-3',
'website': 'https://www.odoomates.tech',
'depends': ['account', 'mail'],
'data': [
'security/security.xml',
'security/ir.model.access.csv',
'data/mail_template_data.xml',
'wizard/followup_print_view.xml',
'wizard/followup_results_view.xml',
'views/followup_view.xml',
'views/account_move.xml',
'views/partners.xml',
'views/report_followup.xml',
'views/reports.xml',
'views/followup_partner_view.xml',
'report/followup_report.xml',
],
'demo': ['demo/demo.xml'],
'images': ['static/description/banner.png'],
}

View File

@@ -0,0 +1,242 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="email_template_om_account_followup_level0" model="mail.template">
<field name="name">First polite payment follow-up reminder email</field>
<field name="email_from">{{ (user.email or '') }}</field>
<field name="subject">{{ user.company_id.name }} Payment Reminder</field>
<field name="lang">{{ object.lang }}</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
<p>Dear <t t-out="object.name"/>,</p>
<p>
Exception made if there was a mistake of ours, it seems that the following amount stays unpaid. Please, take
appropriate measures in order to carry out this payment in the next 8 days.
Would your payment have been carried out after this mail was sent, please ignore this message. Do not hesitate to
contact our accounting department.
</p>
<br/>
Best Regards,
<br/>
<br/>
<t t-out="user.name" />
<br/>
<br/>
<t t-out="object.get_followup_table_html()" />
<br/>
</div>
]]></field>
</record>
<!--Mail template level 1 -->
<record id="email_template_om_account_followup_level1" model="mail.template">
<field name="name">A bit urging second payment follow-up reminder email</field>
<field name="email_from">{{ (user.email or '') }}</field>
<field name="subject">{{ user.company_id.name }} Payment Reminder</field>
<field name="email_to">{{ object.email }}</field>
<field name="lang">{{ object.lang }}</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
<p>Dear <t t-out="object.name"/>,</p>
<p>
We are disappointed to see that despite sending a reminder, that your account is now seriously overdue.
It is essential that immediate payment is made, otherwise we will have to consider placing a stop on your account
which means that we will no longer be able to supply your company with (goods/services).
Please, take appropriate measures in order to carry out this payment in the next 8 days.
If there is a problem with paying invoice that we are not aware of, do not hesitate to contact our accounting
department. so that we can resolve the matter quickly.
Details of due payments is printed below.
</p>
<br/>
Best Regards,
<br/>
<br/>
<t t-out="user.name" />
<br/>
<br/>
<t t-out="object.get_followup_table_html()" />
<br/>
</div>
]]></field>
</record>
<!--Mail template level 2 -->
<record id="email_template_om_account_followup_level2" model="mail.template">
<field name="name">Urging payment follow-up reminder email</field>
<field name="email_from">{{ (user.email or '') }}</field>
<field name="subject">{{ user.company_id.name }} Payment Reminder</field>
<field name="email_to">{{ object.email }}</field>
<field name="lang">{{ object.lang }}</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
<p>Dear <t t-out="object.name"/>,</p>
<p>
Despite several reminders, your account is still not settled.
Unless full payment is made in next 8 days, legal action for the recovery of the debt will be taken without
further notice.
I trust that this action will prove unnecessary and details of due payments is printed below.
In case of any queries concerning this matter, do not hesitate to contact our accounting department.
</p>
<br/>
Best Regards,
<br/>
<br/>
<t t-out="user.name" />
<br/>
<br/>
<t t-out="object.get_followup_table_html()" />
<br/>
</div>
]]></field>
</record>
<!-- Default follow up message -->
<record id="email_template_om_account_followup_default" model="mail.template">
<field name="name">Default payment follow-up reminder e-mail</field>
<field name="email_from">{{ (user.email or '') }}</field>
<field name="subject">{{ user.company_id.name }} Payment Reminder</field>
<field name="email_to">{{ object.email }}</field>
<field name="lang">{{ object.lang }}</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
<p>Dear <t t-out="object.name"/>,</p>
<p>
Exception made if there was a mistake of ours, it seems that the following amount stays unpaid. Please, take
appropriate measures in order to carry out this payment in the next 8 days.
Would your payment have been carried out after this mail was sent, please ignore this message. Do not hesitate to
contact our accounting department.
</p>
<br/>
Best Regards,
<br/>
<br/>
<t t-out="user.name" />
<br/>
<br/>
<t t-out="object.get_followup_table_html()" />
<br/>
</div>
]]></field>
</record>
<record id="demo_followup1" model="followup.followup" forcecreate="False">
<field name="company_id" ref="base.main_company"/>
</record>
<record id="demo_followup_line1" model="followup.line" forcecreate="False">
<field name="name">Send first reminder email</field>
<field name="sequence">0</field>
<field name="delay">15</field>
<field name="followup_id" ref="demo_followup1"/>
<field name="send_email">True</field>
<field name="description">
Dear %(partner_name)s,
Exception made if there was a mistake of ours, it seems that
the following amount stays unpaid. Please, take appropriate
measures in order to carry out this payment in the next 8 days.
Would your payment have been carried out after this mail was
sent, please ignore this message. Do not hesitate to contact
our accounting department.
Best Regards,
</field>
<field name="email_template_id" ref="email_template_om_account_followup_level0"/>
</record>
<record id="demo_followup_line2" model="followup.line" forcecreate="False">
<field name="name">Send reminder letter and email</field>
<field name="sequence">1</field>
<field name="delay">30</field>
<field name="followup_id" ref="demo_followup1"/>
<field name="email_template_id"
ref="email_template_om_account_followup_level1"/>
<field name="send_email">True</field>
<field name="send_letter">True</field>
<field name="description">
Dear %(partner_name)s,
We are disappointed to see that despite sending a reminder,
that your account is now seriously overdue.
It is essential that immediate payment is made, otherwise we
will have to consider placing a stop on your account which
means that we will no longer be able to supply your company
with (goods/services).
Please, take appropriate measures in order to carry out this
payment in the next 8 days.
If there is a problem with paying invoice that we are not aware
of, do not hesitate to contact our accounting department, so
that we can resolve the matter quickly.
Details of due payments is printed below.
Best Regards,
</field>
</record>
<record id="demo_followup_line3" model="followup.line" forcecreate="False">
<field name="name">Call the customer on the phone</field>
<field name="sequence">3</field>
<field name="delay">40</field>
<field name="followup_id" ref="demo_followup1"/>
<field name="email_template_id"
ref="email_template_om_account_followup_level2"/>
<field eval="False" name="send_email"/>
<field name="manual_action">True</field>
<field name="manual_action_note">Call the customer on the phone!</field>
<field name="description">
Dear %(partner_name)s,
Despite several reminders, your account is still not settled.
Unless full payment is made in next 8 days, then legal action
for the recovery of the debt will be taken without further
notice.
I trust that this action will prove unnecessary and details of
due payments is printed below.
In case of any queries concerning this matter, do not hesitate
to contact our accounting department.
Best Regards,
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="demo_followup_line4" model="followup.line">
<field name="name">Urging reminder email</field>
<field name="sequence">4</field>
<field name="delay">50</field>
<field name="followup_id" ref="demo_followup1"/>
<field name="send_email">True</field>
<field name="email_template_id" ref="email_template_om_account_followup_level2"/>
<field name="description">
Dear %(partner_name)s,
Despite several reminders, your account is still not settled.
Unless full payment is made in next 8 days, then legal action
for the recovery of the debt will be taken without further
notice.
I trust that this action will prove unnecessary and details of
due payments is printed below.
In case of any queries concerning this matter, do not hesitate
to contact our accounting department.
Best Regards,
</field>
</record>
<record id="demo_followup_line5" model="followup.line">
<field name="name">Urging reminder letter</field>
<field name="sequence">5</field>
<field name="delay">60</field>
<field name="followup_id" ref="demo_followup1"/>
<field eval="False" name="send_email"/>
<field name="send_letter">True</field>
<field name="email_template_id" ref="email_template_om_account_followup_level2"/>
<field name="description">
Dear %(partner_name)s,
Despite several reminders, your account is still not settled.
Unless full payment is made in next 8 days, then legal action
for the recovery of the debt will be taken without further
notice.
I trust that this action will prove unnecessary and details of
due payments is printed below.
In case of any queries concerning this matter, do not hesitate
to contact our accounting department.
Best Regards,
</field>
</record>
</data>
</odoo>

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

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from . import account_move
from . import followup
from . import followup_partner
from . import partner
from . import settings

View File

@@ -0,0 +1,13 @@
from odoo import api, fields, models, _
class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
followup_line_id = fields.Many2one('followup.line', 'Follow-up Level')
followup_date = fields.Date('Latest Follow-up')
result = fields.Float(compute='_get_result', string="Balance Amount")
def _get_result(self):
for aml in self:
aml.result = aml.debit - aml.credit

View File

@@ -0,0 +1,91 @@
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
class FollowupFollowup(models.Model):
_name = 'followup.followup'
_description = 'Account Follow-up'
_rec_name = 'name'
name = fields.Char(string="Name", related='company_id.name', readonly=True)
followup_line = fields.One2many('followup.line', 'followup_id', 'Follow-up', copy=True)
company_id = fields.Many2one('res.company', 'Company', required=True, default=lambda self: self.env.company)
_company_uniq = models.Constraint(
'unique(company_id)',
'Only one follow-up per company is allowed',
)
class FollowupLine(models.Model):
_name = 'followup.line'
_description = 'Follow-up Criteria'
_order = 'delay'
def _compute_sequence(self):
delays = [line.delay for line in self.followup_id.followup_line]
delays.sort()
for line in self.followup_id.followup_line:
sequence = delays.index(line.delay)
line.sequence = sequence+1
@api.model
def default_get(self, default_fields):
values = super(FollowupLine, self).default_get(default_fields)
if self.env.ref('om_account_followup.email_template_om_account_followup_default'):
values['email_template_id'] = self.env.ref('om_account_followup.email_template_om_account_followup_default').id
return values
name = fields.Char('Follow-Up Action', required=True)
sequence = fields.Integer('Sequence', compute='_compute_sequence',
store=False,
help="Gives the sequence order when displaying a list of follow-up lines.")
followup_id = fields.Many2one('followup.followup', 'Follow Ups',
required=True, ondelete="cascade")
delay = fields.Integer('Due Days',
help="The number of days after the due date of the "
"invoice to wait before sending the reminder. Could be negative if you want "
"to send a polite alert beforehand.",
required=True)
description = fields.Text('Printed Message', translate=True, default="""
Dear %(partner_name)s,
Exception made if there was a mistake of ours, it seems that the following
amount stays unpaid. Please, take appropriate measures in order to carry out
this payment in the next 8 days.
Would your payment have been carried out after this mail was sent, please
ignore this message. Do not hesitate to contact our accounting department.
Best Regards,
""", )
send_email = fields.Boolean('Send an Email', default=True,
help="When processing, it will send an email")
send_letter = fields.Boolean('Send a Letter', default=True,
help="When processing, it will print a letter")
manual_action = fields.Boolean('Manual Action', default=False,
help="When processing, it will set the "
"manual action to be taken for that customer. ")
manual_action_note = fields.Text('Action To Do')
manual_action_responsible_id = fields.Many2one('res.users',
string='Assign a Responsible', ondelete='set null')
email_template_id = fields.Many2one('mail.template', 'Email Template',
ondelete='set null')
_days_uniq = models.Constraint(
'unique(followup_id, delay)',
'Days of the follow-up levels must be different',
)
@api.constrains('description')
def _check_description(self):
for line in self:
if line.description:
try:
line.description % {'partner_name': '', 'date': '',
'user_signature': '',
'company_name': ''}
except ValidationError:
raise ValidationError(
_('Your description is invalid, use the right legend '
'or %% if you want to use the percent character.'))

View File

@@ -0,0 +1,50 @@
from odoo import api, fields, models, _
from odoo import tools
class FollowupStatByPartner(models.Model):
_name = "followup.stat.by.partner"
_description = "Follow-up Statistics by Partner"
_rec_name = 'partner_id'
_auto = False
def _get_invoice_partner_id(self):
for rec in self:
rec.invoice_partner_id = rec.partner_id.address_get(
adr_pref=['invoice']).get('invoice', rec.partner_id.id)
partner_id = fields.Many2one('res.partner', 'Partner', readonly=True)
date_move = fields.Date('First move', readonly=True)
date_move_last = fields.Date('Last move', readonly=True)
date_followup = fields.Date('Latest follow-up', readonly=True)
max_followup_id = fields.Many2one('followup.line', 'Max Follow Up Level', readonly=True, ondelete="cascade")
balance = fields.Float('Balance', readonly=True)
company_id = fields.Many2one('res.company', 'Company', readonly=True)
invoice_partner_id = fields.Many2one('res.partner', compute='_get_invoice_partner_id', string='Invoice Address')
@api.model
def init(self):
tools.drop_view_if_exists(self.env.cr, 'followup_stat_by_partner')
self.env.cr.execute("""
create view followup_stat_by_partner as (
SELECT
l.partner_id * 10000::bigint + l.company_id as id,
l.partner_id AS partner_id,
min(l.date) AS date_move,
max(l.date) AS date_move_last,
max(l.followup_date) AS date_followup,
max(l.followup_line_id) AS max_followup_id,
sum(l.debit - l.credit) AS balance,
l.company_id as company_id
FROM
account_move_line l
LEFT JOIN account_account a ON (l.account_id = a.id)
WHERE
a.account_type = 'asset_receivable' AND
l.full_reconcile_id is NULL AND
l.partner_id IS NOT NULL
GROUP BY
l.partner_id, l.company_id
)""")

View File

@@ -0,0 +1,408 @@
from functools import reduce
from lxml import etree
from odoo import api, fields, models, _
from datetime import datetime
from odoo.exceptions import ValidationError
from odoo.tools.misc import formatLang
class ResPartner(models.Model):
_inherit = "res.partner"
def fields_view_get(self, view_id=None, view_type='form', toolbar=False,
submenu=False):
res = super(ResPartner, self).fields_view_get(
view_id=view_id, view_type=view_type, toolbar=toolbar,
submenu=submenu)
if view_type == 'form' and self.env.context.get('Followupfirst'):
doc = etree.XML(res['arch'], parser=None, base_url=None)
first_node = doc.xpath("//page[@name='followup_tab']")
root = first_node[0].getparent()
root.insert(0, first_node[0])
res['arch'] = etree.tostring(doc, encoding="utf-8")
return res
def _get_latest(self):
company = self.env.user.company_id
for partner in self:
amls = partner.unreconciled_aml_ids
latest_date = False
latest_level = False
latest_days = False
latest_level_without_lit = False
latest_days_without_lit = False
for aml in amls:
aml_followup = aml.followup_line_id
if (aml.company_id == company) and aml_followup and \
(not latest_days or latest_days < aml_followup.delay):
latest_days = aml_followup.delay
latest_level = aml_followup.id
if (aml.company_id == company) and aml.followup_date and (
not latest_date or latest_date < aml.followup_date):
latest_date = aml.followup_date
if (aml.company_id == company) and \
(aml_followup and (not latest_days_without_lit or
latest_days_without_lit < aml_followup.delay)):
latest_days_without_lit = aml_followup.delay
latest_level_without_lit = aml_followup.id
partner.latest_followup_date = latest_date
partner.latest_followup_level_id = latest_level
partner.latest_followup_level_id_without_lit = latest_level_without_lit
def do_partner_manual_action_dermanord(self, followup_line):
action_text = followup_line.manual_action_note or ''
action_date = self.payment_next_action_date or \
fields.Date.today()
if self.payment_responsible_id:
responsible_id = self.payment_responsible_id.id
else:
p = followup_line.manual_action_responsible_id
responsible_id = p and p.id or False
self.write({'payment_next_action_date': action_date,
'payment_next_action': action_text,
'payment_responsible_id': responsible_id})
def do_partner_manual_action(self, partner_ids):
for partner in self.browse(partner_ids):
followup_without_lit = partner.latest_followup_level_id_without_lit
if partner.payment_next_action:
action_text = \
(partner.payment_next_action or '') + "\n" + \
(followup_without_lit.manual_action_note or '')
else:
action_text = followup_without_lit.manual_action_note or ''
action_date = partner.payment_next_action_date or \
fields.Date.today()
if partner.payment_responsible_id:
responsible_id = partner.payment_responsible_id.id
else:
p = followup_without_lit.manual_action_responsible_id
responsible_id = p and p.id or False
partner.write({'payment_next_action_date': action_date,
'payment_next_action': action_text,
'payment_responsible_id': responsible_id})
def do_partner_print(self, wizard_partner_ids, data):
if not wizard_partner_ids:
return {}
data['partner_ids'] = wizard_partner_ids
datas = {
'ids': wizard_partner_ids,
'model': 'followup.followup',
'form': data
}
return self.env.ref(
'om_account_followup.action_report_followup').report_action(
self, data=datas)
def do_partner_mail(self):
ctx = self.env.context.copy()
ctx['followup'] = True
template = 'om_account_followup.email_template_om_account_followup_default'
unknown_mails = 0
for partner in self:
partners_to_email = [child for child in partner.child_ids if
child.type == 'invoice' and child.email]
if not partners_to_email and partner.email:
partners_to_email = [partner]
if partners_to_email:
level = partner.latest_followup_level_id_without_lit
for partner_to_email in partners_to_email:
if level and level.send_email and \
level.email_template_id and \
level.email_template_id.id:
level.email_template_id.with_context(ctx).send_mail(
partner_to_email.id)
else:
mail_template_id = self.env.ref(template)
mail_template_id.with_context(ctx).send_mail(
partner_to_email.id)
if partner not in partners_to_email:
partner.message_post(body=_(
'Overdue email sent to %s' % ', '.join(
['%s <%s>' % (partner.name, partner.email) for
partner in partners_to_email])))
else:
unknown_mails = unknown_mails + 1
action_text = _("Email not sent because of email address "
"of partner not filled in")
if partner.payment_next_action_date:
payment_action_date = min(
fields.Date.today(),
partner.payment_next_action_date)
else:
payment_action_date = fields.Date.today()
if partner.payment_next_action:
payment_next_action = \
partner.payment_next_action + " \n " + action_text
else:
payment_next_action = action_text
partner.with_context(ctx).write(
{'payment_next_action_date': payment_action_date,
'payment_next_action': payment_next_action})
return unknown_mails
def get_followup_table_html(self):
self.ensure_one()
partner = self.commercial_partner_id
followup_table = ''
if partner.unreconciled_aml_ids:
company = self.env.user.company_id
current_date = fields.Date.today()
report = self.env['report.om_account_followup.report_followup']
final_res = report._lines_get_with_partner(partner, company.id)
for currency_dict in final_res:
currency = currency_dict.get('line', [
{'currency_id': company.currency_id}])[0]['currency_id']
followup_table += '''
<table border="2" width=100%%>
<tr>
<td>''' + _("Invoice Date") + '''</td>
<td>''' + _("Description") + '''</td>
<td>''' + _("Reference") + '''</td>
<td>''' + _("Due Date") + '''</td>
<td>''' + _("Amount") + " (%s)" % (
currency.symbol) + '''</td>
<td>''' + _("Lit.") + '''</td>
</tr>
'''
total = 0
for aml in currency_dict['line']:
total += aml['balance']
strbegin = "<TD>"
strend = "</TD>"
date = aml['date_maturity'] or aml['date']
date = datetime.strptime(date, "%m/%d/%Y").date()
if date <= current_date and aml['balance'] > 0:
strbegin = "<TD><B>"
strend = "</B></TD>"
followup_table += "<TR>" + strbegin + str(aml['date']) + \
strend + strbegin + aml['name'] + \
strend + strbegin + \
(aml['ref'] or '') + strend + \
strbegin + str(date) + strend + \
strbegin + str(aml['balance']) + \
strend + "</TR>"
total = reduce(lambda x, y: x + y['balance'],
currency_dict['line'], 0.00)
total = formatLang(self.env, total, currency_obj=currency)
followup_table += '''<tr> </tr>
</table>
<center>''' + _(
"Amount due") + ''' : %s </center>''' % (total)
return followup_table
def write(self, vals):
if vals.get("payment_responsible_id", False):
for part in self:
if part.payment_responsible_id != \
self.env['res.users'].browse(vals["payment_responsible_id"]):
# Find partner_id of user put as responsible
responsible_partner_id = self.env["res.users"].browse(
vals['payment_responsible_id']).partner_id.id
part.message_post(
body=_("You became responsible to do the next action "
"for the payment follow-up of") +
" <b><a href='#id=" + str(part.id) +
"&view_type=form&model=res.partner'> " + part.name +
" </a></b>",
type='comment',
context=self.env.context,
partner_ids=[responsible_partner_id])
return super(ResPartner, self).write(vals)
def action_done(self):
return self.write({'payment_next_action_date': False,
'payment_next_action': '',
'payment_responsible_id': False})
def do_button_print(self):
self.ensure_one()
company_id = self.env.user.company_id.id
if not self.env['account.move.line'].search(
[('partner_id', '=', self.id),
('account_id.account_type', '=', 'asset_receivable'),
('full_reconcile_id', '=', False),
('company_id', '=', company_id),
'|', ('date_maturity', '=', False),
('date_maturity', '<=', fields.Date.today())]):
raise ValidationError(
_("The partner does not have any accounting entries to "
"print in the overdue report for the current company."))
self.message_post(body=_('Printed overdue payments report'))
self.message_post(body=_('Printed overdue payments report'))
wizard_partner_ids = [self.id * 10000 + company_id]
followup_ids = self.env['followup.followup'].search(
[('company_id', '=', company_id)])
if not followup_ids:
raise ValidationError(_(
"There is no followup plan defined for the current company."))
data = {
'date': fields.date.today(),
'followup_id': followup_ids[0].id,
}
return self.do_partner_print(wizard_partner_ids, data)
def _get_amounts_and_date(self):
company = self.env.user.company_id
current_date = fields.Date.today()
for partner in self:
worst_due_date = False
amount_due = amount_overdue = 0.0
for aml in partner.unreconciled_aml_ids:
if (aml.company_id == company):
date_maturity = aml.date_maturity or aml.date
if not worst_due_date or date_maturity < worst_due_date:
worst_due_date = date_maturity
amount_due += aml.result
if (date_maturity <= current_date):
amount_overdue += aml.result
partner.payment_amount_due = amount_due
partner.payment_amount_overdue = amount_overdue
partner.payment_earliest_due_date = worst_due_date
def _get_followup_overdue_query(self, args, overdue_only=False):
company_id = self.env.user.company_id.id
having_clauses = []
having_values = []
for field, operator, value in args:
if operator in ['=', '!=', '>', '>=', '<', '<=']:
having_clauses.append(f'SUM(bal2) {operator} %s')
having_values.append(value)
else:
raise ValueError(f"Unsupported operator: {operator}")
having_where_clause = ' AND '.join(having_clauses)
overdue_only_str = 'AND date_maturity <= NOW()' if overdue_only else ''
query = ('''
SELECT pid AS partner_id, SUM(bal2) FROM (
SELECT
CASE WHEN bal IS NOT NULL THEN bal ELSE 0.0 END AS bal2,
p.id as pid
FROM (
SELECT
(debit - credit) AS bal,
partner_id
FROM account_move_line l
LEFT JOIN account_account a ON a.id = l.account_id
WHERE a.account_type = 'asset_receivable'
%s AND full_reconcile_id IS NULL
AND l.company_id = %%s
) AS l
RIGHT JOIN res_partner p ON p.id = partner_id
) AS pl
GROUP BY pid HAVING %s
''') % (overdue_only_str, having_where_clause)
params = [company_id] + having_values
return query, params
def _payment_overdue_search(self, operator, operand):
args = [('payment_amount_overdue', operator, operand)]
query, params = self._get_followup_overdue_query(args, overdue_only=True)
self.env.cr.execute(query, params)
res = self.env.cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', [x[0] for x in res])]
def _payment_earliest_date_search(self, operator, operand):
args = [('payment_earliest_due_date', operator, operand)]
company_id = self.env.user.company_id.id
having_where_clause = ' AND '.join(
map(lambda x: "(MIN(l.date_maturity) %s '%%s')" % (x[1]), args))
having_values = [x[2] for x in args]
having_where_clause = having_where_clause % (having_values[0])
query = """SELECT partner_id FROM account_move_line l
LEFT JOIN account_account a ON a.id = l.account_id
WHERE a.account_type = 'asset_receivable'
AND l.company_id = %s
AND l.full_reconcile_id IS NULL
AND partner_id IS NOT NULL GROUP BY partner_id"""
query = query % company_id
if having_where_clause:
query += ' HAVING %s ' % (having_where_clause)
self.env.cr.execute(query)
res = self.env.cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', [x[0] for x in res])]
def _payment_due_search(self, operator, operand):
args = [('payment_amount_due', operator, operand)]
query, params = self._get_followup_overdue_query(args, overdue_only=False)
self.env.cr.execute(query, params)
res = self.env.cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', [x[0] for x in res])]
def _get_partners(self):
partners = set()
for aml in self:
if aml.partner_id:
partners.add(aml.partner_id.id)
return list(partners)
payment_responsible_id = fields.Many2one(
'res.users', ondelete='set null',
string='Follow-up Responsible', tracking=True, copy=False,
help="Optionally you can assign a user to this field, which will make "
"him responsible for the action.")
payment_note = fields.Text(
'Customer Payment Promise', help="Payment Note", copy=False
)
payment_next_action = fields.Text(
'Next Action', copy=False, tracking=True,
help="This is the next action to be taken. It will automatically be "
"set when the partner gets a follow-up level that requires a manual action. "
)
payment_next_action_date = fields.Date(
'Next Action Date', copy=False,
help="This is when the manual follow-up is needed. The date will be "
"set to the current date when the partner gets a follow-up level "
"that requires a manual action. Can be practical to set manually "
"e.g. to see if he keeps his promises."
)
unreconciled_aml_ids = fields.One2many(
'account.move.line', 'partner_id',
domain=[('full_reconcile_id', '=', False), ('account_id.account_type', '=', 'asset_receivable')]
)
latest_followup_date = fields.Date(
compute='_get_latest', string="Latest Follow-up Date", compute_sudo=True,
help="Latest date that the follow-up level of the partner was changed"
)
latest_followup_level_id = fields.Many2one(
'followup.line', compute='_get_latest', compute_sudo=True,
string="Latest Follow-up Level", help="The maximum follow-up level"
)
latest_followup_sequence = fields.Integer(
'Sequence',
help="Gives the sequence order when displaying a list of follow-up lines.", default=0
)
latest_followup_level_id_without_lit = fields.Many2one(
'followup.line', compute='_get_latest', compute_sudo=True,
string="Latest Follow-up Level without litigation",
help="The maximum follow-up level without taking into "
"account the account move lines with litigation")
payment_amount_due = fields.Float(
compute='_get_amounts_and_date',
string="Amount Due", search='_payment_due_search'
)
payment_amount_overdue = fields.Float(
compute='_get_amounts_and_date',
string="Amount Overdue", search='_payment_overdue_search'
)
payment_earliest_due_date = fields.Date(
compute='_get_amounts_and_date', string="Worst Due Date",
search='_payment_earliest_date_search'
)

View File

@@ -0,0 +1,15 @@
from odoo import api, fields, models, _
class AccountConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
def open_followup_level_form(self):
res_ids = self.env['followup.followup'].search([], limit=1)
return {
'type': 'ir.actions.act_window',
'name': 'Follow-up Levels',
'res_model': 'followup.followup',
'res_id': res_ids and res_ids.id or False,
'view_mode': 'form,list',
}

View File

@@ -0,0 +1,2 @@
from . import followup_print
from . import followup_report

View File

@@ -0,0 +1,110 @@
import time
from collections import defaultdict
from odoo.exceptions import ValidationError
from odoo import api, fields, models, _
from odoo.tools import format_date
class ReportFollowup(models.AbstractModel):
_name = 'report.om_account_followup.report_followup'
_description = 'Report Followup'
@api.model
def _get_report_values(self, docids, data=None):
model = self.env['followup.sending.results']
ids = self.env.context.get('active_ids') or False
docs = model.browse(ids)
return {
'docs': docs,
'doc_ids': docids,
'doc_model': model,
'time': time,
'ids_to_objects': self._ids_to_objects,
'getLines': self._lines_get,
'get_text': self._get_text,
'data': data and data['form'] or {}}
def _ids_to_objects(self, ids):
all_lines = []
for line in self.env['followup.stat.by.partner'].browse(ids):
if line not in all_lines:
all_lines.append(line)
return all_lines
def _lines_get(self, stat_by_partner_line):
return self._lines_get_with_partner(stat_by_partner_line.partner_id,
stat_by_partner_line.company_id.id)
def _lines_get_with_partner(self, partner, company_id):
moveline_obj = self.env['account.move.line']
moveline_ids = moveline_obj.search(
[('partner_id', '=', partner.id),
('account_id.account_type', '=', 'asset_receivable'),
('full_reconcile_id', '=', False),
('company_id', '=', company_id),
'|', ('date_maturity', '=', False),
('date_maturity', '<=', fields.Date.today())])
lines_per_currency = defaultdict(list)
total = 0
for line in moveline_ids:
currency = line.currency_id or line.company_id.currency_id
balance = line.debit - line.credit
if currency != line.company_id.currency_id:
balance = line.amount_currency
line_data = {
'name': line.move_id.name,
'ref': line.ref,
'date': format_date(self.env, line.date),
'date_maturity': format_date(self.env, line.date_maturity),
'balance': balance,
'currency_id': currency,
}
total = total + line_data['balance']
lines_per_currency[currency].append(line_data)
return [{'total': total, 'line': lines, 'currency': currency} for
currency, lines in
lines_per_currency.items()]
def _get_text(self, stat_line, followup_id, context=None):
fp_obj = self.env['followup.followup']
fp_line = fp_obj.browse(followup_id).followup_line
if not fp_line:
raise ValidationError(
_("The followup plan defined for the current company does not "
"have any followup action."))
default_text = ''
li_delay = []
for line in fp_line:
if not default_text and line.description:
default_text = line.description
li_delay.append(line.delay)
li_delay.sort(reverse=True)
partner_line_ids = self.env['account.move.line'].search(
[('partner_id', '=', stat_line.partner_id.id),
('full_reconcile_id', '=', False),
('company_id', '=', stat_line.company_id.id),
('debit', '!=', False),
('account_id.account_type', '=', 'asset_receivable'),
('followup_line_id', '!=', False)])
partner_max_delay = 0
partner_max_text = ''
for i in partner_line_ids:
if i.followup_line_id.delay > partner_max_delay and \
i.followup_line_id.description:
partner_max_delay = i.followup_line_id.delay
partner_max_text = i.followup_line_id.description
text = partner_max_delay and partner_max_text or default_text
if text:
lang_obj = self.env['res.lang']
lang_ids = lang_obj.search(
[('code', '=', stat_line.partner_id.lang)], limit=1)
date_format = lang_ids and lang_ids.date_format or '%Y-%m-%d'
text = text % {
'partner_name': stat_line.partner_id.name,
'date': time.strftime(date_format),
'company_name': stat_line.company_id.name,
'user_signature': self.env.user.signature or '',
}
return text

View File

@@ -0,0 +1,47 @@
from odoo import api, fields, models
from odoo import tools
class AccountFollowupStat(models.Model):
_name = "followup.stat"
_description = "Follow-up Statistics"
_rec_name = 'partner_id'
_order = 'date_move'
_auto = False
partner_id = fields.Many2one('res.partner', 'Partner', readonly=True)
date_move = fields.Date('First move', readonly=True)
date_move_last = fields.Date('Last move', readonly=True)
date_followup = fields.Date('Latest followup', readonly=True)
followup_id = fields.Many2one('followup.line', 'Follow Ups', readonly=True, ondelete="cascade")
balance = fields.Float('Balance', readonly=True)
debit = fields.Float('Debit', readonly=True)
credit = fields.Float('Credit', readonly=True)
company_id = fields.Many2one('res.company', 'Company', readonly=True)
@api.model
def init(self):
tools.drop_view_if_exists(self.env.cr, 'followup_stat')
self.env.cr.execute("""
create or replace view followup_stat as (
SELECT
l.id as id,
l.partner_id AS partner_id,
min(l.date) AS date_move,
max(l.date) AS date_move_last,
max(l.followup_date) AS date_followup,
max(l.followup_line_id) AS followup_id,
sum(l.debit) AS debit,
sum(l.credit) AS credit,
sum(l.debit - l.credit) AS balance,
l.company_id AS company_id
FROM
account_move_line l
LEFT JOIN account_account a ON (l.account_id = a.id)
WHERE
a.account_type = 'asset_receivable' AND
l.full_reconcile_id is NULL AND
l.partner_id IS NOT NULL
GROUP BY
l.id, l.partner_id, l.company_id
)""")

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_om_account_followup_stat_graph" model="ir.ui.view">
<field name="name">followup.stat.graph</field>
<field name="model">followup.stat</field>
<field name="arch" type="xml">
<graph string="Follow-up lines">
<field name="followup_id" type="row"/>
<field name="date_followup" type="col"/>
<field name="balance" type="measure"/>
</graph>
</field>
</record>
<record id="view_om_account_followup_stat_search" model="ir.ui.view">
<field name="name">followup.stat.search</field>
<field name="model">followup.stat</field>
<field name="arch" type="xml">
<search string="Follow-ups Sent">
<field name="date_move"/>
<field name="date_move_last"/>
<separator/>
<filter string="Not Litigation" name="not_litigation"
help="Including journal entries marked as a litigation"/>
<field name="partner_id"/>
<field name="balance"/>
<group>
<filter string="Partner" name="partner"
context="{'group_by':'partner_id'}"/>
<filter string="Follow-up Level" name="followup_level"
context="{'group_by':'followup_id'}"/>
<filter string="Company" name="company"
groups="base.group_multi_company"
context="{'group_by':'company_id'}"/>
<separator/>
<filter string="Latest Follow-up Month" name="lastest_month"
context="{'group_by':'date_followup:month'}"/>
</group>
</search>
</field>
</record>
<record id="action_followup_stat" model="ir.actions.act_window">
<field name="name">Follow-ups Analysis</field>
<field name="res_model">followup.stat</field>
<field name="view_mode">graph</field>
<field name="context">{'search_default_followup_level':1}</field>
<field name="search_view_id" ref="view_om_account_followup_stat_search"/>
</record>
<menuitem action="action_followup_stat"
id="menu_action_followup_stat_follow"
parent="om_account_followup.menu_finance_followup"
groups="account.group_account_invoice"
name="Follow-ups Analysis"
sequence="20"/>
</data>
</odoo>

View File

@@ -0,0 +1,11 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_followup_followup_line,followup.followup.line,model_followup_line,account.group_account_invoice,1,0,0,0
access_followup_followup_line_manager,followup.followup.line.manager,model_followup_line,account.group_account_manager,1,1,1,1
access_followup_followup_accountant,followup.followup.user,model_followup_followup,account.group_account_invoice,1,0,0,0
access_followup_followup_manager,followup.followup.manager,model_followup_followup,account.group_account_manager,1,1,1,1
access_followup_stat_invoice,followup.stat.invoice,model_followup_stat,account.group_account_invoice,1,1,0,0
access_followup_stat_by_partner_manager,followup.stat.by.partner,model_followup_stat_by_partner,account.group_account_user,1,1,0,0
access_followup_stat_user,followup.stat.user,model_followup_stat,account.group_account_user,1,1,0,0
access_followup_stat_manager,followup.stat.manager,model_followup_stat,account.group_account_manager,1,1,1,1
access_followup_print,access_followup_print,model_followup_print,base.group_user,1,1,1,1
access_followup_sending_results,access_followup_sending_results,model_followup_sending_results,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_followup_followup_line followup.followup.line model_followup_line account.group_account_invoice 1 0 0 0
3 access_followup_followup_line_manager followup.followup.line.manager model_followup_line account.group_account_manager 1 1 1 1
4 access_followup_followup_accountant followup.followup.user model_followup_followup account.group_account_invoice 1 0 0 0
5 access_followup_followup_manager followup.followup.manager model_followup_followup account.group_account_manager 1 1 1 1
6 access_followup_stat_invoice followup.stat.invoice model_followup_stat account.group_account_invoice 1 1 0 0
7 access_followup_stat_by_partner_manager followup.stat.by.partner model_followup_stat_by_partner account.group_account_user 1 1 0 0
8 access_followup_stat_user followup.stat.user model_followup_stat account.group_account_user 1 1 0 0
9 access_followup_stat_manager followup.stat.manager model_followup_stat account.group_account_manager 1 1 1 1
10 access_followup_print access_followup_print model_followup_print base.group_user 1 1 1 1
11 access_followup_sending_results access_followup_sending_results model_followup_sending_results base.group_user 1 1 1 1

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="om_account_followup_comp_rule" model="ir.rule">
<field name="name">Account Follow-up multi company rule</field>
<field name="model_id" ref="model_followup_followup"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),
('company_id','child_of',[user.company_id.id])]</field>
</record>
<record id="om_account_followup_stat_by_partner_comp_rule" model="ir.rule">
<field name="name">Account Follow-up Statistics by Partner Rule</field>
<field ref="model_followup_stat_by_partner" name="model_id"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),
('company_id','child_of',[user.company_id.id])]</field>
</record>
</data>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,81 @@
<section class="oe_container oe_dark">
<div class="col-md-12">
<h2 class="oe_slogan" style="font-size: 35px;color:#2C0091"><b>Customer Follow Up Management</b></h2>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan" style="color:olive;">Follow-Up Levels</h2>
<div class="oe_demo oe_picture oe_screenshot">
<img src="follow_up_level.png">
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan" style="color:olive;">Send Follow-Ups</h2>
<div class="oe_demo oe_picture oe_screenshot">
<img src="followup.png">
</div>
</div>
</section>
<br/>
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan" style="color:olive;">Manual Follow-Ups</h2>
<div class="oe_demo oe_picture oe_screenshot">
<img src="manual_followup.png">
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan" style="color:olive;">Follow-ups Analysis</h2>
<div class="oe_demo oe_picture oe_screenshot">
<img src="follow_up_analysis.png">
</div>
</div>
</section>
<br/>
<br/>
<br/>
<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 support 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: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_move_line_reconcile_tree" model="ir.ui.view">
<field name="name">account.move.line.list</field>
<field name="model">account.move.line</field>
<field name="arch" type="xml">
<list string="Journal Items to Reconcile" create="false">
<field name="date"/>
<field name="move_id"/>
<field name="ref"/>
<field name="name"/>
<field name="partner_id"/>
<field name="account_id"/>
<field name="journal_id" invisible="1"/>
<field name="full_reconcile_id"/>
<field name="debit" sum="Total debit"/>
<field name="credit" sum="Total credit"/>
</list>
</field>
</record>
<record id="account_manual_reconcile_action" model="ir.actions.act_window">
<field name="context">{'search_default_unreconciled': 1,'view_mode':True}</field>
<field name="name">Journal Items to Reconcile</field>
<field name="res_model">account.move.line</field>
<field name="view_id" ref="view_move_line_reconcile_tree"/>
<field name="view_mode">list</field>
<field name="help" type="html">
<p>No journal items found.</p>
</field>
</record>
<record id="account_move_line_partner_tree" model="ir.ui.view">
<field name="name">account.move.line.partner.list</field>
<field name="model">account.move.line</field>
<field eval="32" name="priority"/>
<field name="arch" type="xml">
<list editable="bottom" string="Partner Entries">
<field name="date"/>
<field name="move_id"/>
<field name="ref"/>
<field name="name"/>
<field name="partner_id"/>
<field name="company_id" invisible="1"/>
<field name="company_id" groups="base.group_multi_company" readonly="1"/>
<field name="account_id" options="{'no_open': True, 'no_create': True}"
domain="[('company_ids', 'in', company_id)]" groups="account.group_account_readonly"/>
<field name="followup_line_id"/>
<field name="followup_date"/>
<field name="debit" sum="Total debit"/>
<field name="credit" sum="Total credit"/>
<field name="date_maturity"/>
</list>
</field>
</record>
<record id="view_move_line_form" model="ir.ui.view">
<field name="name">account.move.line.form.followup</field>
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_form"/>
<field name="arch" type="xml">
<field name="date_maturity" position="after">
<field name="followup_line_id"/>
<field name="followup_date"/>
</field>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="om_account_followup_stat_by_partner_search" model="ir.ui.view">
<field name="name">followup.stat.by.partner.search</field>
<field name="model">followup.stat.by.partner</field>
<field name="arch" type="xml">
<search string="Partner to Remind">
<field name="date_followup"/>
<filter string="Balance > 0"
domain="[('balance','&gt;',0)]" icon="terp-dolar"
name="balance_positive"/>
<field name="partner_id"/>
<field name="max_followup_id"/>
<field name="company_id"
groups="base.group_multi_company"/>
</search>
</field>
</record>
<record id="om_account_followup_stat_by_partner_tree" model="ir.ui.view">
<field name="name">followup.stat.by.partner.list</field>
<field name="model">followup.stat.by.partner</field>
<field name="arch" type="xml">
<list string="Partner to Remind">
<field name="partner_id"/>
<field name="balance"/>
<field name="max_followup_id"/>
<field name="date_followup"/>
<field name="date_move_last"/>
<field name="company_id" groups="base.group_multi_company"/>
</list>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,212 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_om_account_followup_followup_line_tree" model="ir.ui.view">
<field name="name">followup.line.list</field>
<field name="model">followup.line</field>
<field name="arch" type="xml">
<list string="Follow-up Steps">
<field name="name"/>
<field name="delay"/>
<field name="send_email"/>
<field name="send_letter"/>
<field name="manual_action"/>
</list>
</field>
</record>
<record id="view_om_account_followup_followup_line_form" model="ir.ui.view">
<field name="name">followup.line.form</field>
<field name="model">followup.line</field>
<field name="arch" type="xml">
<form string="Follow-up Steps">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name"/>
</h1>
<div class="oe_inline">
After
<field name="delay" class="oe_inline"/>
days overdue, do the following actions:
</div>
<div>
<field name="manual_action" class="oe_inline"/>
<label for="manual_action"/>
</div>
<div>
<field name="send_email" class="oe_inline"/>
<label for="send_email"/>
</div>
<div>
<field name="send_letter" class="oe_inline"/>
<label for="send_letter"/>
</div>
<group string="Manual Action" invisible="not manual_action">
<field name="manual_action_responsible_id"/>
<field name="manual_action_note"/>
</group>
<group string="Send an Email" invisible="not send_email">
<field name="email_template_id"/>
</group>
<group string="Send a Letter or Email"
invisible="not send_email and not send_letter">
<p colspan="2" class="oe_grey">
Write here the introduction in the letter,
according to the level of the follow-up. You can
use the following keywords in the text. Don't
forget to translate in all languages you installed
using to top right icon.
<table>
<tr>
<td t-translation="off">%%(partner_name)s
</td>
<td>: Partner Name</td>
</tr>
<tr>
<td t-translation="off">%%(date)s</td>
<td>: Current Date</td>
</tr>
<tr>
<td t-translation="off">
%%(user_signature)s
</td>
<td>: User Name</td>
</tr>
<tr>
<td t-translation="off">%%(company_name)s
</td>
<td>: User's Company Name</td>
</tr>
</table>
</p>
<field name="description" nolabel="1" colspan="2"/>
</group>
</form>
</field>
</record>
<record id="view_om_account_followup_followup_form" model="ir.ui.view">
<field name="name">followup.followup.form</field>
<field name="model">followup.followup</field>
<field name="arch" type="xml">
<form string="Follow-up">
<h1>
<field name="name"/>
</h1>
<label for="company_id" groups="base.group_multi_company"/>
<field name="company_id" widget="selection"
class="oe_inline"
groups="base.group_multi_company"/>
<p class="oe_grey">
To remind customers of paying their invoices, you can
define different actions depending on how severely
overdue the customer is. These actions are bundled
into follow-up levels that are triggered when the due
date of an invoice has passed a certain
number of days. If there are other overdue invoices for
the
same customer, the actions of the most
overdue invoice will be executed.
</p>
<field name="followup_line"/>
</form>
</field>
</record>
<record id="view_om_account_followup_followup_tree" model="ir.ui.view">
<field name="name">followup.followup.list</field>
<field name="model">followup.followup</field>
<field name="arch" type="xml">
<list string="Follow-up">
<field name="company_id"/>
</list>
</field>
</record>
<record id="view_om_account_followup_filter" model="ir.ui.view">
<field name="name">account.followup.select</field>
<field name="model">followup.followup</field>
<field name="arch" type="xml">
<search string="Search Follow-up">
<field name="company_id" groups="base.group_multi_company"/>
</search>
</field>
</record>
<record id="action_om_account_followup_definition_form" model="ir.actions.act_window">
<field name="name">Follow-up Levels</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">followup.followup</field>
<field name="search_view_id" ref="view_om_account_followup_filter"/>
<field name="view_mode">list,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to define follow-up levels and their related actions.
</p>
<p>
For each step, specify the actions to be taken and delay in
days. It is
possible to use print and e-mail templates to send specific
messages to
the customer.
</p>
</field>
</record>
<record id="view_move_line_reconcile_tree" model="ir.ui.view">
<field name="name">account.move.line.list</field>
<field name="model">account.move.line</field>
<field name="arch" type="xml">
<list string="Journal Items to Reconcile" create="false">
<field name="date"/>
<field name="move_id"/>
<field name="ref"/>
<field name="name"/>
<field name="partner_id"/>
<field name="account_id"/>
<field name="journal_id" invisible="1"/>
<field name="full_reconcile_id"/>
<field name="debit" sum="Total debit"/>
<field name="credit" sum="Total credit"/>
</list>
</field>
</record>
<record id="account_manual_reconcile_action" model="ir.actions.act_window">
<field name="context">{'search_default_unreconciled': 1,'view_mode':True}</field>
<field name="name">Journal Items to Reconcile</field>
<field name="res_model">account.move.line</field>
<field name="view_id" ref="view_move_line_reconcile_tree"/>
<field name="view_mode">list</field>
<field name="help" type="html">
<p>
No journal items found.
</p>
</field>
</record>
<menuitem id="om_account_followup_main_menu"
parent="account.menu_finance_configuration"
name="Follow-up"/>
<menuitem id="om_account_followup_menu"
name="Follow-up Levels"
action="action_om_account_followup_definition_form"
parent="om_account_followup_main_menu" />
<menuitem id="om_account_followup_main_menu"
parent="account.menu_finance_configuration"
name="Follow-up"/>
<menuitem id="om_account_followup_menu"
name="Follow-up Levels"
action="action_om_account_followup_definition_form"
parent="om_account_followup_main_menu"/>
</data>
</odoo>

View File

@@ -0,0 +1,192 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="customer_followup_tree" model="ir.ui.view">
<field name="name">res.partner.followup.inherit.list</field>
<field name="model">res.partner</field>
<field name="priority" eval="20"/>
<field name="arch" type="xml">
<list string="Customer Followup" create="false" delete="false">
<field name="display_name"/>
<field name="payment_next_action_date"/>
<field name="payment_next_action"/>
<field name="user_id" invisible="1"/>
<field name="country_id" invisible="1"/>
<field name="parent_id" invisible="1"/>
<field name="payment_responsible_id"/>
<field name="payment_earliest_due_date"/>
<field name="latest_followup_level_id"/>
<field name="payment_amount_overdue"/>
<field name="payment_amount_due"/>
</list>
</field>
</record>
<record id="view_partner_inherit_customer_followup_tree" model="ir.ui.view">
<field name="name">res.partner.followup.inherit.list</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_tree"/>
<field name="arch" type="xml">
<field name="complete_name" position="after">
<field name="payment_responsible_id" invisible="1"/>
</field>
</field>
</record>
<record id="customer_followup_search_view" model="ir.ui.view">
<field name="name">Search</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter"/>
<field name="arch" type="xml">
<xpath expr="//group[1]" position="after">
<group>
<filter string="Partners with Overdue Credits" domain="[('payment_amount_overdue', '>', 0.0)]"
name="credits"/>
<separator/>
<filter string="Follow-ups To Do"
domain="[('payment_next_action_date', '&lt;=', time.strftime('%%Y-%%m-%%d')), ('payment_amount_overdue', '>', 0.0)]"
name="todo"/>
<separator/>
<filter string="No Responsible" name="no_responsibe" domain="[('payment_responsible_id', '=', False)]"/>
<filter string="My Follow-ups" domain="[('payment_responsible_id','=', uid)]" name="my"/>
</group>
</xpath>
<xpath expr="//group[1]" position="inside">
<filter string="Follow-up Responsible" name="responsibe"
context="{'group_by':'payment_responsible_id'}"/>
<filter string="Followup Level" name="followup_level"
context="{'group_by':'latest_followup_level_id'}"/>
</xpath>
</field>
</record>
<record id="action_customer_followup" model="ir.actions.act_window">
<field name="name">Manual Follow-Ups</field>
<field name="view_id" ref="customer_followup_tree"/>
<field name="res_model">res.partner</field>
<field name="view_mode">list,form</field>
<field name="domain">[('payment_amount_due', '>', 0.0)]</field>
<field name="context">{'Followupfirst':True, 'search_default_todo': True}</field>
<field name="search_view_id" ref="customer_followup_search_view"/>
</record>
<record id="view_partner_inherit_followup_form" model="ir.ui.view">
<field name="name">res.partner.followup.form.inherit</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="model">res.partner</field>
<field name="arch" type="xml">
<xpath expr="//page[@name='sales_purchases']" position="after">
<page string="Payment Follow-up"
groups="account.group_account_invoice"
name="followup_tab">
<div class="oe_right"
name="followup_button">
<button name="do_button_print" type="object"
string="Print Overdue Payments"
groups="account.group_account_user"
help="Print overdue payments report independent of follow-up line"
invisible="payment_amount_due &lt;= 0.0"/>
<button name="do_partner_mail" type="object"
string="Send Overdue Email"
groups="account.group_account_user"
help="If not specified by the latest follow-up level, it will send from the default email template"
invisible="payment_amount_due &lt;= 0.0"/>
</div>
<p invisible="not latest_followup_date">
The
<field name="latest_followup_date"
class="oe_inline"/>
, the latest payment follow-up was:
<field name="latest_followup_level_id"
class="oe_inline"/>
</p>
<group>
<field name="payment_responsible_id"
placeholder="Responsible of credit collection"
class="oe_inline"/>
<label for="payment_next_action"/>
<div>
<field name="payment_next_action_date"
class="oe_inline"/>
<button name="action_done" type="object"
string="⇾ Mark as Done"
help="Click to mark the action as done."
class="oe_link"
invisible="not payment_next_action_date"
groups="account.group_account_user"/>
<field name="payment_next_action"
placeholder="Action to be taken e.g. Give a phonecall, Check if it's paid, ..."/>
</div>
</group>
<label for="payment_note" class="oe_edit_only"/>
<field name="payment_note"
placeholder="He said the problem was temporary and promised to pay 50%% before 15th of May, balance before 1st of July."/>
<p class="oe_grey">
Below is the history of the transactions of this
customer. You can check "No Follow-up" in
order to exclude it from the next follow-up
actions.
</p>
<field name="unreconciled_aml_ids">
<list string="Account Move line" editable="bottom"
create="false" delete="false"
colors="red:(not date_maturity or date_maturity&lt;=current_date) and result&gt;0">
<field name="date" readonly="True"/>
<field name="company_id" readonly="True"
groups="base.group_multi_company"/>
<field name="move_id" readonly="True"/>
<field name="date_maturity" readonly="True"/>
<field name="result" readonly="True"/>
<field name="followup_line_id" invisible='1'/>
</list>
</field>
<group class="oe_subtotal_footer oe_right">
<field name="payment_amount_due"/>
</group>
<div class="oe_clear"/>
</page>
</xpath>
</field>
</record>
<record id="action_view_customer_followup_form" model="ir.actions.act_window.view">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_partner_inherit_followup_form"/>
<field name="act_window_id" ref="action_customer_followup"/>
</record>
<record id="action_view_customer_followup_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">list</field>
<field name="view_id" ref="customer_followup_tree"/>
<field name="act_window_id" ref="action_customer_followup"/>
</record>
<menuitem id="om_account_followup_s"
action="action_customer_followup"
parent="menu_finance_followup"
name="Do Manual Follow-Ups"
sequence="3"/>
<record id="action_customer_my_followup" model="ir.actions.act_window">
<field name="name">My Follow-Ups</field>
<field name="view_id" ref="customer_followup_tree"/>
<field name="res_model">res.partner</field>
<field name="view_mode">list,form</field>
<field name="domain">[('payment_amount_due', '>', 0.0)]</field>
<field name="context">{'Followupfirst':True, 'search_default_todo': True, 'search_default_my': True}</field>
<field name="search_view_id" ref="customer_followup_search_view"/>
</record>
<menuitem id="menu_sale_followup"
parent="menu_finance_followup"
sequence="10"
action="action_customer_my_followup"
groups="account.group_account_invoice"/>
</data>
</odoo>

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<template id="report_followup">
<t t-call="web.html_container">
<t t-foreach="ids_to_objects(data['partner_ids'])" t-as="o">
<t t-set="o" t-value="o.with_context({'lang':o.partner_id.lang})"/>
<t t-call="web.external_layout">
<div class="page">
<p>
<span t-field="o.invoice_partner_id"/>
<br/>
<t t-if="o.partner_id.vat">
<span t-field="o.partner_id.vat"/>
<br/>
</t>
Document: Customer account statement
<br/>
Date:
<span t-esc="data['date']"/>
<br/>
Customer ref:
<span t-field="o.partner_id.ref"/>
</p>
<p t-raw="get_text(o,data['followup_id']).replace('\n', '&lt;br&gt;')"/>
<t t-foreach="getLines(o)" t-as="cur_lines">
<table class="table table-condensed"
style="margin-top: 50px;">
<thead>
<tr>
<th>Invoice Date</th>
<th>Description</th>
<th class="text-center">Ref</th>
<th class="text-center">Maturity Date</th>
<th class="text-right">Amount</th>
<th class="text-center">Due</th>
</tr>
</thead>
<tbody>
<tr t-foreach="cur_lines['line']"
t-as="line">
<td>
<span t-esc="line['date']"/>
</td>
<td>
<span t-esc="line['name']"/>
</td>
<td>
<span t-esc="line['ref']"/>
</td>
<td class="text-center">
<span t-esc="line['date_maturity']"/>
</td>
<td class="text-right">
<span t-esc="line['balance']"/>
</td>
</tr>
</tbody>
</table>
<p>Total:
<span t-esc="cur_lines['total']"/>
</p>
</t>
</div>
</t>
</t>
</t>
</template>
</data>
</odoo>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<odoo>
<data>
<record id="action_report_followup" model="ir.actions.report">
<field name="name">Follow-up Report</field>
<field name="model">followup.followup</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">om_account_followup.report_followup</field>
<field name="report_file">om_account_followup.report_followup</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,3 @@
from . import followup_print
from . import followup_results

View File

@@ -0,0 +1,224 @@
import datetime
import time
from odoo import api, fields, models, _
from markupsafe import Markup
class FollowupPrint(models.TransientModel):
_name = 'followup.print'
_description = 'Print Follow-up & Send Mail to Customers'
def _get_followup(self):
if self.env.context.get('active_model',
'ir.ui.menu') == 'followup.followup':
return self.env.context.get('active_id', False)
company_id = self.env.user.company_id.id
followp_id = self.env['followup.followup'].search(
[('company_id', '=', company_id)], limit=1)
return followp_id or False
date = fields.Date('Follow-up Sending Date', required=True,
help="This field allow you to select a forecast date "
"to plan your follow-ups",
default=lambda *a: time.strftime('%Y-%m-%d'))
followup_id = fields.Many2one('followup.followup', 'Follow-Up',
required=True, readonly=True,
default=_get_followup)
partner_ids = fields.Many2many('followup.stat.by.partner',
'partner_stat_rel', 'osv_memory_id',
'partner_id', 'Partners', required=True)
company_id = fields.Many2one('res.company', readonly=True,
related='followup_id.company_id')
email_conf = fields.Boolean('Send Email Confirmation')
email_subject = fields.Char('Email Subject', size=64,
default=_('Invoices Reminder'))
partner_lang = fields.Boolean(
'Send Email in Partner Language', default=True,
help='Do not change message text, if you want to send email in '
'partner language, or configure from company')
email_body = fields.Text('Email Body', default='')
summary = fields.Text('Summary', readonly=True)
test_print = fields.Boolean(
'Test Print', help='Check if you want to print follow-ups without '
'changing follow-up level.')
def process_partners(self, partner_ids, data):
partner_obj = self.env['res.partner']
partner_ids_to_print = []
nbmanuals = 0
manuals = {}
nbmails = 0
nbunknownmails = 0
nbprints = 0
resulttext = " "
for partner in self.env['followup.stat.by.partner'].browse(
partner_ids):
if partner.max_followup_id.manual_action:
partner_obj.do_partner_manual_action([partner.partner_id.id])
nbmanuals = nbmanuals + 1
key = partner.partner_id.payment_responsible_id.name or _(
"Anybody")
if key not in manuals.keys():
manuals[key] = 1
else:
manuals[key] = manuals[key] + 1
if partner.max_followup_id.send_email:
nbunknownmails += partner.partner_id.do_partner_mail()
nbmails += 1
if partner.max_followup_id.send_letter:
partner_ids_to_print.append(partner.id)
nbprints += 1
followup_without_lit = \
partner.partner_id.latest_followup_level_id_without_lit
message = "%s<I> %s </I>%s" % (_("Follow-up letter of "),
followup_without_lit.name,
_(" will be sent"))
partner.partner_id.message_post(body=message)
if nbunknownmails == 0:
resulttext += str(nbmails) + _(" email(s) sent")
else:
resulttext += str(nbmails) + _(
" email(s) should have been sent, but ") + str(
nbunknownmails) + _(
" had unknown email address(es)") + "\n <BR/> "
resulttext += "<BR/>" + str(nbprints) + _(
" letter(s) in report") + " \n <BR/>" + str(nbmanuals) + _(
" manual action(s) assigned:")
needprinting = False
if nbprints > 0:
needprinting = True
resulttext += "<p align=\"center\">"
for item in manuals:
resulttext = resulttext + "<li>" + item + ":" + str(
manuals[item]) + "\n </li>"
resulttext += "</p>"
result = {}
action = partner_obj.do_partner_print(partner_ids_to_print, data)
result['needprinting'] = needprinting
result['resulttext'] = Markup(resulttext)
result['action'] = action or {}
return result
def do_update_followup_level(self, to_update, partner_list, date):
for id in to_update.keys():
if to_update[id]['partner_id'] in partner_list:
self.env['account.move.line'].browse([int(id)]).write(
{'followup_line_id': to_update[id]['level'],
'followup_date': date})
def clear_manual_actions(self, partner_list):
partner_list_ids = [partner.partner_id.id for partner in self.env[
'followup.stat.by.partner'].browse(partner_list)]
ids = self.env['res.partner'].search(
['&', ('id', 'not in', partner_list_ids), '|',
('payment_responsible_id', '!=', False),
('payment_next_action_date', '!=', False)])
partners_to_clear = []
for part in ids:
if not part.unreconciled_aml_ids:
partners_to_clear.append(part.id)
part.action_done()
return len(partners_to_clear)
def do_process(self):
context = dict(self.env.context or {})
tmp = self._get_partners_followp()
partner_list = tmp['partner_ids']
to_update = tmp['to_update']
date = self.date
data = self.read()[0]
data['followup_id'] = data['followup_id'][0]
self.do_update_followup_level(to_update, partner_list, date)
restot_context = context.copy()
restot = self.with_context(restot_context).process_partners(
partner_list, data)
context.update(restot_context)
nbactionscleared = self.clear_manual_actions(partner_list)
if nbactionscleared > 0:
restot['resulttext'] = restot['resulttext'] + "<li>" + _(
"%s partners have no credits and as such the "
"action is cleared") % (str(nbactionscleared)) + "</li>"
resource_id = self.env.ref(
'om_account_followup.view_om_account_followup_sending_results')
context.update({'description': restot['resulttext'],
'needprinting': restot['needprinting'],
'report_data': restot['action']})
return {
'name': _('Send Letters and Emails: Actions Summary'),
'view_type': 'form',
'context': context,
'view_mode': 'list,form',
'res_model': 'followup.sending.results',
'views': [(resource_id.id, 'form')],
'type': 'ir.actions.act_window',
'target': 'new',
}
def _get_msg(self):
return self.env.user.company_id.follow_up_msg
def _get_partners_followp(self):
data = self
company_id = data.company_id.id
context = self.env.context
self.env.cr.execute(
'''SELECT
l.partner_id,
l.followup_line_id,
l.date_maturity,
l.date, l.id
FROM account_move_line AS l
LEFT JOIN account_account AS a
ON (l.account_id=a.id)
WHERE (l.full_reconcile_id IS NULL)
AND a.account_type = 'asset_receivable'
AND (l.partner_id is NOT NULL)
AND (l.debit > 0)
AND (l.company_id = %s)
ORDER BY l.date''' % (company_id))
move_lines = self.env.cr.fetchall()
old = None
fups = {}
fup_id = 'followup_id' in context and context[
'followup_id'] or data.followup_id.id
date = 'date' in context and context['date'] or data.date
date = fields.Date.to_string(date)
current_date = datetime.date(*time.strptime(date, '%Y-%m-%d')[:3])
self.env.cr.execute(
'''SELECT *
FROM followup_line
WHERE followup_id=%s
ORDER BY delay''' % (fup_id,))
for result in self.env.cr.dictfetchall():
delay = datetime.timedelta(days=result['delay'])
fups[old] = (current_date - delay, result['id'])
old = result['id']
partner_list = []
to_update = {}
for partner_id, followup_line_id, date_maturity, date, id in \
move_lines:
if not partner_id:
continue
if followup_line_id not in fups:
continue
stat_line_id = partner_id * 10000 + company_id
if date_maturity:
date_maturity = fields.Date.to_string(date_maturity)
if date_maturity <= fups[followup_line_id][0].strftime(
'%Y-%m-%d'):
if stat_line_id not in partner_list:
partner_list.append(stat_line_id)
to_update[str(id)] = {'level': fups[followup_line_id][1],
'partner_id': stat_line_id}
elif date and date <= fups[followup_line_id][0]:
if stat_line_id not in partner_list:
partner_list.append(stat_line_id)
to_update[str(id)] = {'level': fups[followup_line_id][1],
'partner_id': stat_line_id}
return {'partner_ids': partner_list, 'to_update': to_update}

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_om_account_followup_print" model="ir.ui.view">
<field name="name">account.followup.print.form</field>
<field name="model">followup.print</field>
<field name="arch" type="xml">
<form string="Send follow-ups">
<group col="4">
<field name="date" groups="base.group_no_one"/>
<field name="followup_id"
groups="base.group_multi_company"/>
</group>
<p class="oe_grey">
This action will send follow-up emails, print the
letters and
set the manual actions per customer, according to the
follow-up levels defined.
</p>
<footer>
<button name="do_process"
string="Send emails and generate letters"
type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link"
special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_om_account_followup_print" model="ir.actions.act_window">
<field name="name">Send Follow-Ups</field>
<field name="res_model">followup.print</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem id="menu_finance_followup" parent="account.menu_finance"
name="Follow-Ups"
groups="account.group_account_invoice"/>
<menuitem action="action_om_account_followup_print"
id="om_account_followup_print_menu"
parent="menu_finance_followup"
name="Send Letters and Emails"
groups="account.group_account_user,account.group_account_manager"
sequence="2"/>
</data>
</odoo>

View File

@@ -0,0 +1,21 @@
from odoo import api, fields, models, _
class FollowupSendingResults(models.TransientModel):
_name = 'followup.sending.results'
_description = 'Results from the sending of the different letters and emails'
def do_report(self):
return self.env.context.get('report_data')
def do_done(self):
return {}
def _get_description(self):
return self.env.context.get('description')
def _get_need_printing(self):
return self.env.context.get('needprinting')
description = fields.Html("Description", readonly=True, default=_get_description)
needprinting = fields.Boolean("Needs Printing", default=_get_need_printing)

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_om_account_followup_sending_results" model="ir.ui.view">
<field name="name">followup.sending.results.form</field>
<field name="model">followup.sending.results</field>
<field name="arch" type="xml">
<form string="Summary of actions">
<field name="description" class="oe_view_only"/>
<footer>
<field name="needprinting" invisible="1"/>
<div invisible="not needprinting">
<button name="do_report" string="Download Letters"
type="object" class="oe_highlight"/>
</div>
<div invisible="needprinting">
<button name="do_done" string="Close" type="object"
class="oe_highlight"/>
</div>
</footer>
</form>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,2 @@
from . import wizard
from . import models

View File

@@ -0,0 +1,23 @@
{
'name': 'Odoo 19 Fiscal Year & Lock Date',
'version': '19.0.1.0.1', # __odoosky_original_version__: '1.0.1'
'category': 'Accounting',
'summary': 'Odoo 19 Fiscal Year, Fiscal Year in Odoo 19, Lock Date in Odoo 19',
'description': 'Odoo 19 Fiscal Year, Fiscal Year in Odoo 19',
'live_test_url': 'https://www.youtube.com/watch?v=Kj4hR7_uNs4',
'sequence': '1',
'website': 'https://www.odoomates.tech',
'author': 'Odoo Mates, Odoo SA',
'maintainer': 'Odoo Mates',
'license': 'LGPL-3',
'support': 'odoomates@gmail.com',
'depends': ['account'],
'data': [
'security/security.xml',
'security/ir.model.access.csv',
'wizard/change_lock_date.xml',
'views/fiscal_year.xml',
'views/settings.xml',
],
'images': ['static/description/banner.png'],
}

View File

@@ -0,0 +1,291 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * om_fiscal_year
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-15 18:18+0000\n"
"PO-Revision-Date: 2022-04-15 18:18+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Account Period Closing"
msgstr ""
#. module: om_fiscal_year
#: model:res.groups,name:om_fiscal_year.group_fiscal_year
msgid "Allow to define fiscal years of more or less than a year"
msgstr "تسمح بتحديد السنوات المالية التي تزيد أو تقل عن عام\n"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Cancel"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_change_lock_date
msgid "Change Lock Date"
msgstr "تغيير تاريخ القفل\n"
#. module: om_fiscal_year
#: model_terms:ir.actions.act_window,help:om_fiscal_year.actions_account_fiscal_year
msgid "Click here to create a new fiscal year."
msgstr "انقر هنا لإنشاء سنة مالية جديدة.\n"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_res_company
msgid "Companies"
msgstr "شركات"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__company_id
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__company_id
msgid "Company"
msgstr "الشركة"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_res_config_settings
msgid "Config Settings"
msgstr "ضبط الاعدادات"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__create_uid
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__create_uid
msgid "Created by"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__create_date
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__create_date
msgid "Created on"
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Define fiscal years of more or less than one year"
msgstr "تحديد السنوات المالية التي تزيد أو تقل عن سنة واحدة\n"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__display_name
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__display_name
msgid "Display Name"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__date_to
msgid "End Date"
msgstr "تاريخ الانتهاء"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_account_fiscal_year__date_to
msgid "Ending Date, included in the fiscal year."
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Period Closing"
msgstr "إغلاق الفترة المالية\n"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_account_fiscal_year
#: model:ir.ui.menu,name:om_fiscal_year.menu_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Year"
msgstr "السنة المالية\n"
#. module: om_fiscal_year
#: model:ir.actions.act_window,name:om_fiscal_year.actions_account_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__group_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Years"
msgstr "السنوات المالية"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_last_day
msgid "Fiscalyear Last Day"
msgstr "آخر أيام السنة المالية"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_last_month
msgid "Fiscalyear Last Month"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__id
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__id
msgid "ID"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__period_lock_date
msgid "Journal Entries Lock Date"
msgstr "تاريخ قفل إدخالات دفتر اليومية\n"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Last Day"
msgstr "بالأمس"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year____last_update
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date____last_update
msgid "Last Modified on"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__write_uid
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__write_uid
msgid "Last Updated by"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__write_date
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__write_date
msgid "Last Updated on"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_lock_date
msgid "Lock Date"
msgstr "تاريخ القفل\n"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__fiscalyear_lock_date
msgid "Lock Date for All Users"
msgstr "تاريخ القفل لجميع المستخدمين\n"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__period_lock_date
msgid "Lock Date for Non-Advisers"
msgstr "تاريخ القفل لغير المستشارين\n"
#. module: om_fiscal_year
#: model:ir.ui.menu,name:om_fiscal_year.menu_action_change_lock_date
msgid "Lock Dates"
msgstr "تواريخ القفل\n"
#. module: om_fiscal_year
#: model:ir.actions.act_window,name:om_fiscal_year.action_view_change_lock_date
msgid "Lock your Fiscal Period"
msgstr "قفل الفترة المالية الخاصة بك\n"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Lock your fiscal period"
msgstr "قفل الفترة المالية الخاصة بك\n"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Management Closing"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__name
msgid "Name"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__tax_lock_date
msgid ""
"No users can edit journal entries related to a tax prior and inclusive of "
"this date."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_res_config_settings__fiscalyear_lock_date
msgid ""
"No users, including Advisers, can edit accounts prior to and inclusive of "
"this date. Use it for fiscal year locking for example."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__fiscalyear_lock_date
msgid ""
"No users, including Advisers, can edit accounts prior to and inclusive of "
"this date. Use it for fiscal year locking."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_res_config_settings__period_lock_date
msgid ""
"Only users with the 'Adviser' role can edit accounts prior to and inclusive "
"of this date. Use it for period locking inside an open fiscal year, for "
"example."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__period_lock_date
msgid "Prevent posting of journal entries in this period."
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Save"
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid "Show unposted entries"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__date_from
msgid "Start Date"
msgstr "تاريخ البداية"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_account_fiscal_year__date_from
msgid "Start Date, included in the fiscal year."
msgstr "تاريخ البدء مضمن في السنة المالية.\n"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__tax_lock_date
msgid "Tax Lock Date"
msgstr "تاريخ القفل الضريبي\n"
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/account_fiscal_year.py:0
#, python-format
msgid "The ending date must not be prior to the starting date."
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid ""
"There are still unposted entries in the period you want to lock. You should "
"either post or delete them."
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid ""
"There are still unreconciled bank statement lines in the period you want to "
"lock.You should either reconcile or delete them."
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/wizard/change_lock_date.py:0
#, python-format
msgid "You Are Not Allowed To Perform This Operation"
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/account_fiscal_year.py:0
#, python-format
msgid ""
"You can not have an overlap between two fiscal years, please correct the "
"start and/or end dates of your fiscal years."
msgstr ""
"لا يمكن أن يكون هناك تداخل بين سنتين ماليتين ، يرجى تصحيح تواريخ البدء و / "
"أو الانتهاء للسنتين الماليتين.\n"

View File

@@ -0,0 +1,289 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * om_fiscal_year
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-06 02:59+0000\n"
"PO-Revision-Date: 2022-07-06 02:59+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Account Period Closing"
msgstr ""
#. module: om_fiscal_year
#: model:res.groups,name:om_fiscal_year.group_fiscal_year
msgid "Allow to define fiscal years of more or less than a year"
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Cancel"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_change_lock_date
msgid "Change Lock Date"
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.actions.act_window,help:om_fiscal_year.actions_account_fiscal_year
msgid "Click here to create a new fiscal year."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_res_company
msgid "Companies"
msgstr "الشركات "
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__company_id
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__company_id
msgid "Company"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_res_config_settings
msgid "Config Settings"
msgstr "ضبط الاعدادات"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__create_uid
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__create_uid
msgid "Created by"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__create_date
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__create_date
msgid "Created on"
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Define fiscal years of more or less than one year"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__display_name
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__display_name
msgid "Display Name"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__date_to
msgid "End Date"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_account_fiscal_year__date_to
msgid "Ending Date, included in the fiscal year."
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Period Closing"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_account_fiscal_year
#: model:ir.ui.menu,name:om_fiscal_year.menu_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Year"
msgstr "السنة المالية\n"
#. module: om_fiscal_year
#: model:ir.actions.act_window,name:om_fiscal_year.actions_account_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__group_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Years"
msgstr "السنوات المالية\n"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_last_day
msgid "Fiscalyear Last Day"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_last_month
msgid "Fiscalyear Last Month"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__id
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__id
msgid "ID"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__period_lock_date
msgid "Journal Entries Lock Date"
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Last Day"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year____last_update
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date____last_update
msgid "Last Modified on"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__write_uid
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__write_uid
msgid "Last Updated by"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__write_date
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__write_date
msgid "Last Updated on"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_lock_date
msgid "Lock Date"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__fiscalyear_lock_date
msgid "Lock Date for All Users"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__period_lock_date
msgid "Lock Date for Non-Advisers"
msgstr ""
#. module: om_fiscal_year
#: model:ir.ui.menu,name:om_fiscal_year.menu_action_change_lock_date
msgid "Lock Dates"
msgstr "تواريخ القفل\n"
#. module: om_fiscal_year
#: model:ir.actions.act_window,name:om_fiscal_year.action_view_change_lock_date
msgid "Lock your Fiscal Period"
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Lock your fiscal period"
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Management Closing"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__name
msgid "Name"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__tax_lock_date
msgid ""
"No users can edit journal entries related to a tax prior and inclusive of "
"this date."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_res_config_settings__fiscalyear_lock_date
msgid ""
"No users, including Advisers, can edit accounts prior to and inclusive of "
"this date. Use it for fiscal year locking for example."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__fiscalyear_lock_date
msgid ""
"No users, including Advisers, can edit accounts prior to and inclusive of "
"this date. Use it for fiscal year locking."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_res_config_settings__period_lock_date
msgid ""
"Only users with the 'Adviser' role can edit accounts prior to and inclusive "
"of this date. Use it for period locking inside an open fiscal year, for "
"example."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__period_lock_date
msgid "Prevent posting of journal entries in this period."
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Save"
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid "Show unposted entries"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__date_from
msgid "Start Date"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_account_fiscal_year__date_from
msgid "Start Date, included in the fiscal year."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__tax_lock_date
msgid "Tax Lock Date"
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/account_fiscal_year.py:0
#, python-format
msgid "The ending date must not be prior to the starting date."
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid ""
"There are still unposted entries in the period you want to lock. You should "
"either post or delete them."
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid ""
"There are still unreconciled bank statement lines in the period you want to "
"lock.You should either reconcile or delete them."
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/wizard/change_lock_date.py:0
#, python-format
msgid "You Are Not Allowed To Perform This Operation"
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/account_fiscal_year.py:0
#, python-format
msgid ""
"You can not have an overlap between two fiscal years, please correct the "
"start and/or end dates of your fiscal years."
msgstr ""

View File

@@ -0,0 +1,250 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * om_fiscal_year
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-22 12:36+0000\n"
"PO-Revision-Date: 2024-03-22 12:36+0000\n"
"Last-Translator: Sergio Ariel Ameghino <ariel.ameghino@gmail.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Account Period Closing"
msgstr "Cierre del período de la cuenta"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_lock_date
msgid "All Users Lock Date"
msgstr "Fecha de cierre"
#. module: om_fiscal_year
#: model:res.groups,name:om_fiscal_year.group_fiscal_year
msgid "Allow to define fiscal years of more or less than a year"
msgstr "Permitir definir años fiscales de más de 1 año o menos de 1 año"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Cancel"
msgstr "Cancelar"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_change_lock_date
msgid "Change Lock Date"
msgstr "Cambiar fecha de bloqueo"
#. module: om_fiscal_year
#: model_terms:ir.actions.act_window,help:om_fiscal_year.actions_account_fiscal_year
msgid "Click here to create a new fiscal year."
msgstr "Haga clic aquí para crear un nuevo año fiscal."
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_res_company
msgid "Companies"
msgstr "Compañías"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__company_id
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__company_id
msgid "Company"
msgstr "Compañía"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_res_config_settings
msgid "Config Settings"
msgstr "Ajustes de configuración"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__create_uid
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__create_uid
msgid "Created by"
msgstr "Creado por"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__create_date
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__create_date
msgid "Created on"
msgstr "Creado en"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Define fiscal years of more or less than one year"
msgstr "Definir años fiscales de más de 1 año o menos de 1 año"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__display_name
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__display_name
msgid "Display Name"
msgstr "Nombre mostrado"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__date_to
msgid "End Date"
msgstr "Fecha final"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_account_fiscal_year__date_to
msgid "Ending Date, included in the fiscal year."
msgstr "Fecha de finalización, incluida en el ejercicio fiscal."
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Period Closing"
msgstr "Cierre del período fiscal"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_account_fiscal_year
#: model:ir.ui.menu,name:om_fiscal_year.menu_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Year"
msgstr "Año fiscal"
#. module: om_fiscal_year
#: model:ir.actions.act_window,name:om_fiscal_year.actions_account_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__group_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Years"
msgstr "Años fiscales"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_last_day
msgid "Fiscalyear Last Day"
msgstr "Año fiscal último día"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_last_month
msgid "Fiscalyear Last Month"
msgstr "Año fiscal último mes"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__id
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__id
msgid "ID"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__period_lock_date
msgid "Journal Entries Lock Date"
msgstr "Fecha de bloqueo de asientos de diario"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__period_lock_date
msgid "Journals Entries Lock Date"
msgstr "Fecha de bloqueo para no asesores"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Last Day"
msgstr "Último día"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__write_uid
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__write_uid
msgid "Last Updated by"
msgstr "Última actualización por"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__write_date
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__write_date
msgid "Last Updated on"
msgstr "Última actualización en"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__fiscalyear_lock_date
msgid "Lock Date for All Users"
msgstr "Fecha de bloqueo para todos los usuarios"
#. module: om_fiscal_year
#: model:ir.ui.menu,name:om_fiscal_year.menu_action_change_lock_date
msgid "Lock Dates"
msgstr "Fechas de bloqueo"
#. module: om_fiscal_year
#: model:ir.actions.act_window,name:om_fiscal_year.action_view_change_lock_date
msgid "Lock your Fiscal Period"
msgstr "Bloqueo de su período fiscal"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Lock your fiscal period"
msgstr "Bloqueo de su período fiscal"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Management Closing"
msgstr "Cierre de gestión"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__name
msgid "Name"
msgstr "Nombre"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__tax_lock_date
msgid ""
"No users can edit journal entries related to a tax prior and inclusive of "
"this date."
msgstr ""
"Ningún usuario puede editar asientos de diario relacionados con un impuesto "
"anterior e inclusive de esta fecha."
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_res_config_settings__fiscalyear_lock_date
msgid ""
"No users, including Advisers, can edit accounts prior to and inclusive of "
"this date. Use it for fiscal year locking for example."
msgstr ""
"Ningún usuario, incluidos los asesores, puede editar cuentas antes de esta "
"fecha inclusive. Úselo para bloquear el año fiscal por ejemplo."
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__fiscalyear_lock_date
msgid ""
"No users, including Advisers, can edit accounts prior to and inclusive of "
"this date. Use it for fiscal year locking."
msgstr ""
"Ningún usuario, incluidos los Asesores, puede editar cuentas antes de esta "
"fecha inclusive. Úselo para bloquear el año fiscal."
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_res_config_settings__period_lock_date
msgid ""
"Only users with the 'Adviser' role can edit accounts prior to and inclusive "
"of this date. Use it for period locking inside an open fiscal year, for "
"example."
msgstr ""
"Solo los usuarios con el rol de 'Asesor' pueden editar cuentas antes de esta"
" fecha inclusive. Úselo para el bloqueo de períodos dentro de un año fiscal "
"abierto, por ejemplo."
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__period_lock_date
msgid "Prevent posting of journal entries in this period."
msgstr "Evitar la contabilización de asientos de diario en este período."
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Save"
msgstr "Guardar"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__date_from
msgid "Start Date"
msgstr "Fecha de inicio"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_account_fiscal_year__date_from
msgid "Start Date, included in the fiscal year."
msgstr "Fecha de inicio, incluida en el ejercicio fiscal."
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__tax_lock_date
msgid "Tax Lock Date"
msgstr "Fecha de bloqueo de impuestos"

View File

@@ -0,0 +1,309 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * om_fiscal_year
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-15 06:46+0000\n"
"PO-Revision-Date: 2022-07-06 00:19+0200\n"
"Last-Translator: Sylvain Lc\n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Poedit 3.1\n"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Account Period Closing"
msgstr "Clôture de la période du compte"
#. module: om_fiscal_year
#: model:res.groups,name:om_fiscal_year.group_fiscal_year
msgid "Allow to define fiscal years of more or less than a year"
msgstr "Permettre de définir des années fiscales de plus ou moins d'un an"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Cancel"
msgstr "Annuler"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_change_lock_date
msgid "Change Lock Date"
msgstr "Changer la date de cloture"
#. module: om_fiscal_year
#: model_terms:ir.actions.act_window,help:om_fiscal_year.actions_account_fiscal_year
msgid "Click here to create a new fiscal year."
msgstr "Cliquez ici pour créer une nouvelle année fiscale."
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_res_company
msgid "Companies"
msgstr "Sociétés"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__company_id
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__company_id
msgid "Company"
msgstr "Société"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_res_config_settings
msgid "Config Settings"
msgstr "Paramètres de Configuration"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__create_uid
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__create_uid
msgid "Created by"
msgstr "Créé par"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__create_date
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__create_date
msgid "Created on"
msgstr "Créé sur"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Define fiscal years of more or less than one year"
msgstr "Définir des exercices de plus ou de moins d'un an"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__display_name
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__display_name
msgid "Display Name"
msgstr "Afficher un nom"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__date_to
msgid "End Date"
msgstr "Date de fin"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_account_fiscal_year__date_to
msgid "Ending Date, included in the fiscal year."
msgstr "Date de fin, comprise dans l'année fiscale."
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Period Closing"
msgstr "Clôture de lexercice"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_account_fiscal_year
#: model:ir.ui.menu,name:om_fiscal_year.menu_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Year"
msgstr "Exercice fiscal"
#. module: om_fiscal_year
#: model:ir.actions.act_window,name:om_fiscal_year.actions_account_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__group_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Years"
msgstr "Exercices fiscaux"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_last_day
msgid "Fiscalyear Last Day"
msgstr "Dernier jour de l'exercice fiscal"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_last_month
msgid "Fiscalyear Last Month"
msgstr "Dernier mois de l'exercice fiscal"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__id
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__id
msgid "ID"
msgstr "ID"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__period_lock_date
msgid "Journal Entries Lock Date"
msgstr "Date de verrouillage des entrées de journal"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Last Day"
msgstr "Dernier jour"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year____last_update
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date____last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__write_uid
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__write_uid
msgid "Last Updated by"
msgstr "Dernière mise à jour par"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__write_date
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__write_date
msgid "Last Updated on"
msgstr "Dernière mise à jour le"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_lock_date
msgid "Lock Date"
msgstr "Date de verrouillage"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__fiscalyear_lock_date
msgid "Lock Date for All Users"
msgstr "Date de clôture pour tous les utilisateurs"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__period_lock_date
msgid "Lock Date for Non-Advisers"
msgstr "Date de verrouillage pour les non-conseillers"
#. module: om_fiscal_year
#: model:ir.ui.menu,name:om_fiscal_year.menu_action_change_lock_date
msgid "Lock Dates"
msgstr "Date de clôture"
#. module: om_fiscal_year
#: model:ir.actions.act_window,name:om_fiscal_year.action_view_change_lock_date
msgid "Lock your Fiscal Period"
msgstr "Clôturer la période fiscale"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Lock your fiscal period"
msgstr "Clôturer la période fiscale"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Management Closing"
msgstr "Clôture de la direction"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__name
msgid "Name"
msgstr "Nom"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__tax_lock_date
msgid ""
"No users can edit journal entries related to a tax prior and inclusive of "
"this date."
msgstr ""
"Aucun utilisateur ne peut modifier des pièces comptables relatives à une "
"taxe avant et au jour de cette date."
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_res_config_settings__fiscalyear_lock_date
msgid ""
"No users, including Advisers, can edit accounts prior to and inclusive of "
"this date. Use it for fiscal year locking for example."
msgstr ""
"Aucun utilisateur, y compris les conseillers, ne peuvent modifier les "
"comptes comptables avant cette date inclusivement. Utilisez cela pour "
"verrouiller l'exercice fiscal par exemple."
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__fiscalyear_lock_date
msgid ""
"No users, including Advisers, can edit accounts prior to and inclusive of "
"this date. Use it for fiscal year locking."
msgstr ""
"Aucun utilisateur, y compris les conseillers, ne peuvent modifier les "
"comptes comptables avant cette date inclusivement. Utilisez cela pour "
"verrouiller l'exercice fiscal par exemple."
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_res_config_settings__period_lock_date
msgid ""
"Only users with the 'Adviser' role can edit accounts prior to and inclusive "
"of this date. Use it for period locking inside an open fiscal year, for "
"example."
msgstr ""
"Seuls les utilisateurs avec le rôle 'Conseiller' peuvent modifier les "
"comptes comptables avant cette date inclusivement. Utilisez cela pour "
"verrouiller des périodes pendant l'exercice fiscal par exemple."
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__period_lock_date
msgid "Prevent posting of journal entries in this period."
msgstr "Empêcher la publication dentrées de journal pendant cette période."
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Save"
msgstr "Sauvegarder"
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid "Show unposted entries"
msgstr "Montrer les Ecritures non Postées"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__date_from
msgid "Start Date"
msgstr "Date de début"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_account_fiscal_year__date_from
msgid "Start Date, included in the fiscal year."
msgstr "Date de début, incluse dans l'année fiscale."
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__tax_lock_date
msgid "Tax Lock Date"
msgstr "Date de verrouillage de la taxe"
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/account_fiscal_year.py:0
#, python-format
msgid "The ending date must not be prior to the starting date."
msgstr "La date de fin ne peut être antérieure à la date de début."
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid ""
"There are still unposted entries in the period you want to lock. You should "
"either post or delete them."
msgstr ""
"Il y a toujours des entrées non comptabilisées dans la période que vous "
"souhaitez verrouiller. Vous devez les comptabiliser ou les supprimer."
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid ""
"There are still unreconciled bank statement lines in the period you want to "
"lock.You should either reconcile or delete them."
msgstr ""
"Il reste des lignes de relevé bancaire non rapprochées dans la période que "
"vous souhaitez verrouiller. Vous devez soit les rapprocher, soit les "
"supprimer."
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/wizard/change_lock_date.py:0
#, python-format
msgid "You Are Not Allowed To Perform This Operation"
msgstr "Vous nêtes pas autorisé à effectuer cette opération"
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/account_fiscal_year.py:0
#, python-format
msgid ""
"You can not have an overlap between two fiscal years, please correct the "
"start and/or end dates of your fiscal years."
msgstr ""
"Vous ne pouvez pas avoir de chevauchement entre deux exercices, veuillez "
"corriger les dates de début et/ou de fin de vos exercices."

View File

@@ -0,0 +1,289 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * om_fiscal_year
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-15 06:46+0000\n"
"PO-Revision-Date: 2022-04-15 06:46+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Account Period Closing"
msgstr ""
#. module: om_fiscal_year
#: model:res.groups,name:om_fiscal_year.group_fiscal_year
msgid "Allow to define fiscal years of more or less than a year"
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Cancel"
msgstr "İptal"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_change_lock_date
msgid "Change Lock Date"
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.actions.act_window,help:om_fiscal_year.actions_account_fiscal_year
msgid "Click here to create a new fiscal year."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_res_company
msgid "Companies"
msgstr "Şirketler"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__company_id
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__company_id
msgid "Company"
msgstr "Şirket"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_res_config_settings
msgid "Config Settings"
msgstr "Yapılandırma Ayarları"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__create_uid
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__create_uid
msgid "Created by"
msgstr "Oluşturan"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__create_date
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__create_date
msgid "Created on"
msgstr "Oluşturulma Tarihi"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Define fiscal years of more or less than one year"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__display_name
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__display_name
msgid "Display Name"
msgstr "Görüntülenen Ad"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__date_to
msgid "End Date"
msgstr "Bitiş Tarihi"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_account_fiscal_year__date_to
msgid "Ending Date, included in the fiscal year."
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Period Closing"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_account_fiscal_year
#: model:ir.ui.menu,name:om_fiscal_year.menu_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Year"
msgstr "Hesap Yılı"
#. module: om_fiscal_year
#: model:ir.actions.act_window,name:om_fiscal_year.actions_account_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__group_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Years"
msgstr "Hesap Yılları"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_last_day
msgid "Fiscalyear Last Day"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_last_month
msgid "Fiscalyear Last Month"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__id
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__id
msgid "ID"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__period_lock_date
msgid "Journal Entries Lock Date"
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Last Day"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year____last_update
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date____last_update
msgid "Last Modified on"
msgstr "Son Değişiklik Tarihi"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__write_uid
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__write_uid
msgid "Last Updated by"
msgstr "Son Güncellemeyi Yapan"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__write_date
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__write_date
msgid "Last Updated on"
msgstr "Son Güncelleme Tarihi"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_lock_date
msgid "Lock Date"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__fiscalyear_lock_date
msgid "Lock Date for All Users"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__period_lock_date
msgid "Lock Date for Non-Advisers"
msgstr ""
#. module: om_fiscal_year
#: model:ir.ui.menu,name:om_fiscal_year.menu_action_change_lock_date
msgid "Lock Dates"
msgstr "Kilitleme Tarihleri"
#. module: om_fiscal_year
#: model:ir.actions.act_window,name:om_fiscal_year.action_view_change_lock_date
msgid "Lock your Fiscal Period"
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Lock your fiscal period"
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Management Closing"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__name
msgid "Name"
msgstr "İsim"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__tax_lock_date
msgid ""
"No users can edit journal entries related to a tax prior and inclusive of "
"this date."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_res_config_settings__fiscalyear_lock_date
msgid ""
"No users, including Advisers, can edit accounts prior to and inclusive of "
"this date. Use it for fiscal year locking for example."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__fiscalyear_lock_date
msgid ""
"No users, including Advisers, can edit accounts prior to and inclusive of "
"this date. Use it for fiscal year locking."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_res_config_settings__period_lock_date
msgid ""
"Only users with the 'Adviser' role can edit accounts prior to and inclusive "
"of this date. Use it for period locking inside an open fiscal year, for "
"example."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__period_lock_date
msgid "Prevent posting of journal entries in this period."
msgstr ""
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Save"
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid "Show unposted entries"
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__date_from
msgid "Start Date"
msgstr "Başlangıç Tarihi"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_account_fiscal_year__date_from
msgid "Start Date, included in the fiscal year."
msgstr ""
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__tax_lock_date
msgid "Tax Lock Date"
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/account_fiscal_year.py:0
#, python-format
msgid "The ending date must not be prior to the starting date."
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid ""
"There are still unposted entries in the period you want to lock. You should "
"either post or delete them."
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid ""
"There are still unreconciled bank statement lines in the period you want to "
"lock.You should either reconcile or delete them."
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/wizard/change_lock_date.py:0
#, python-format
msgid "You Are Not Allowed To Perform This Operation"
msgstr ""
#. module: om_fiscal_year
#: code:addons/om_fiscal_year/models/account_fiscal_year.py:0
#, python-format
msgid ""
"You can not have an overlap between two fiscal years, please correct the "
"start and/or end dates of your fiscal years."
msgstr ""

View File

@@ -0,0 +1,289 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * om_fiscal_year
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-21 02:42+0000\n"
"PO-Revision-Date: 2024-03-21 02:42+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Account Period Closing"
msgstr "會計關帳日期"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_lock_date
msgid "Lock Date for All Users"
msgstr "所有使用者關帳日期"
#. module: om_fiscal_year
#: model:res.groups,name:om_fiscal_year.group_fiscal_year
msgid "Allow to define fiscal years of more or less than a year"
msgstr "允許定義多於或少於一年的會計年度"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Cancel"
msgstr "取消"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_change_lock_date
msgid "Change Lock Date"
msgstr "更改關帳日期"
#. module: om_fiscal_year
#: model_terms:ir.actions.act_window,help:om_fiscal_year.actions_account_fiscal_year
msgid "Click here to create a new fiscal year."
msgstr "點選新增會計年度"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_res_company
msgid "Companies"
msgstr "公司"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__company_id
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__company_id
msgid "Company"
msgstr "公司"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_res_config_settings
msgid "Config Settings"
msgstr "配置設定"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__create_uid
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__create_uid
msgid "Created by"
msgstr "建立於"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__create_date
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__create_date
msgid "Created on"
msgstr "建立於"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Define fiscal years of more or less than one year"
msgstr "定義多於或少於一年的會計年度"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__display_name
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__display_name
msgid "Display Name"
msgstr "顯示名稱"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__date_to
msgid "End Date"
msgstr "結束日期"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_account_fiscal_year__date_to
msgid "Ending Date, included in the fiscal year."
msgstr "結束日期,包含在會計年度中"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Period Closing"
msgstr "會計關帳期間"
#. module: om_fiscal_year
#: model:ir.model,name:om_fiscal_year.model_account_fiscal_year
#: model:ir.ui.menu,name:om_fiscal_year.menu_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Year"
msgstr "會計年度"
#. module: om_fiscal_year
#: model:ir.actions.act_window,name:om_fiscal_year.actions_account_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__group_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Fiscal Years"
msgstr "會計年度"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_last_day
msgid "Fiscalyear Last Day"
msgstr "會計年度最後一天"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__fiscalyear_last_month
msgid "Fiscalyear Last Month"
msgstr "會計年度最後一月"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__id
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__id
msgid "ID"
msgstr "ID"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__period_lock_date
msgid "Journal Entries Lock Date"
msgstr "日記帳本關帳日期"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_res_config_settings__period_lock_date
msgid "Journals Entries Lock Date"
msgstr "日記帳本關帳日期"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Last Day"
msgstr "最後一天"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__write_uid
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__write_uid
msgid "Last Updated by"
msgstr "最後更新"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__write_date
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__write_date
msgid "Last Updated on"
msgstr "最後更新"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__fiscalyear_lock_date
msgid "Lock Date for All Users"
msgstr "所有使用者關帳日期"
#. module: om_fiscal_year
#: model:ir.ui.menu,name:om_fiscal_year.menu_action_change_lock_date
msgid "Lock Dates"
msgstr "會計關帳日期"
#. module: om_fiscal_year
#: model:ir.actions.act_window,name:om_fiscal_year.action_view_change_lock_date
msgid "Lock your Fiscal Period"
msgstr "會計關帳期間"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.res_config_settings_view_form
msgid "Lock your fiscal period"
msgstr "會計關帳期間"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Management Closing"
msgstr "管理關帳日期"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__name
msgid "Name"
msgstr "名稱"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__tax_lock_date
msgid ""
"No users can edit journal entries related to a tax prior and inclusive of "
"this date."
msgstr "任何使用者都無法編輯與該日期之前(含該日期)的稅務相關的日記帳分錄。"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_res_config_settings__fiscalyear_lock_date
msgid ""
"No users, including Advisers, can edit accounts prior to and inclusive of "
"this date. Use it for fiscal year locking for example."
msgstr "任何使用者(包括管理者)都不能在「之前」(含)編輯帳戶這個日期。例如,將其用於會計年度關帳。”"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__fiscalyear_lock_date
msgid ""
"No users, including Advisers, can edit accounts prior to and inclusive of "
"this date. Use it for fiscal year locking."
msgstr "在此日期之前(包括該日期),任何使用者(包括管理者)都不能編輯帳戶。 使用它來關帳會計年度。"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_res_config_settings__period_lock_date
msgid ""
"Only users with the 'Adviser' role can edit accounts prior to and inclusive "
"of this date. Use it for period locking inside an open fiscal year, for "
"example."
msgstr "只有具有「管理者」角色的使用者才能在此日期之前(含該日期)編輯帳戶。 例如,將其用於開放會計年度內的期間關帳。"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_change_lock_date__period_lock_date
msgid "Prevent posting of journal entries in this period."
msgstr "防止在此期間過帳日記帳分錄"
#. module: om_fiscal_year
#: model_terms:ir.ui.view,arch_db:om_fiscal_year.view_change_lock_date
msgid "Save"
msgstr "保存"
#. module: om_fiscal_year
#. odoo-python
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid "Show unposted entries"
msgstr "顯示未過帳的分錄"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_account_fiscal_year__date_from
msgid "Start Date"
msgstr "開始日期"
#. module: om_fiscal_year
#: model:ir.model.fields,help:om_fiscal_year.field_account_fiscal_year__date_from
msgid "Start Date, included in the fiscal year."
msgstr "開始日期,包含在會計年度中。"
#. module: om_fiscal_year
#: model:ir.model.fields,field_description:om_fiscal_year.field_change_lock_date__tax_lock_date
msgid "Tax Lock Date"
msgstr "稅額關帳日期"
#. module: om_fiscal_year
#. odoo-python
#: code:addons/om_fiscal_year/models/account_fiscal_year.py:0
#, python-format
msgid "The ending date must not be prior to the starting date."
msgstr "結束日期不得早於開始日期。"
#. module: om_fiscal_year
#. odoo-python
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid ""
"There are still unposted entries in the period you want to lock. You should "
"either post or delete them."
msgstr "在您想要關帳的期間內仍有未過帳的條目。 您應該發布或刪除它們。"
#. module: om_fiscal_year
#. odoo-python
#: code:addons/om_fiscal_year/models/res_company.py:0
#, python-format
msgid ""
"There are still unreconciled bank statement lines in the period you want to "
"lock.You should either reconcile or delete them."
msgstr "您想要關帳的期間內仍有未調整的銀行對帳單行。您應該調整或刪除它們"
#. module: om_fiscal_year
#. odoo-python
#: code:addons/om_fiscal_year/wizard/change_lock_date.py:0
#, python-format
msgid "You Are Not Allowed To Perform This Operation"
msgstr "您無權執行此操作"
#. module: om_fiscal_year
#. odoo-python
#: code:addons/om_fiscal_year/models/account_fiscal_year.py:0
#, python-format
msgid ""
"You can not have an overlap between two fiscal years, please correct the "
"start and/or end dates of your fiscal years."
msgstr "兩個會計年度之間不能有重疊,請更正會計年度的開始和/或結束日期。"

View File

@@ -0,0 +1,3 @@
from . import account_fiscal_year
from . import account_settings
from . import res_company

View File

@@ -0,0 +1,48 @@
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
class AccountFiscalYear(models.Model):
_name = 'account.fiscal.year'
_description = 'Fiscal Year'
name = fields.Char(string='Name', required=True)
date_from = fields.Date(string='Start Date', required=True,
help='Start Date, included in the fiscal year.')
date_to = fields.Date(string='End Date', required=True,
help='Ending Date, included in the fiscal year.')
company_id = fields.Many2one('res.company', string='Company', required=True,
default=lambda self: self.env.company)
@api.constrains('date_from', 'date_to', 'company_id')
def _check_dates(self):
'''
Check interleaving between fiscal years.
There are 3 cases to consider:
s1 s2 e1 e2
( [----)----]
s2 s1 e2 e1
[----(----] )
s1 s2 e2 e1
( [----] )
'''
for fy in self:
# Starting date must be prior to the ending date
date_from = fy.date_from
date_to = fy.date_to
if date_to < date_from:
raise ValidationError(_('The ending date must not be prior to the starting date.'))
domain = [
('id', '!=', fy.id),
('company_id', '=', fy.company_id.id),
'|', '|',
'&', ('date_from', '<=', fy.date_from), ('date_to', '>=', fy.date_from),
'&', ('date_from', '<=', fy.date_to), ('date_to', '>=', fy.date_to),
'&', ('date_from', '<=', fy.date_from), ('date_to', '>=', fy.date_to),
]
if self.search_count(domain) > 0:
raise ValidationError(_('You can not have an overlap between two fiscal years, '
'please correct the start and/or end dates of your fiscal years.'))

View File

@@ -0,0 +1,30 @@
from odoo import api, fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
fiscalyear_last_day = fields.Integer(
related='company_id.fiscalyear_last_day', readonly=False
)
fiscalyear_last_month = fields.Selection(
related='company_id.fiscalyear_last_month', readonly=False
)
tax_lock_date = fields.Date(
related='company_id.hard_lock_date', readonly=False
)
sale_lock_date = fields.Date(
related='company_id.hard_lock_date', readonly=False
)
purchase_lock_date = fields.Date(
related='company_id.hard_lock_date', readonly=False
)
hard_lock_date = fields.Date(
related='company_id.hard_lock_date', readonly=False
)
fiscalyear_lock_date = fields.Date(
related='company_id.fiscalyear_lock_date', readonly=False
)
group_fiscal_year = fields.Boolean(
string='Fiscal Years', implied_group='om_fiscal_year.group_fiscal_year'
)

View File

@@ -0,0 +1,38 @@
from odoo import api, fields, models, _
from odoo.exceptions import RedirectWarning, ValidationError
class ResCompany(models.Model):
_inherit = 'res.company'
def _validate_fiscalyear_lock(self, values):
if values.get('fiscalyear_lock_date'):
draft_entries = self.env['account.move'].search([
('company_id', 'in', self.ids),
('state', '=', 'draft'),
('date', '<=', values['fiscalyear_lock_date'])])
if draft_entries:
error_msg = _(
'There are still unposted entries in the period you want to lock. You should either post or delete them.')
action_error = {
'view_mode': 'list',
'name': 'Unposted Entries',
'res_model': 'account.move',
'type': 'ir.actions.act_window',
'domain': [('id', 'in', draft_entries.ids)],
'search_view_id': [self.env.ref('account.view_account_move_filter').id, 'search'],
'views': [[self.env.ref('account.view_move_tree').id, 'list'],
[self.env.ref('account.view_move_form').id, 'form']],
}
raise RedirectWarning(error_msg, action_error, _('Show unposted entries'))
unreconciled_statement_lines = self.env['account.bank.statement.line'].search([
('company_id', 'in', self.ids),
('is_reconciled', '=', False),
('date', '<=', values['fiscalyear_lock_date']),
('move_id.state', 'in', ('draft', 'posted')),
])
if unreconciled_statement_lines:
error_msg = _("There are still unreconciled bank statement lines in the period you want to lock."
"You should either reconcile or delete them.")
raise ValidationError(error_msg)

View File

@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_fiscal_year,access.account.fiscal.year,model_account_fiscal_year,account.group_account_user,1,1,0,0
access_account_fiscal_year_manager,access.account.fiscal.year.manager,model_account_fiscal_year,account.group_account_manager,1,1,1,1
access_change_lock_date_manager,access.change.lock.date.manager,model_change_lock_date,account.group_account_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_fiscal_year access.account.fiscal.year model_account_fiscal_year account.group_account_user 1 1 0 0
3 access_account_fiscal_year_manager access.account.fiscal.year.manager model_account_fiscal_year account.group_account_manager 1 1 1 1
4 access_change_lock_date_manager access.change.lock.date.manager model_change_lock_date account.group_account_manager 1 1 1 1

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<record id="group_fiscal_year" model="res.groups">
<field name="name">Allow to define fiscal years of more or less than a year</field>
<field name="user_ids" eval="[Command.link(ref('base.user_root')), Command.link(ref('base.user_admin'))]"/>
</record>
</data>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,60 @@
<section class="oe_container oe_dark">
<div class="col-md-12">
<h2 class="oe_slogan" style="font-size: 35px;color:#2C0091"><b>Odoo 19 Fiscal Year & Closing</b></h2>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h3 class="oe_slogan" style="color:#332c3c;font-size: 28px;">Fiscal Year</h3>
<div class="oe_demo oe_picture oe_screenshot">
<img src="fiscal_year.png" style="height:400px;">
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h3 class="oe_slogan" style="color:#332c3c;font-size: 28px;">Lock Date</h3>
<div class="oe_demo oe_picture oe_screenshot">
<img src="lock_date.png" style="height:400px;">
</div>
</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: 73 KiB

Some files were not shown because too many files have changed in this diff Show More