[16.1] Add lock date migration and settings UI

This commit is contained in:
Ernad Husremovic 2025-11-02 11:25:27 +01:00
parent 64fdc5b0df
commit c7d2961320
6 changed files with 186 additions and 14 deletions

View file

@ -0,0 +1,63 @@
from odoo import api, SUPERUSER_ID
def _table_has_column(cr, table, column):
cr.execute(
"""
SELECT 1
FROM information_schema.columns
WHERE table_name = %s AND column_name = %s
""",
(table, column),
)
return bool(cr.fetchone())
def migrate(cr, version):
if not version:
return
env = api.Environment(cr, SUPERUSER_ID, {})
# Initialise new company lock fields using existing lock dates.
companies = env['res.company'].with_context(active_test=False).search([])
for company in companies:
vals = {}
if not company.sale_lock_date and company.period_lock_date:
vals['sale_lock_date'] = company.period_lock_date
if not company.purchase_lock_date and company.period_lock_date:
vals['purchase_lock_date'] = company.period_lock_date
if not company.hard_lock_date and company.fiscalyear_lock_date:
vals['hard_lock_date'] = company.fiscalyear_lock_date
if vals:
company.sudo().write(vals)
# If account_move_export is installed, migrate configuration defaults.
if env.registry.get('account.move.export.config'):
# Ensure new boolean fields are not left NULL and join_char has a value.
cr.execute(
"""
UPDATE account_move_export_config
SET group_lines = COALESCE(group_lines, FALSE),
lock_tax = COALESCE(lock_tax, FALSE),
lock_sale = COALESCE(lock_sale, FALSE),
lock_purchase = COALESCE(lock_purchase, FALSE),
lock_fiscalyear = COALESCE(lock_fiscalyear, FALSE),
lock_hard = COALESCE(lock_hard, FALSE),
join_char = COALESCE(NULLIF(join_char, ''), '-')
"""
)
if _table_has_column(cr, 'account_move_export_config', 'lock'):
cr.execute(
"""
UPDATE account_move_export_config
SET lock_tax = lock_tax OR lock IN ('tax', 'period', 'fiscalyear'),
lock_sale = lock_sale OR lock IN ('period', 'fiscalyear'),
lock_purchase = lock_purchase OR lock IN ('period', 'fiscalyear'),
lock_fiscalyear = lock_fiscalyear OR lock = 'fiscalyear',
lock_hard = lock_hard OR lock = 'fiscalyear'
"""
)
# Optionally drop the legacy column to avoid confusion.
cr.execute("ALTER TABLE account_move_export_config DROP COLUMN lock")

View file

@ -38,6 +38,13 @@ class AccountAccount(models.Model):
currency_id = fields.Many2one('res.currency', string='Account Currency', tracking=True, currency_id = fields.Many2one('res.currency', string='Account Currency', tracking=True,
help="Forces all journal items in this account to have a specific currency (i.e. bank journals). If no currency is set, entries can use any currency.") help="Forces all journal items in this account to have a specific currency (i.e. bank journals). If no currency is set, entries can use any currency.")
company_currency_id = fields.Many2one(related='company_id.currency_id') company_currency_id = fields.Many2one(related='company_id.currency_id')
company_ids = fields.Many2many(
'res.company',
compute='_compute_company_ids',
search='_search_company_ids',
string='Companies',
compute_sudo=True,
)
code = fields.Char(size=64, required=True, tracking=True, unaccent=False) code = fields.Char(size=64, required=True, tracking=True, unaccent=False)
deprecated = fields.Boolean(default=False, tracking=True) deprecated = fields.Boolean(default=False, tracking=True)
used = fields.Boolean(compute='_compute_used', search='_search_used') used = fields.Boolean(compute='_compute_used', search='_search_used')
@ -344,6 +351,23 @@ class AccountAccount(models.Model):
for record in self: for record in self:
record.used = record.id in ids record.used = record.id in ids
def _compute_company_ids(self):
for account in self:
account.company_ids = account.company_id
def _search_company_ids(self, operator, value):
if operator in ('in', 'not in'):
if isinstance(value, (list, tuple, set)):
values = list(value)
elif hasattr(value, 'ids'):
values = value.ids
else:
values = [value]
return [('company_id', operator, values)]
if hasattr(value, 'id'):
value = value.id
return [('company_id', operator, value)]
@api.model @api.model
def _search_new_account_code(self, company, digits, prefix): def _search_new_account_code(self, company, digits, prefix):
for num in range(1, 10000): for num in range(1, 10000):

