<commit_before>"""
The latest version of this package is available at:
<http://github.com/jantman/biweeklybudget>

################################################################################
Copyright 2016 <NAME> <<EMAIL>> <http://www.jasonantman.com>

    This file is part of biweeklybudget, also known as biweeklybudget.

    biweeklybudget 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.

    biweeklybudget 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 biweeklybudget.  If not, see <http://www.gnu.org/licenses/>.

The Copyright and Authors attributions contained herein may not be removed or
otherwise altered, except to add the Author attribution of a contributor to
this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
################################################################################
While not legally required, I sincerely request that anyone who finds
bugs please submit them at <https://github.com/jantman/biweeklybudget> or
to me via email, and that you send any contributions or improvements
either as a pull request on GitHub, or to me via email.
################################################################################

AUTHORS:
<NAME> <<EMAIL>> <http://www.jasonantman.com>
################################################################################
"""

from sqlalchemy import (
    Column, Integer, String, Boolean, Date, SmallInteger, Numeric,
    ForeignKey
)
from sqlalchemy.orm import relationship, validates
from biweeklybudget.models.base import Base, ModelAsDict


class ScheduledTransaction(Base, ModelAsDict):

    __tablename__ = 'scheduled_transactions'
    __table_args__ = (
        {'mysql_engine': 'InnoDB'}
    )

    #: Primary Key
    id = Column(Integer, primary_key=True)

    #: Amount of the transaction
    amount = Column(Numeric(precision=10, scale=4), nullable=False)

    #: description
    description = Column(String(254), nullable=False, index=True)

    #: notes
    notes = Column(String(254))

    #: ID of the account the transaction is against
    account_id = Column(Integer, ForeignKey('accounts.id'))

    #: Relationship - :py:class:`~.Account` the transaction is against
    account = relationship(
        "Account", backref="scheduled_transactions"
    )

    #: ID of the budget the transaction is against
    budget_id = Column(Integer, ForeignKey('budgets.id'))

    #: Relationship - :py:class:`~.Budget` the transaction is against
    budget = relationship(
        "Budget", backref="scheduled_transactions"
    )

    #: whether the scheduled transaction is enabled or disabled
    is_active = Column(Boolean, default=True)

    #: Denotes a scheduled transaction that will happen once on the given date
    date = Column(Date)

    #: Denotes a scheduled transaction that happens on the same day of each
    #: month
    day_of_month = Column(SmallInteger)

    #: Denotes a scheduled transaction that happens N times per pay period
    num_per_period = Column(SmallInteger)

    def __repr__(self):
        return "<ScheduledTransaction(id=%d)>" % (
            self.id
        )

    @validates('day_of_month')
    def validate_day_of_month(self, _, value):
        assert value > 0
        assert value <= 31
        return value

    @validates('num_per_period')
    def validate_num_per_period(self, _, value):
        assert value > 0
        return value<commit_msg>issue #21 - ScheduledTransaction - add better validation of day_of_month; add recurrence_str and schedule_type hybrid properties<commit_after>"""
The latest version of this package is available at:
<http://github.com/jantman/biweeklybudget>

################################################################################
Copyright 2016 <NAME> <<EMAIL>> <http://www.jasonantman.com>

    This file is part of biweeklybudget, also known as biweeklybudget.

    biweeklybudget 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.

    biweeklybudget 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 biweeklybudget.  If not, see <http://www.gnu.org/licenses/>.

The Copyright and Authors attributions contained herein may not be removed or
otherwise altered, except to add the Author attribution of a contributor to
this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
################################################################################
While not legally required, I sincerely request that anyone who finds
bugs please submit them at <https://github.com/jantman/biweeklybudget> or
to me via email, and that you send any contributions or improvements
either as a pull request on GitHub, or to me via email.
################################################################################

