Skip to content
Snippets Groups Projects
  • Houzefa Abbasbhay's avatar
    121a1cd601e8
    Migrate to Odoo 13.0 · 121a1cd601e8
    Houzefa Abbasbhay authored
    Specifics to play better with accounting changes:
    
    * No longer change the base ``date`` field; add ``accounting_date`` instead.
    * Build periods when loading accounting data for a new company.
    121a1cd601e8
    History
    Migrate to Odoo 13.0
    Houzefa Abbasbhay authored
    Specifics to play better with accounting changes:
    
    * No longer change the base ``date`` field; add ``accounting_date`` instead.
    * Build periods when loading accounting data for a new company.
account_fiscalyear.py 7.03 KiB
##############################################################################
#
#    Accounting periods, for Odoo
#    Copyright (C) 2018 XCG Consulting <http://odoo.consulting>
#
#    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 dateutil.relativedelta import relativedelta

from odoo import SUPERUSER_ID, _, api, fields, models
from odoo.exceptions import ValidationError

_logger = logging.getLogger(__name__)


class AccountFiscalyear(models.Model):
    """Account fiscal year defined an accounting year.
    """

    _name = "account.fiscalyear"
    _description = "Fiscal year"

    _order = "date_start, id"

    code = fields.Char(
        string="Code",
        help="Unique code of this fiscal year.",
        index=True,
        required=True,
    )

    company_id = fields.Many2one(
        comodel_name="res.company",
        string="Company",
        default=lambda rec: rec.env.company,
        help="The company the fiscal year is in.",
        required=True,
        index=True,
    )

    name = fields.Char(
        string="Name",
        help="Displayed name of this fiscal year.",
        required=True,
    )

    period_ids = fields.One2many(
        comodel_name="account.period",
        inverse_name="fiscalyear_id",
        string="Periods",
        help="The periods inside this fiscal year.",
    )

    date_start = fields.Date(
        compute="_compute_date_start",
        string="Start Date",
        store=True,
        # required=True,
    )

    # TODO should not be changed after creation
    date_stop = fields.Date(
        string="Stop Date", help="When this fiscal year ends", required=True
    )

    _sql_constraints = [
        (
            "unique_code_per_company",
            "UNIQUE(code, company_id)",
            "The code must be unique.",
        )
    ]

    @api.depends("date_stop")
    def _compute_date_start(self):
        for fiscalyear in self:
            if not fiscalyear.date_stop:
                continue
            fiscalyear.date_start = (
                fiscalyear.date_stop - fiscalyear._get_duration()
            )

    @api.constrains("date_stop")
    def _check_date_stop(self):
        """ Check end date value when fiscal
        year last day/month is provided.
        """
        config_fy_month = self.env.company.fiscalyear_last_month  # str
        config_fy_day = self.env.company.fiscalyear_last_day  # int
        if not config_fy_day and not config_fy_month:
            return

        ed = self.date_stop
        if str(ed.month) != config_fy_month or ed.day != config_fy_day:
            raise ValidationError(
                _(
                    "The end date must match the fiscal "
                    "year general config (%s/%s)"
                )
                % (config_fy_month, config_fy_day)
            )

    def create_period(self):
        """ Call by the "Create Periods" Button.

            This function use "date_stop" field to generate
            automatically all periods.
        """
        for fy in self:
            ed = fy.date_stop
            sd = ed - fy._get_duration()

            while sd < ed:
                period_ed = sd + relativedelta(months=1, days=-1)
                if period_ed > fy.date_stop:
                    period_ed = fy.date_stop

                fy.write(
                    {
                        "period_ids": [
                            (
                                0,
                                0,
                                {
                                    "name": sd.strftime("%m/%Y"),
                                    "code": sd.strftime("%m/%Y"),
                                    "date_start": sd,
                                    "date_stop": period_ed,
                                },
                            )
                        ]
                    }
                )
                sd = sd + relativedelta(months=1)

        return True

    def _get_duration(self):
        """
        :return: relativedelta of duration of a fiscal year
        """
        # This is a method so it can be changed in the case of needing to
        # handle shorter or longer fiscal year
        return relativedelta(months=12, days=-1)

    def action_realloc_period_am(self):
        """ Call by the "Reallocate Periods" action.

        Look up all accounts move where periods is empty and
        try to reallocate it.
        """
        for record in self:
            # Only administrator can reallocate periods.
            if not record.env.uid == SUPERUSER_ID:
                raise ValidationError(
                    _(
                        "It is not possible to run this action,"
                        "please contact your administrator."
                    )
                )

            account_moves = record.env["account.move"].search(
                [("period_id", "=", False), ("state", "=", "posted")]
            )

            for am in account_moves:
                # Cache some data.
                company = am.company_id

                # Periods are ordered by date so selecting the first one is
                # fine.
                period = record.env["account.period"].search(
                    [
                        ("company_id", "=", company.id),
                        ("date_start", "<=", am.date),
                        ("date_stop", ">=", am.date),
                    ],
                    limit=1,
                )

                if period:
                    _logger.info(
                        "reallocate period :%s: onto account move :%s:",
                        period.code,
                        am.name,
                    )

                    am.write(
                        {"period_id": period.id, "transaction_date": am.date}
                    )

    def find(self, dt=None):
        """
        :param dt: date (odoo format so string), if None, use today
        :return: fiscal years that include the indicated date, using either
        the company_id in the context or if not set, the user company_id
        """

        if not dt:
            dt = fields.Date.context_today(self)
        company_id = self.env.context.get("company_id") or self.env.company.id
        return self.search(
            [
                ("date_start", "<=", dt),
                ("date_stop", ">=", dt),
                ("company_id", "=", company_id),
            ]
        )