View file

@ -57,6 +57,21 @@ class ResCompany(models.Model):
string="Tax Return Lock Date", string="Tax Return Lock Date",
tracking=True, tracking=True,
help="No users can edit journal entries related to a tax prior and inclusive of this date.") help="No users can edit journal entries related to a tax prior and inclusive of this date.")
sale_lock_date = fields.Date(
string="Sales Lock Date",
tracking=True,
help="No users can post or modify customer invoices prior to and inclusive of this date."
)
purchase_lock_date = fields.Date(
string="Purchases Lock Date",
tracking=True,
help="No users can post or modify vendor bills prior to and inclusive of this date."
)
hard_lock_date = fields.Date(
string="Hard Lock Date",
tracking=True,
help="No users can post or modify any journal entries prior to and inclusive of this date."
)
transfer_account_id = fields.Many2one('account.account', transfer_account_id = fields.Many2one('account.account',
domain="[('reconcile', '=', True), ('account_type', '=', 'asset_current'), ('deprecated', '=', False)]", string="Inter-Banks Transfer Account", help="Intermediary account used when moving money from a liqity account to another") domain="[('reconcile', '=', True), ('account_type', '=', 'asset_current'), ('deprecated', '=', False)]", string="Inter-Banks Transfer Account", help="Intermediary account used when moving money from a liqity account to another")
expects_chart_of_accounts = fields.Boolean(string='Expects a Chart of Accounts', default=True) expects_chart_of_accounts = fields.Boolean(string='Expects a Chart of Accounts', default=True)
@ -375,25 +390,41 @@ class ResCompany(models.Model):
return lock_date return lock_date
def write(self, values): def write(self, values):
#restrict the closing of FY if there are still unposted entries # Restrict the closing of FY if there are still unposted entries
self._validate_fiscalyear_lock(values) self._validate_fiscalyear_lock(values)
# Reflect the change on accounts result = True
for company in self: for company in self:
if values.get('bank_account_code_prefix'): company_vals = values.copy()
new_bank_code = values.get('bank_account_code_prefix') or company.bank_account_code_prefix old_bank_prefix = company.bank_account_code_prefix
company.reflect_code_prefix_change(company.bank_account_code_prefix, new_bank_code) old_cash_prefix = company.cash_account_code_prefix
if values.get('cash_account_code_prefix'): # Forbid currency change when entries already exist.
new_cash_code = values.get('cash_account_code_prefix') or company.cash_account_code_prefix if 'currency_id' in company_vals and company_vals['currency_id'] != company.currency_id.id:
company.reflect_code_prefix_change(company.cash_account_code_prefix, new_cash_code) if self.env['account.move.line'].sudo().search([('company_id', '=', company.id)], limit=1):
#forbid the change of currency_id if there are already some accounting entries existing
if 'currency_id' in values and values['currency_id'] != company.currency_id.id:
if self.env['account.move.line'].sudo().search([('company_id', '=', company.id)]):
raise UserError(_('You cannot change the currency of the company since some journal items already exist')) raise UserError(_('You cannot change the currency of the company since some journal items already exist'))
return super(ResCompany, self).write(values) sync_vals = {}
if 'period_lock_date' in company_vals and 'sale_lock_date' not in company_vals:
if not company.sale_lock_date or company.sale_lock_date == company.period_lock_date:
sync_vals['sale_lock_date'] = company_vals['period_lock_date']
if not company.purchase_lock_date or company.purchase_lock_date == company.period_lock_date:
sync_vals['purchase_lock_date'] = company_vals['period_lock_date']
if 'fiscalyear_lock_date' in company_vals and 'hard_lock_date' not in company_vals:
if not company.hard_lock_date or company.hard_lock_date == company.fiscalyear_lock_date:
sync_vals['hard_lock_date'] = company_vals['fiscalyear_lock_date']
if sync_vals:
company_vals.update(sync_vals)
result &= super(ResCompany, company).write(company_vals)
if 'bank_account_code_prefix' in company_vals and company.bank_account_code_prefix != old_bank_prefix:
company.reflect_code_prefix_change(old_bank_prefix, company.bank_account_code_prefix or company_vals.get('bank_account_code_prefix'))
if 'cash_account_code_prefix' in company_vals and company.cash_account_code_prefix != old_cash_prefix:
company.reflect_code_prefix_change(old_cash_prefix, company.cash_account_code_prefix or company_vals.get('cash_account_code_prefix'))
return result
@api.model @api.model
def setting_init_bank_account_action(self): def setting_init_bank_account_action(self):