AUTHORS:
<NAME> <<EMAIL>> <http://www.jasonantman.com>
################################################################################
"""

from sqlalchemy import (
    Column, Integer, String, Boolean, Date, SmallInteger, Numeric,
    ForeignKey, func
)
from sqlalchemy.orm import relationship, validates
from sqlalchemy.sql import case

from biweeklybudget.models.base import Base, ModelAsDict
from biweeklybudget.utils import date_suffix
from sqlalchemy.ext.hybrid import hybrid_property


class ScheduledTransaction(Base, ModelAsDict):

    __tablename__ = 'scheduled_transactions'
    __table_args__ = (
        {'mysql_engine': 'InnoDB'}
    )

    #: Primary Key
    id = Column(Integer, primary_key=True)

    #: Amount of the transaction
    amount = Column(Numeric(precision=10, scale=4), nullable=False)

    #: description
    description = Column(String(254), nullable=False, index=True)

    #: notes
    notes = Column(String(254))

    #: ID of the account the transaction is against
    account_id = Column(Integer, ForeignKey('accounts.id'))

    #: Relationship - :py:class:`~.Account` the transaction is against
    account = relationship(
        "Account", backref="scheduled_transactions"
    )

    #: ID of the budget the transaction is against
    budget_id = Column(Integer, ForeignKey('budgets.id'))

    #: Relationship - :py:class:`~.Budget` the transaction is against
    budget = relationship(
        "Budget", backref="scheduled_transactions"
    )

    #: whether the scheduled transaction is enabled or disabled
    is_active = Column(Boolean, default=True)

    #: Denotes a scheduled transaction that will happen once on the given date
    date = Column(Date)

    #: Denotes a scheduled transaction that happens on the same day of each
    #: month
    day_of_month = Column(SmallInteger)

    #: Denotes a scheduled transaction that happens N times per pay period
    num_per_period = Column(SmallInteger)

    def __repr__(self):
        return "<ScheduledTransaction(id=%d)>" % (
            self.id
        )

    @validates('day_of_month')
    def validate_day_of_month(self, _, value):
        assert value > 0
        assert value <= 28
        return value

    @validates('num_per_period')
    def validate_num_per_period(self, _, value):
        assert value > 0
        return value

    @hybrid_property
    def recurrence_str(self):
        """
        Return a string describing the recurrence interval. This is a string
        of the format ``YYYY-mm-dd``, ``N per period`` or ``N(st|nd|rd|th)``
        where ``N`` is an integer.

        :return: string describing recurrence interval
        :rtype: str
        """
        if self.schedule_type == 'date':
            return self.date.strftime('%Y-%m-%d')
        if self.schedule_type == 'monthly':
            return '%d%s' % (
                self.day_of_month,
                date_suffix(self.day_of_month)
            )
        if self.schedule_type == 'per period':
            return '%d per period' % self.num_per_period
        return None

    @recurrence_str.expression
    def recurrence_str(cls):
        """
        SQL expression for ``recurrence_str``.
        """
        return case(
            [
                (
                    cls.date.isnot(None),
                    func.date_format(cls.date, '%Y-%m-%d')
                ),
                (
                    cls.day_of_month.isnot(None),
                    cls.day_of_month
                ),
                (
                    cls.num_per_period.isnot(None),
                    func.concat(cls.num_per_period, ' per period')
                )
            ],
            else_=''
        )

    @hybrid_property
    def schedule_type(self):
        """
        Return a string describing the type of schedule; one of ``date`` (a
        specific Date), ``per period`` (a number per pay period)`` or
        ``monthly`` (a given day of the month).

        :return: string describing type of schedule
        :rtype: str
        """
        if self.date is not None:
            return 'date'
        if self.day_of_month is not None:
            return 'monthly'
        if self.num_per_period is not None:
            return 'per period'
        return None

    @schedule_type.expression
    def schedule_type(cls):
        """
        SQL Expression for ``schedule_type``.
        """
        return case(
            [
                (cls.date.isnot(None), 'date'),
                (cls.day_of_month.isnot(None), 'monthly'),
                (cls.num_per_period.isnot(None), 'per period')
            ],
            else_=''
        )
{'fraction_non_alphanumeric': 0.14149844720496896, 'fraction_numerical': 0.004076086956521739, 'mean_word_length': 3.011288439081355, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 31, 'https://': 2, 'lorem ipsum': 0, 'www.': 6, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3129504', 'n_tokens_mistral': 2812, 'n_tokens_neox': 2626, 'n_words': 939}