Commit 5dcbaa61 authored by Luca Cristaldi's avatar Luca Cristaldi
Browse files

Add honorary memebrship fee

parent e0c5c8a4
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -22,3 +22,11 @@ class SequenceMissingError(ValidationError):

class MemberUnpaidFeeError(ValidationError):
    pass


class AmountLessThanZeroError(ValidationError):
    pass


class PartyAccountPayableRequiredError(ValidationError):
    pass
+35 −9
Original line number Diff line number Diff line
# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

from decimal import Decimal
from trytond.config import config
from trytond.i18n import gettext
@@ -11,7 +10,8 @@ from trytond.model.exceptions import AccessError
from trytond.transaction import Transaction
from trytond.wizard import StateTransition, Wizard, StateView, Button

from .exceptions import PeriodDateOverlapError
from .exceptions import (PeriodDateOverlapError, AmountLessThanZeroError,
                         PartyAccountPayableRequiredError)

__all__ = [
    'Membership', 'Period', 'Fee', 'Line', 'PostFee', 'GenerateFee',
@@ -25,7 +25,10 @@ _STATES = {'readonly': Eval('state') != 'open'}

_LINE_STATES = {'readonly': Eval('member_state', '') != 'draft'}

_FEE_STATES = {'readonly': Bool(Eval('move'))}
_FEE_STATES = {
    'readonly':
    Bool(Eval('move')) and Eval('fee', Decimal('0.0')) == Decimal('0.0')
}

_MEMBER_STATES = [
    ('draft', 'Draft'),
@@ -167,6 +170,13 @@ class Period(ModelSQL, ModelView):
        super(Period, cls).validate(periods)
        for period in periods:
            period.check_dates()
            period.check_fee()

    def check_fee(self):
        if self.amount < Decimal('0.0'):
            raise AmountLessThanZeroError(
                gettext('association.msg_period_fee_invalid',
                period=self.rec_name))

    def check_dates(self):
        transaction = Transaction()
@@ -221,6 +231,13 @@ class Fee(ModelSQL, ModelView):
        "Move State"), 'on_change_with_move_state')
    paid = fields.Function(fields.Boolean('paid'), 'is_paid')
    reconciled = fields.Function(fields.Date('Reconciled'), 'get_reconciled')
    amount = fields.Function(fields.Numeric("Amount", fee_digit),
        'on_change_with_amount')

    @fields.depends('period', '_parent_period.amount')
    def on_change_with_amount(self, name=None):
        if self.period:
            return self.period.amount

    @classmethod
    def __setup__(cls):
@@ -234,8 +251,10 @@ class Fee(ModelSQL, ModelView):

        cls._buttons.update({
            'post_move': {
                'invisible': Eval('move_state') == 'posted',
                'depends': ['move_state'],
                'invisible':
                    (Eval('move_state') == 'posted')
                    | (Eval('amount', Decimal('0.0')) == Decimal('0.0')),
                'depends': ['move_state', 'amount'],
            },
        })

@@ -247,7 +266,7 @@ class Fee(ModelSQL, ModelView):
    @classmethod
    def delete(cls, fees):
        for fee in fees:
            if fee.move:
            if fee.move or fee.paid:
                raise AccessError(
                    gettext('association.msg_fee_delete', fee=fee.rec_name))
        super(Fee, cls).delete(fees)
@@ -281,13 +300,19 @@ class Fee(ModelSQL, ModelView):
            return max(r.date for r in reconciliations)

    def is_paid(self, name):
        return self.reconciled is not None
        return (self.reconciled is not None) or (
            self.move is None and self.period.amount == Decimal('0.0'))

    def get_move_line(self, amount, amount_second_currency):
        'Return counterpart Move Line for the amount'
        pool = Pool()
        MoveLine = pool.get('account.move.line')

        if not self.member.party.account_receivable:
            raise PartyAccountPayableRequiredError(
                gettext('association.msg_party_receivable_missing',
                party=self.member.party.rec_name))

        account = None
        if amount > 0:
            account = self.member.party.account_receivable
@@ -334,12 +359,13 @@ class Fee(ModelSQL, ModelView):
        pool = Pool()
        Date = pool.get('ir.date')
        Move = pool.get("account.move")
        to_post = []
        for fee in fees:
            if not fee.move:
            if not fee.move and fee.period.amount > Decimal('0.0'):
                fee.move = fee.create_move(Date.today())
                fee.save()
                to_post.append(fee.move)

        to_post = [x.move for x in fees]
        Move.post(to_post)

    @classmethod
+6 −0
Original line number Diff line number Diff line
@@ -15,5 +15,11 @@ this repository contains the full copyright notices and license terms. -->
        <record model="ir.message" id="msg_unpaid_fee">
            <field name="text">You cannot expel the member "%(member)s" because there are still some unpaid fees</field>
        </record>
        <record model="ir.message" id="msg_period_fee_invalid">
            <field name="text">The period "%(period)s" fee amount should have a value >= zero</field>
        </record>
        <record model="ir.message" id="msg_party_receivable_missing">
            <field name="text">The party "%(party)s" doesn't have an account receivable defined</field>
        </record>
    </data>
</tryton>
 No newline at end of file
+111 −0
Original line number Diff line number Diff line
===============
Member Scenario
===============

