Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
from . import res_company
from . import account_move
from . import account_lock_exception

View File

@@ -0,0 +1,90 @@
# Copyright 2025 ForgeFlow S.L.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.osv import expression
from .res_company import SOFT_LOCK_TO_DATE_FIELDS
class AccountLockException(models.Model):
_inherit = "account.lock_exception"
# The changed lock date
lock_date_field = fields.Selection(
selection_add=[
("fiscalyear_lock_to_date", "Global Lock To Date"),
("sale_lock_to_date", "Sales Lock To Date"),
("purchase_lock_to_date", "Purchase Lock To Date"),
],
ondelete={
"fiscalyear_lock_to_date": "cascade",
"sale_lock_to_date": "cascade",
"purchase_lock_to_date": "cascade",
},
)
# (Non-stored) computed lock to date fields; c.f. res.company
fiscalyear_lock_to_date = fields.Date(
string="Global Lock To Date",
compute="_compute_lock_dates",
search="_search_fiscalyear_lock_to_date",
help="The date the Global Lock To Date is set to by this exception. "
"If the lock to date is not changed it is set to False.",
)
sale_lock_to_date = fields.Date(
string="Sales Lock To Date",
compute="_compute_lock_dates",
search="_search_sale_lock_to_date",
help="The date the Sale Lock To Date is set to by this exception. "
"If the lock to date is not changed it is set to False.",
)
purchase_lock_to_date = fields.Date(
compute="_compute_lock_dates",
search="_search_purchase_lock_to_date",
help="The date the Purchase Lock To Date is set to by this exception. "
"If the lock to date is not changed it is set to False.",
)
@api.depends("lock_date_field", "lock_date")
def _compute_lock_dates(self):
res = super()._compute_lock_dates()
for exception in self:
for field in SOFT_LOCK_TO_DATE_FIELDS:
if field == exception.lock_date_field:
exception[field] = exception.lock_date
else:
exception[field] = False
return res
def _search_lock_to_date(self, field, operator, value):
if operator not in [">", ">="] or not value:
raise UserError(_("Operation not supported"))
return [
"&",
("lock_date_field", "=", field),
"|",
("lock_date", "=", False),
("lock_date", operator, value),
]
def _search_fiscalyear_lock_to_date(self, operator, value):
return self._search_lock_to_date("fiscalyear_lock_to_date", operator, value)
def _search_sale_lock_to_date(self, operator, value):
return self._search_lock_to_date("sale_lock_to_date", operator, value)
def _search_purchase_lock_to_date(self, operator, value):
return self._search_lock_to_date("purchase_lock_to_date", operator, value)
@api.model
def _get_active_exceptions_to_domain(self, company, soft_lock_to_date_fields):
return [
*expression.OR(
[(field, ">", company[field])]
for field in soft_lock_to_date_fields
if company[field]
),
("company_id", "=", company.id),
("state", "=", "active"), # checks the datetime
]

View File

@@ -0,0 +1,46 @@
# Copyright 2019 ForgeFlow S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, models
from odoo.exceptions import ValidationError
from odoo.addons.account.models.account_move import BYPASS_LOCK_CHECK
class AccountMove(models.Model):
_inherit = "account.move"
def _check_lock_to_dates(self):
if self.env.context.get("bypass_lock_check") is BYPASS_LOCK_CHECK:
return
for move in self:
journal = move.journal_id
violated_lock_to_dates = move.company_id._get_lock_to_date_violations(
move.date,
fiscalyear=True,
sale=journal and journal.type == "sale",
purchase=journal and journal.type == "purchase",
hard=True,
)
if violated_lock_to_dates:
message = _(
"You cannot add/modify entries posterior to "
"and inclusive of: %(lock_date_info)s.",
lock_date_info=self.env["res.company"]._format_lock_dates(
violated_lock_to_dates
),
)
raise ValidationError(message)
return True
def action_post(self):
self._check_lock_to_dates()
return super().action_post()
def button_cancel(self):
self._check_lock_to_dates()
return super().button_cancel()
def button_draft(self):
self._check_lock_to_dates()
return super().button_draft()

View File

