Newer
Older
##############################################################################
#
# Accounting periods, for Odoo
# Copyright (C) 2018, 2022 XCG Consulting <https://xcg-consulting.fr/>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import logging
from math import log10
from dateutil.relativedelta import relativedelta
from odoo import _, api, exceptions, fields, models, tools
_logger = logging.getLogger(__name__)
class AccountMove(models.Model):
"""Add a period & dates onto accounting documents."""
accounting_date = fields.Date(
string="Accounting date",
copy=False,
help="Validation date of the accounting document.",
states={"posted": [("readonly", True)]},
comodel_name="account.period",
string="Period",
help="The period this accounting document is in.",
states={"posted": [("readonly", True), ("required", True)]},
)
transaction_date = fields.Date(
"Invoicing date when provided; otherwise this is the accounting " "date."
states={"posted": [("readonly", True)]},
@api.model_create_multi
def create(self, vals_list):
"""Override to set a transaction date from invoices."""
for vals in vals_list:
invoice_date = vals.get("invoice_date")
if invoice_date:
vals["transaction_date"] = invoice_date
"""Override accounting document validation to fill accounting dates &
period.
"""
self.fill_accounting_dates()
def fill_accounting_dates(self):
- Also set the transaction date ("transaction_date" field) when empty.
- Force the period to always be around the current date.
- Only select open periods.
today = fields.Date.today()
acc_date = accdoc.accounting_date or today
# Set the acc_date only if the force_period_on_date
# context has provided.
if self.env.context.get("force_period_on_date"):
# If accounting document date is empty, get today date.
# Periods are ordered by date so selecting the first one is fine.
period = self.env["account.period"].search(
[
("company_id", "=", company.id),
("date_start", "<=", acc_date),
("date_effective_cutoff", ">=", acc_date),
("state", "!=", "done"),
],
limit=1,
)
if not period:
raise exceptions.UserError(
_("No period found around %(date)s in the company %(name)s.")
% {"date": acc_date, "name": company.sudo().name}
# When we are between the period end and cut-off date, force the
# last day of the period.
in_cutoff = False
if accdoc.journal_id.type == "sale":
in_cutoff = acc_date > period_end
acc_date = period_end if in_cutoff else acc_date
# The data to update the accounting document with.
accdoc_values = {
"accounting_date": acc_date,
"period_id": period.id,
}
# Set a transaction date when no previous one set. Also, force it
# during cut-off.
if in_cutoff or not accdoc.transaction_date:
accdoc_values["transaction_date"] = acc_date
# Ready! Update the accounting document.
accdoc.write(accdoc_values)
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
def _populate_factories(self) -> list:
"""Add periods to the generated account.move"""
today = fields.Date.today()
def get_accounting_date(values, counter, random):
"""return an accounting date"""
accounting_date = (
values["date"].date() if values["date"].date() < today else None
)
# make sure target period exists (or action_post will throw an exception)
if accounting_date:
# make it random between the date and today
seconds_after = (today - accounting_date).total_seconds()
accounting_date = accounting_date + relativedelta(
seconds=seconds_after * -log10(0.001 + 0.999 * random.random()) / 3
)
# make sure that period exists
self.env["account.period"].with_context(
company_id=values["company_id"]
).find(accounting_date.date(), True)
return accounting_date
result = super()._populate_factories()
result.append(
# set some accounting_date so that when posting everything does not end up
# on the current period
("accounting_date", tools.populate.compute(get_accounting_date))
)
return result