View file

@ -179,6 +179,12 @@ class ResConfigSettings(models.TransientModel):
related='company_id.account_journal_early_pay_discount_gain_account_id', related='company_id.account_journal_early_pay_discount_gain_account_id',
domain="[('deprecated', '=', False), ('company_id', '=', company_id), ('account_type', 'in', ('income', 'income_other', 'expense'))]", domain="[('deprecated', '=', False), ('company_id', '=', company_id), ('account_type', 'in', ('income', 'income_other', 'expense'))]",
) )
period_lock_date = fields.Date(related='company_id.period_lock_date', readonly=False)
fiscalyear_lock_date = fields.Date(related='company_id.fiscalyear_lock_date', readonly=False)
tax_lock_date = fields.Date(related='company_id.tax_lock_date', readonly=False)
sale_lock_date = fields.Date(related='company_id.sale_lock_date', readonly=False)
purchase_lock_date = fields.Date(related='company_id.purchase_lock_date', readonly=False)
hard_lock_date = fields.Date(related='company_id.hard_lock_date', readonly=False)
def set_values(self): def set_values(self):
super().set_values() super().set_values()

View file

@ -82,6 +82,54 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-12 col-lg-6 o_setting_box" id="lock_dates_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<span class="o_form_label">Lock Dates</span>
<span class="fa fa-lg fa-building-o" title="Values set here are company-specific." aria-label="Values set here are company-specific." groups="base.group_multi_company" role="img"/>
<div class="text-muted">
Prevent posting moves before the selected cut-off dates.
</div>
<div class="content-group">
<div class="row mt16">
<label for="period_lock_date" class="col-lg-6 o_light_label">Non-Advisers Lock Date</label>
<div class="col-lg-6">
<field name="period_lock_date" widget="date"/>
</div>
</div>
<div class="row mt16">
<label for="fiscalyear_lock_date" class="col-lg-6 o_light_label">All Users Lock Date</label>
<div class="col-lg-6">
<field name="fiscalyear_lock_date" widget="date"/>
</div>
</div>
<div class="row mt16">
<label for="tax_lock_date" class="col-lg-6 o_light_label">Tax Lock Date</label>
<div class="col-lg-6">
<field name="tax_lock_date" widget="date"/>
</div>
</div>
<div class="row mt16">
<label for="sale_lock_date" class="col-lg-6 o_light_label">Sales Lock Date</label>
<div class="col-lg-6">
<field name="sale_lock_date" widget="date"/>
</div>
</div>
<div class="row mt16">
<label for="purchase_lock_date" class="col-lg-6 o_light_label">Purchases Lock Date</label>
<div class="col-lg-6">
<field name="purchase_lock_date" widget="date"/>
</div>
</div>
<div class="row mt16">
<label for="hard_lock_date" class="col-lg-6 o_light_label">Hard Lock Date</label>
<div class="col-lg-6">
<field name="hard_lock_date" widget="date"/>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box"> <div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/> <div class="o_setting_left_pane"/>
<div class="o_setting_right_pane"> <div class="o_setting_right_pane">