@@ -0,0 +1,264 @@
# Copyright 2019 ForgeFlow S.L.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
SOFT_LOCK_TO_DATE_FIELDS = [
"fiscalyear_lock_to_date",
"sale_lock_to_date",
"purchase_lock_to_date",
]
LOCK_TO_DATE_FIELDS = [
*SOFT_LOCK_TO_DATE_FIELDS,
"hard_lock_to_date",
]
class ResCompany(models.Model):
_inherit = "res.company"
sale_lock_to_date = fields.Date(
string="Sales Lock To Date",
tracking=True,
help="Prevents creation and modification of entries in sales journals"
" posterior to the defined date inclusive.",
)
purchase_lock_to_date = fields.Date(
tracking=True,
help="Prevents creation and modification of entries in purchase journals"
" posterior to the defined date inclusive.",
)
fiscalyear_lock_to_date = fields.Date(
string="Global Lock To Date",
tracking=True,
help="No users can edit accounts posterior to this date."
" Use it for fiscal year locking for example.",
)
hard_lock_to_date = fields.Date(
tracking=True,
help='Like the "Global Lock Date", but no exceptions are possible.',
)
user_fiscalyear_lock_to_date = fields.Date(
compute="_compute_user_fiscalyear_lock_to_date"
)
user_sale_lock_to_date = fields.Date(compute="_compute_user_sale_lock_to_date")
user_purchase_lock_to_date = fields.Date(
compute="_compute_user_purchase_lock_to_date"
)
user_hard_lock_to_date = fields.Date(compute="_compute_user_hard_lock_to_date")
@api.depends("fiscalyear_lock_to_date")
@api.depends_context("uid", "ignore_exceptions")
def _compute_user_fiscalyear_lock_to_date(self):
ignore_exceptions = bool(self.env.context.get("ignore_exceptions", False))
for company in self:
company.user_fiscalyear_lock_to_date = company._get_user_lock_to_date(
"fiscalyear_lock_to_date", ignore_exceptions
)
@api.depends("sale_lock_to_date")
@api.depends_context("uid", "ignore_exceptions")
def _compute_user_sale_lock_to_date(self):
ignore_exceptions = bool(self.env.context.get("ignore_exceptions", False))
for company in self:
company.user_sale_lock_to_date = company._get_user_lock_to_date(
"sale_lock_to_date", ignore_exceptions
)
@api.depends("purchase_lock_to_date")
@api.depends_context("uid", "ignore_exceptions")
def _compute_user_purchase_lock_to_date(self):
ignore_exceptions = bool(self.env.context.get("ignore_exceptions", False))
for company in self:
company.user_purchase_lock_to_date = company._get_user_lock_to_date(
"purchase_lock_to_date", ignore_exceptions
)
@api.depends("hard_lock_to_date")
def _compute_user_hard_lock_to_date(self):
for company in self:
company.user_hard_lock_to_date = (
min(
c.hard_lock_to_date
for c in company.with_context(active_test=False).sudo().parent_ids
if c.hard_lock_to_date
)
if any(
c.hard_lock_to_date
for c in company.with_context(active_test=False).sudo().parent_ids
)
else False
)
def _validate_locks(self, values):
res = super()._validate_locks(values)
if "hard_lock_to_date" in values:
hard_lock_to_date = fields.Date.to_date(values["hard_lock_to_date"])
for company in self:
if not company.hard_lock_to_date:
continue
if not hard_lock_to_date:
raise ValidationError(_("The Hard Lock Date cannot be removed."))
if hard_lock_to_date > company.hard_lock_to_date:
raise ValidationError(
_(
"A new Hard Lock To Date must be prior "
"(or equal) to the previous one."
)
)
nb_draft_entries = self.env["account.move"].search(
[
("company_id", "child_of", self.ids),
("state", "=", "draft"),
("date", ">=", hard_lock_to_date),
],
limit=1,
)
if nb_draft_entries:
raise ValidationError(
_(
"There are still unposted entries in the period to date"
" you want to hard lock. "
"You should either post or delete them."
)
)
self.env["res.company"].invalidate_model(
fnames=[f"user_{field}" for field in LOCK_TO_DATE_FIELDS if field in values]
)
return res
def _get_user_lock_to_date(self, soft_lock_to_date_field, ignore_exceptions=False):
self.ensure_one()
soft_lock_to_date = False
# We need to use sudo, since we might not have access to a parent company.
for company in self.sudo().parent_ids:
if company[soft_lock_to_date_field]:
if ignore_exceptions:
exception = None
else:
exception = self.env["account.lock_exception"].search(
[
("state", "=", "active"), # checks the datetime
"|",
("user_id", "=", None),
("user_id", "=", self.env.user.id),
(
soft_lock_to_date_field,
">",
company[soft_lock_to_date_field],
),
("company_id", "=", company.id),
],
order="lock_date asc NULLS FIRST",
limit=1,
)
if exception:
# The search domain of the exception ensures
# `exception[
# soft_lock_to_date_field] > company[
# soft_lock_to_date_field]`
# or `exception[soft_lock_to_date_field] is False`
soft_lock_to_date = (
min(soft_lock_to_date, exception[soft_lock_to_date_field])
if soft_lock_to_date and exception[soft_lock_to_date_field]
else soft_lock_to_date
or exception[soft_lock_to_date_field]
or False
)
else:
soft_lock_to_date = (
min(soft_lock_to_date, company[soft_lock_to_date_field])
if soft_lock_to_date and company[soft_lock_to_date_field]
else soft_lock_to_date
or company[soft_lock_to_date_field]
or False
)
return soft_lock_to_date
def _get_user_fiscal_lock_to_date(self, journal, ignore_exceptions=False):
self.ensure_one()
company = self.with_context(ignore_exceptions=ignore_exceptions)
lock = (
min(company.user_fiscalyear_lock_to_date, company.user_hard_lock_to_date)
if company.user_fiscalyear_lock_to_date and company.user_hard_lock_to_date
else company.user_fiscalyear_lock_to_date
or company.user_hard_lock_to_date
or False
)
if journal.type == "sale":
lock = (
min(company.user_sale_lock_to_date, lock)
if company.user_sale_lock_to_date and lock
else company.user_sale_lock_to_date or lock or False
)
elif journal.type == "purchase":
lock = (
min(company.user_purchase_lock_to_date, lock)
if company.user_purchase_lock_to_date and lock
else company.user_purchase_lock_to_date or lock or False
)
return lock
def _get_violated_soft_lock_to_date(self, soft_lock_to_date_field, date):
violated_date = None
if not self:
return violated_date
self.ensure_one()
user_lock_to_date_field = f"user_{soft_lock_to_date_field}"
regular_lock_to_date = self.with_context(ignore_exceptions=True)[
user_lock_to_date_field
]
if regular_lock_to_date and date >= regular_lock_to_date:
user_lock_to_date = self.with_context(ignore_exceptions=False)[
user_lock_to_date_field
]
violated_date = (
None
if regular_lock_to_date and date < user_lock_to_date
else user_lock_to_date
)
return violated_date
def _get_lock_to_date_violations(
self, accounting_date, fiscalyear=True, sale=True, purchase=True, hard=True
):
self.ensure_one()
locks = []
if not accounting_date:
return locks
soft_lock_to_date_fields_to_check = [
# (field, "to check")
("fiscalyear_lock_to_date", fiscalyear),
("sale_lock_to_date", sale),
("purchase_lock_to_date", purchase),
]
for field, to_check in soft_lock_to_date_fields_to_check:
if not to_check:
continue
violated_date = self._get_violated_soft_lock_to_date(field, accounting_date)
if violated_date:
locks.append((violated_date, field))
if hard:
hard_lock_date = self.user_hard_lock_to_date
if hard_lock_date and accounting_date >= hard_lock_date:
locks.append((hard_lock_date, "hard_lock_date"))
return locks
def write(self, values):
companies = super().write(values)
# We revoke all active exceptions affecting the changed lock to dates
# and recreate them (with the updated lock to dates)
changed_soft_lock_fields = [
field for field in SOFT_LOCK_TO_DATE_FIELDS if field in values
]
for company in self:
active_exceptions = self.env["account.lock_exception"].search(
self.env["account.lock_exception"]._get_active_exceptions_to_domain(
company, changed_soft_lock_fields
),
)
active_exceptions._recreate()
return companies