Loading COPYRIGHT +1 −1 Original line number Diff line number Diff line Copyright (C) 2018 Luca Cristaldi Copyright (C) 2019 Luca Cristaldi This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by Loading account.py +2 −2 Original line number Diff line number Diff line Loading @@ -12,4 +12,4 @@ class Move(metaclass=PoolMeta): @classmethod def _get_origin(cls): return super(Move, cls)._get_origin() + ['association.membership.fee'] return super()._get_origin() + ['association.membership.fee'] configuration.py +3 −3 Original line number Diff line number Diff line Loading @@ -8,8 +8,8 @@ from trytond.modules.company.model import (CompanyMultiValueMixin, __all__ = ['Configuration', 'MemberSequence'] class Configuration(ModelSingleton, ModelSQL, ModelView, CompanyMultiValueMixin): class Configuration( ModelSingleton, ModelSQL, ModelView, CompanyMultiValueMixin): """Association Configuration""" __name__ = 'association.configuration' member_sequence = fields.MultiValue( Loading doc/index.rst +43 −27 Original line number Diff line number Diff line =========== Association =========== ------ Member ------ A member is defined by: * code: a sequential code, you can specify the format in the Configuration tab * Party: The party associated with this member * join_date: The date were the member joins the association * leave_date: The date were the member leaves the association * memberships: A list of Mebership Line * memberships: A list of Membership Line If you want easilly check if the member paied all his fees, you can use the "Fees" relate utility. If you want easily check if the member paid all his fees, you can use the "Fees" relate utility. --------------- Membership Line --------------- The membership line is used to specify the starting and ending date of the membership. This so you can selecet a starting/ending date different from the member one. The membership line is used to specify the starting and ending date of the membership. This so you can select a starting/ending date different from the member one. If you leave the fields empty, it will use the join/leave date from the member. A membership line is defined by: * membership: the membership the member enrolled * start_date: the strating date, if not specified use the join date from the member will be used * end_date: the ending date, if not specified the leave date from the member will be used This is used by the "Create Fees" Wizard to filter wich period the user should be billed * membership: the membership the member enrolled * start_date: the starting date, if not specified the join date from the member will be used * end_date: the ending date, if not specified the leave date from the member will be used This is used by the "Create Fees" Wizard to filter witch period the user should be billed ---------- Membership ---------- A membership is a collection of Periods. The list must contain Periods with no overlapping date. A Membership is defined by: * name: The name of the Membership * journal: The Journal that will be used for posting accounting move * account_revenue: The account revenue that will be used for posting accounting move * account_revenue: The account revenue that will be used for posting accounting move * periods: A list of Periods ******************* Print Member's Book ******************* This wizard will print a Report of all the member that are not in the draft state. =================== This wizard will print a Report of all the member that are not in the draft state. ------ Period ------ A periods define a date range and what the member should pay for the period A period is defined by: * start_date: the starting date of the period * end_date: the ending date of the period * fee: the amount that the member should pay for this period Loading @@ -56,19 +69,22 @@ A period is defined by: Fee --- A fee is associated to a Member and a Period. Tryton will check if a Member have Fees with the same period,and will throw an error if you try to do so. A feee is defined as: Tryton will check if a Member have Fees with the same period, and will throw an error if you try to do so. A fee is defined as: * member: The Member associated with this fee * period: The Period associated with this fee * move: The move associated with this fee The module will check if the move is reconciled and posted, if so the paid field will be checked. The fee will be considered as paid when the move is reconciled and posted. You can post the move via the Post button/action, or manually. ****************** Create Fees Wizard ****************** ================== The wizard will generate fee record based on the date. This wizard is used by the cronjob that will trigger (by default) once every day. This wizard is used by the cronjob that will trigger (by default) once every day. member.py +93 −74 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 trytond.model import Workflow, ModelView, ModelSQL, fields, Unique from trytond.pyson import Eval, If, Not, Or from trytond.pyson import Eval, If from trytond.tools import lstrip_wildcard from trytond.transaction import Transaction from trytond.pool import Pool from trytond.i18n import gettext Loading @@ -14,82 +15,89 @@ __all__ = [ 'Member', 'MembersBookWizard', 'PrintMembersBookStart', 'MembersBookReport' ] STATES = [ ('draft', 'Draft'), ('running', 'Running'), ('stopped', 'Stopped'), ] _DEPENDS = ['state'] _STATE = {'readonly': Eval('state') != 'draft'} class Member(Workflow, ModelSQL, ModelView): 'Member' __name__ = "association.member" state = fields.Selection(STATES, 'State', readonly=True) code = fields.Char('Code', select=True, _states = {'readonly': Eval('state') != 'draft'} _depends = ['state'] state = fields.Selection([ ('draft', 'Draft'), ('running', 'Running'), ('stopped', 'Stopped'), ], "State", readonly=True) code = fields.Char("Code", select=True, states={ 'readonly': Eval('code_readonly', True), 'required': Eval('state') != 'draft'}, depends=['code_readonly', 'state'], 'required': Eval('state') != 'draft' }, depends=_depends + ['code_readonly'], help="The internal identifier for the associate.") code_readonly = fields.Function(fields.Boolean('Code Readonly'), code_readonly = fields.Function(fields.Boolean("Code Readonly"), 'get_code_readonly') company = fields.Many2One('company.company', 'Company', states=_STATE, depends=_DEPENDS, company = fields.Many2One('company.company', "Company", states={'readonly': Eval('state') != 'draft'}, depends=_depends, required=True, help="The association this member belongs to.") party = fields.Many2One('party.party', 'Party', states=_STATE, depends=_DEPENDS, party = fields.Many2One('party.party', "Party", states={'readonly': Eval('state') != 'draft'}, depends=_depends, required=True, help="The party that represents the member.") join_date = fields.Date('Join Date', join_date = fields.Date("Join Date", states={ 'readonly': Or( Not(Eval('state').in_(['draft'])), Eval('join_date', False) ), 'required': Eval('state') != 'draft'}, (Eval('state') != 'draft') | Eval('join_date'), 'required': Eval('state') != 'draft' }, domain=[ If( (Eval('join_date')) & (Eval('leave_date')), ('join_date', '<=', Eval('leave_date')), (), )], depends=['leave_date', 'state'], ) ], depends=_depends + ['leave_date'], help="The date the member joined the association.") leave_date = fields.Date('Leave Date', leave_date = fields.Date("Leave Date", domain=[ If( (Eval('join_date')) & (Eval('leave_date')), (Eval('join_date')) & (Eval('leave_date')), ('leave_date', '>=', Eval('join_date')), (), )], depends=['join_date', 'state'], ) ], depends=_depends + ['join_date'], states={ 'invisible': Or( Not(Eval('state').in_(['running', 'stopped'])), Eval('leave_date', False) ), 'invisible': Eval('state') == 'draft', 'required': Eval('state') == 'stopped', 'readonly': Eval('state') != 'running' }, help="The date the member left the association.") memberships = fields.One2Many('association.membership', 'member', memberships = fields.One2Many('association.membership', 'member', "Memberships", domain=[ ('company', '=', Eval('context', {}).get('company', -1)) ('company', '=', Eval('company', -1)) ], depends=['state', 'company'], states={'readonly': Eval('state') != 'draft'}) depends=_depends + ['company'], states=_states) del _states, _depends @classmethod def __setup__(cls): super(Member, cls).__setup__() super().__setup__() t = cls.__table__() cls._sql_constraints = [ ('code_uniq', Unique(t, t.code), 'Member code must be unique.'), Loading Loading @@ -130,7 +138,18 @@ class Member(Workflow, ModelSQL, ModelView): @classmethod def search_rec_name(cls, name, clause): return [('party', ) + tuple(clause[1:])] if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' code_value = clause[2] if clause[1].endswith('like'): code_value = lstrip_wildcard(clause[2]) return [ bool_op, ('code', clause[1], code_value) + tuple(clause[3:]), ('party', ) + tuple(clause[1:]), ] @classmethod def copy(cls, members, default=None): Loading @@ -139,7 +158,8 @@ class Member(Workflow, ModelSQL, ModelView): else: default = default.copy() default.setdefault('code', None) return super(Member, cls).copy(members, default=default) default.setdefault('leave_date', None) return super().copy(members, default=default) @classmethod def default_state(cls): Loading Loading @@ -181,7 +201,6 @@ class Member(Workflow, ModelSQL, ModelView): raise MemberUnpaidFeeError( gettext('association.msg_unpaid_fee', member=", ".join([x.rec_name for x in members]))) cls.save(members) class MembersBookReport(Report): Loading @@ -194,7 +213,7 @@ class MembersBookReport(Report): Member = pool.get('association.member') Company = pool.get('company.company') clause = [('state', '!=', 'draft'), ('company', '=', data['company'])] context = super(MembersBookReport, cls).get_context(records, data) context = super().get_context(records, data) members = Member.search(clause) context["members"] = members context["company"] = Company(data['company']) Loading Loading
COPYRIGHT +1 −1 Original line number Diff line number Diff line Copyright (C) 2018 Luca Cristaldi Copyright (C) 2019 Luca Cristaldi This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by Loading
account.py +2 −2 Original line number Diff line number Diff line Loading @@ -12,4 +12,4 @@ class Move(metaclass=PoolMeta): @classmethod def _get_origin(cls): return super(Move, cls)._get_origin() + ['association.membership.fee'] return super()._get_origin() + ['association.membership.fee']
configuration.py +3 −3 Original line number Diff line number Diff line Loading @@ -8,8 +8,8 @@ from trytond.modules.company.model import (CompanyMultiValueMixin, __all__ = ['Configuration', 'MemberSequence'] class Configuration(ModelSingleton, ModelSQL, ModelView, CompanyMultiValueMixin): class Configuration( ModelSingleton, ModelSQL, ModelView, CompanyMultiValueMixin): """Association Configuration""" __name__ = 'association.configuration' member_sequence = fields.MultiValue( Loading
doc/index.rst +43 −27 Original line number Diff line number Diff line =========== Association =========== ------ Member ------ A member is defined by: * code: a sequential code, you can specify the format in the Configuration tab * Party: The party associated with this member * join_date: The date were the member joins the association * leave_date: The date were the member leaves the association * memberships: A list of Mebership Line * memberships: A list of Membership Line If you want easilly check if the member paied all his fees, you can use the "Fees" relate utility. If you want easily check if the member paid all his fees, you can use the "Fees" relate utility. --------------- Membership Line --------------- The membership line is used to specify the starting and ending date of the membership. This so you can selecet a starting/ending date different from the member one. The membership line is used to specify the starting and ending date of the membership. This so you can select a starting/ending date different from the member one. If you leave the fields empty, it will use the join/leave date from the member. A membership line is defined by: * membership: the membership the member enrolled * start_date: the strating date, if not specified use the join date from the member will be used * end_date: the ending date, if not specified the leave date from the member will be used This is used by the "Create Fees" Wizard to filter wich period the user should be billed * membership: the membership the member enrolled * start_date: the starting date, if not specified the join date from the member will be used * end_date: the ending date, if not specified the leave date from the member will be used This is used by the "Create Fees" Wizard to filter witch period the user should be billed ---------- Membership ---------- A membership is a collection of Periods. The list must contain Periods with no overlapping date. A Membership is defined by: * name: The name of the Membership * journal: The Journal that will be used for posting accounting move * account_revenue: The account revenue that will be used for posting accounting move * account_revenue: The account revenue that will be used for posting accounting move * periods: A list of Periods ******************* Print Member's Book ******************* This wizard will print a Report of all the member that are not in the draft state. =================== This wizard will print a Report of all the member that are not in the draft state. ------ Period ------ A periods define a date range and what the member should pay for the period A period is defined by: * start_date: the starting date of the period * end_date: the ending date of the period * fee: the amount that the member should pay for this period Loading @@ -56,19 +69,22 @@ A period is defined by: Fee --- A fee is associated to a Member and a Period. Tryton will check if a Member have Fees with the same period,and will throw an error if you try to do so. A feee is defined as: Tryton will check if a Member have Fees with the same period, and will throw an error if you try to do so. A fee is defined as: * member: The Member associated with this fee * period: The Period associated with this fee * move: The move associated with this fee The module will check if the move is reconciled and posted, if so the paid field will be checked. The fee will be considered as paid when the move is reconciled and posted. You can post the move via the Post button/action, or manually. ****************** Create Fees Wizard ****************** ================== The wizard will generate fee record based on the date. This wizard is used by the cronjob that will trigger (by default) once every day. This wizard is used by the cronjob that will trigger (by default) once every day.
member.py +93 −74 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 trytond.model import Workflow, ModelView, ModelSQL, fields, Unique from trytond.pyson import Eval, If, Not, Or from trytond.pyson import Eval, If from trytond.tools import lstrip_wildcard from trytond.transaction import Transaction from trytond.pool import Pool from trytond.i18n import gettext Loading @@ -14,82 +15,89 @@ __all__ = [ 'Member', 'MembersBookWizard', 'PrintMembersBookStart', 'MembersBookReport' ] STATES = [ ('draft', 'Draft'), ('running', 'Running'), ('stopped', 'Stopped'), ] _DEPENDS = ['state'] _STATE = {'readonly': Eval('state') != 'draft'} class Member(Workflow, ModelSQL, ModelView): 'Member' __name__ = "association.member" state = fields.Selection(STATES, 'State', readonly=True) code = fields.Char('Code', select=True, _states = {'readonly': Eval('state') != 'draft'} _depends = ['state'] state = fields.Selection([ ('draft', 'Draft'), ('running', 'Running'), ('stopped', 'Stopped'), ], "State", readonly=True) code = fields.Char("Code", select=True, states={ 'readonly': Eval('code_readonly', True), 'required': Eval('state') != 'draft'}, depends=['code_readonly', 'state'], 'required': Eval('state') != 'draft' }, depends=_depends + ['code_readonly'], help="The internal identifier for the associate.") code_readonly = fields.Function(fields.Boolean('Code Readonly'), code_readonly = fields.Function(fields.Boolean("Code Readonly"), 'get_code_readonly') company = fields.Many2One('company.company', 'Company', states=_STATE, depends=_DEPENDS, company = fields.Many2One('company.company', "Company", states={'readonly': Eval('state') != 'draft'}, depends=_depends, required=True, help="The association this member belongs to.") party = fields.Many2One('party.party', 'Party', states=_STATE, depends=_DEPENDS, party = fields.Many2One('party.party', "Party", states={'readonly': Eval('state') != 'draft'}, depends=_depends, required=True, help="The party that represents the member.") join_date = fields.Date('Join Date', join_date = fields.Date("Join Date", states={ 'readonly': Or( Not(Eval('state').in_(['draft'])), Eval('join_date', False) ), 'required': Eval('state') != 'draft'}, (Eval('state') != 'draft') | Eval('join_date'), 'required': Eval('state') != 'draft' }, domain=[ If( (Eval('join_date')) & (Eval('leave_date')), ('join_date', '<=', Eval('leave_date')), (), )], depends=['leave_date', 'state'], ) ], depends=_depends + ['leave_date'], help="The date the member joined the association.") leave_date = fields.Date('Leave Date', leave_date = fields.Date("Leave Date", domain=[ If( (Eval('join_date')) & (Eval('leave_date')), (Eval('join_date')) & (Eval('leave_date')), ('leave_date', '>=', Eval('join_date')), (), )], depends=['join_date', 'state'], ) ], depends=_depends + ['join_date'], states={ 'invisible': Or( Not(Eval('state').in_(['running', 'stopped'])), Eval('leave_date', False) ), 'invisible': Eval('state') == 'draft', 'required': Eval('state') == 'stopped', 'readonly': Eval('state') != 'running' }, help="The date the member left the association.") memberships = fields.One2Many('association.membership', 'member', memberships = fields.One2Many('association.membership', 'member', "Memberships", domain=[ ('company', '=', Eval('context', {}).get('company', -1)) ('company', '=', Eval('company', -1)) ], depends=['state', 'company'], states={'readonly': Eval('state') != 'draft'}) depends=_depends + ['company'], states=_states) del _states, _depends @classmethod def __setup__(cls): super(Member, cls).__setup__() super().__setup__() t = cls.__table__() cls._sql_constraints = [ ('code_uniq', Unique(t, t.code), 'Member code must be unique.'), Loading Loading @@ -130,7 +138,18 @@ class Member(Workflow, ModelSQL, ModelView): @classmethod def search_rec_name(cls, name, clause): return [('party', ) + tuple(clause[1:])] if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' code_value = clause[2] if clause[1].endswith('like'): code_value = lstrip_wildcard(clause[2]) return [ bool_op, ('code', clause[1], code_value) + tuple(clause[3:]), ('party', ) + tuple(clause[1:]), ] @classmethod def copy(cls, members, default=None): Loading @@ -139,7 +158,8 @@ class Member(Workflow, ModelSQL, ModelView): else: default = default.copy() default.setdefault('code', None) return super(Member, cls).copy(members, default=default) default.setdefault('leave_date', None) return super().copy(members, default=default) @classmethod def default_state(cls): Loading Loading @@ -181,7 +201,6 @@ class Member(Workflow, ModelSQL, ModelView): raise MemberUnpaidFeeError( gettext('association.msg_unpaid_fee', member=", ".join([x.rec_name for x in members]))) cls.save(members) class MembersBookReport(Report): Loading @@ -194,7 +213,7 @@ class MembersBookReport(Report): Member = pool.get('association.member') Company = pool.get('company.company') clause = [('state', '!=', 'draft'), ('company', '=', data['company'])] context = super(MembersBookReport, cls).get_context(records, data) context = super().get_context(records, data) members = Member.search(clause) context["members"] = members context["company"] = Company(data['company']) Loading