Imports::

    >>> import datetime
    >>> from dateutil.relativedelta import relativedelta
    >>> from decimal import Decimal
    >>> from operator import attrgetter
    >>> from proteus import Model, Wizard
    >>> from trytond.tests.tools import activate_modules
    >>> from trytond.modules.company.tests.tools import create_company, \
    ...     get_company
    >>> from trytond.modules.account.tests.tools import create_fiscalyear, \
    ...     create_chart, get_accounts, create_tax, create_tax_code
    >>> from trytond.modules.association.tests.tools import create_period
    >>> from decimal import *
    >>> today = datetime.date.today()

Install association::

    >>> config = activate_modules('association')

Create company::

    >>> _ = create_company()
    >>> company = get_company()

Create fiscal year::

    >>> fiscalyear = create_fiscalyear(company)
    >>> fiscalyear.click('create_period')
    >>> period = fiscalyear.periods[0]

Create chart of accounts::

    >>> _ = create_chart(company)
    >>> accounts = get_accounts(company)
    >>> receivable = accounts['receivable']
    >>> payable = accounts['payable']
    >>> revenue = accounts['revenue']
    >>> expense = accounts['expense']
    >>> account_tax = accounts['tax']
    >>> account_cash = accounts['cash']

Create party::

    >>> Party = Model.get('party.party')
    >>> party = Party(name='Party')
    >>> party.account_payable = payable
    >>> party.account_receivable = receivable
    >>> party.save()

Create member::

   >>> Member = Model.get('association.member')
   >>> member = Member()
   >>> member.party = party
   >>> member.save()

Get a revenue journal::

   >>> Journal = Model.get('account.journal')
   >>> journal_revenue, = Journal.find([
   ...         ('code', '=', 'REV'),
   ...         ])

Create honorary membership::

   >>> Membership = Model.get('association.membership')
   >>> Period = Model.get('association.membership.period')
   >>> honorary = Membership()
   >>> honorary.party = party
   >>> honorary.name = "testo"
   >>> honorary.account_revenue = revenue
   >>> honorary.journal = journal_revenue
   >>> start_date = datetime.date(2018,1,1)
   >>> timedelta = datetime.timedelta(weeks=4)
   >>> fiscalMonth = 13
   >>> datedeltas = [(start_date + timedelta*(n-1) + datetime.timedelta(days=1),start_date + timedelta*n,f"period {n}") for n in range(1,fiscalMonth+1)]
   >>> _ = [ honorary.periods.new(start_date=period[0],end_date=period[1], name=period[2], amount=Decimal('0.0')) for period in datedeltas ]
   >>> honorary.save()


Add membership to member::

   >>> _ = member.memberships.new(membership=honorary)
   >>> member.save()

Enroll member::

   >>> member.join_date = start_date
   >>> member.click("run")
   >>> member.save()

Create fee lines::

   >>> create_fee = Wizard('association.membership.fee_create')
   >>> create_fee.form.date = start_date + datetime.timedelta(weeks=40)
   >>> create_fee.execute('create_')

Check fees::

   >>> Fee = Model.get('association.membership.fee')
   >>> honorary_fees = Fee.find([('period.amount','=', Decimal('0.0'))])
   >>> all([x.paid for x in honorary_fees]) and len(honorary_fees) > 0
   True


+9 −4
Original line number Diff line number Diff line
@@ -79,12 +79,17 @@ Create membership::
   >>> datedeltas = [(start_date + timedelta*(n-1) + datetime.timedelta(days=1),start_date + timedelta*n,f"period {n}") for n in range(1,fiscalMonth+1)]
   >>> periods = [ membership.periods.new(start_date=period[0],end_date=period[1], name=period[2], amount=Decimal(42)) for period in datedeltas ]
   >>> membership.save()
   >>> overlapping =membership.periods.new(start_date=start_date,end_date=start_date+timedelta,name="overlapping",amount=Decimal(42)).save()
   >>> overlapping =membership.periods.new(start_date=start_date,end_date=start_date+timedelta,name="overlapping",amount=Decimal(42)).save() #doctest: +ELLIPSIS
   Traceback (most recent call last):
      ...
   trytond.modules.association.exceptions.PeriodDateOverlapError: ('UserError', ('The period "overlapping" overlaps with the period "period 1"', ''))
   trytond.modules.association.exceptions.PeriodDateOverlapError: ('UserError', ...)
   >>> new_date = datetime.date(2020,1,1)
   >>> inverted = membership.periods.new(start_date=new_date + timedelta,end_date=start_date,name="inverted",amount=Decimal(42)).save()
   >>> inverted = membership.periods.new(start_date=new_date + timedelta,end_date=start_date,name="inverted",amount=Decimal(42)).save() #doctest: +ELLIPSIS
   Traceback (most recent call last):
      ...
   trytond.model.modelstorage.DomainValidationError: ('UserError', ('The value for field "Ending Date" in "Membership period" is not valid according to its domain.', ''))
   trytond.model.modelstorage.DomainValidationError: ('UserError', ...)
   >>> negative = membership.periods.new(start_date=new_date,end_date=new_date + timedelta,name="negative 1",amount=Decimal(-10)).save() #doctest: +ELLIPSIS
   Traceback (most recent call last):
      ...
   trytond.modules.association.exceptions.AmountLessThanZeroError: ('UserError', ...)
Loading