diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/membership/models | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/membership/models')
| -rw-r--r-- | addons/membership/models/__init__.py | 7 | ||||
| -rw-r--r-- | addons/membership/models/account_move.py | 115 | ||||
| -rw-r--r-- | addons/membership/models/membership.py | 78 | ||||
| -rw-r--r-- | addons/membership/models/partner.py | 136 | ||||
| -rw-r--r-- | addons/membership/models/product.py | 27 |
5 files changed, 363 insertions, 0 deletions
diff --git a/addons/membership/models/__init__.py b/addons/membership/models/__init__.py new file mode 100644 index 00000000..4a529d01 --- /dev/null +++ b/addons/membership/models/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import membership +from . import partner +from . import product +from . import account_move diff --git a/addons/membership/models/account_move.py b/addons/membership/models/account_move.py new file mode 100644 index 00000000..83f1a4f7 --- /dev/null +++ b/addons/membership/models/account_move.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + +from datetime import date + + +class AccountMove(models.Model): + _inherit = 'account.move' + + def button_draft(self): + # OVERRIDE to update the cancel date. + res = super(AccountMove, self).button_draft() + for move in self: + if move.move_type == 'out_invoice': + self.env['membership.membership_line'].search([ + ('account_invoice_line', 'in', move.mapped('invoice_line_ids').ids) + ]).write({'date_cancel': False}) + return res + + def button_cancel(self): + # OVERRIDE to update the cancel date. + res = super(AccountMove, self).button_cancel() + for move in self: + if move.move_type == 'out_invoice': + self.env['membership.membership_line'].search([ + ('account_invoice_line', 'in', move.mapped('invoice_line_ids').ids) + ]).write({'date_cancel': fields.Date.today()}) + return res + + def write(self, vals): + # OVERRIDE to write the partner on the membership lines. + res = super(AccountMove, self).write(vals) + if 'partner_id' in vals: + self.env['membership.membership_line'].search([ + ('account_invoice_line', 'in', self.mapped('invoice_line_ids').ids) + ]).write({'partner': vals['partner_id']}) + return res + + +class AccountMoveLine(models.Model): + _inherit = 'account.move.line' + + def write(self, vals): + # OVERRIDE + res = super(AccountMoveLine, self).write(vals) + + to_process = self.filtered(lambda line: line.move_id.move_type == 'out_invoice' and line.product_id.membership) + + # Nothing to process, break. + if not to_process: + return res + + existing_memberships = self.env['membership.membership_line'].search([ + ('account_invoice_line', 'in', to_process.ids)]) + to_process = to_process - existing_memberships.mapped('account_invoice_line') + + # All memberships already exist, break. + if not to_process: + return res + + memberships_vals = [] + for line in to_process: + date_from = line.product_id.membership_date_from + date_to = line.product_id.membership_date_to + if (date_from and date_from < (line.move_id.invoice_date or date.min) < (date_to or date.min)): + date_from = line.move_id.invoice_date + memberships_vals.append({ + 'partner': line.move_id.partner_id.id, + 'membership_id': line.product_id.id, + 'member_price': line.price_unit, + 'date': fields.Date.today(), + 'date_from': date_from, + 'date_to': date_to, + 'account_invoice_line': line.id, + }) + self.env['membership.membership_line'].create(memberships_vals) + return res + + @api.model_create_multi + def create(self, vals_list): + # OVERRIDE + lines = super(AccountMoveLine, self).create(vals_list) + to_process = lines.filtered(lambda line: line.move_id.move_type == 'out_invoice' and line.product_id.membership) + + # Nothing to process, break. + if not to_process: + return lines + + existing_memberships = self.env['membership.membership_line'].search([ + ('account_invoice_line', 'in', to_process.ids)]) + to_process = to_process - existing_memberships.mapped('account_invoice_line') + + # All memberships already exist, break. + if not to_process: + return lines + + memberships_vals = [] + for line in to_process: + date_from = line.product_id.membership_date_from + date_to = line.product_id.membership_date_to + if (date_from and date_from < (line.move_id.invoice_date or date.min) < (date_to or date.min)): + date_from = line.move_id.invoice_date + memberships_vals.append({ + 'partner': line.move_id.partner_id.id, + 'membership_id': line.product_id.id, + 'member_price': line.price_unit, + 'date': fields.Date.today(), + 'date_from': date_from, + 'date_to': date_to, + 'account_invoice_line': line.id, + }) + self.env['membership.membership_line'].create(memberships_vals) + return lines diff --git a/addons/membership/models/membership.py b/addons/membership/models/membership.py new file mode 100644 index 00000000..bdfa9a20 --- /dev/null +++ b/addons/membership/models/membership.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + +STATE = [ + ('none', 'Non Member'), + ('canceled', 'Cancelled Member'), + ('old', 'Old Member'), + ('waiting', 'Waiting Member'), + ('invoiced', 'Invoiced Member'), + ('free', 'Free Member'), + ('paid', 'Paid Member'), +] + + +class MembershipLine(models.Model): + _name = 'membership.membership_line' + _rec_name = 'partner' + _order = 'id desc' + _description = 'Membership Line' + + partner = fields.Many2one('res.partner', string='Partner', ondelete='cascade', index=True) + membership_id = fields.Many2one('product.product', string="Membership", required=True) + date_from = fields.Date(string='From', readonly=True) + date_to = fields.Date(string='To', readonly=True) + date_cancel = fields.Date(string='Cancel date') + date = fields.Date(string='Join Date', + help="Date on which member has joined the membership") + member_price = fields.Float(string='Membership Fee', + digits='Product Price', required=True, + help='Amount for the membership') + account_invoice_line = fields.Many2one('account.move.line', string='Account Invoice line', readonly=True, ondelete='cascade') + account_invoice_id = fields.Many2one('account.move', related='account_invoice_line.move_id', string='Invoice', readonly=True) + company_id = fields.Many2one('res.company', related='account_invoice_line.move_id.company_id', string="Company", readonly=True, store=True) + state = fields.Selection(STATE, compute='_compute_state', string='Membership Status', store=True, + help="It indicates the membership status.\n" + "-Non Member: A member who has not applied for any membership.\n" + "-Cancelled Member: A member who has cancelled his membership.\n" + "-Old Member: A member whose membership date has expired.\n" + "-Waiting Member: A member who has applied for the membership and whose invoice is going to be created.\n" + "-Invoiced Member: A member whose invoice has been created.\n" + "-Paid Member: A member who has paid the membership amount.") + + @api.depends('account_invoice_id.state', + 'account_invoice_id.amount_residual', + 'account_invoice_id.payment_state') + def _compute_state(self): + """Compute the state lines """ + if not self: + return + + self._cr.execute(''' + SELECT reversed_entry_id, COUNT(id) + FROM account_move + WHERE reversed_entry_id IN %s + GROUP BY reversed_entry_id + ''', [tuple(self.mapped('account_invoice_id.id'))]) + reverse_map = dict(self._cr.fetchall()) + for line in self: + move_state = line.account_invoice_id.state + payment_state = line.account_invoice_id.payment_state + + line.state = 'none' + if move_state == 'draft': + line.state = 'waiting' + elif move_state == 'posted': + if payment_state == 'paid': + if reverse_map.get(line.account_invoice_id.id): + line.state = 'canceled' + else: + line.state = 'paid' + elif payment_state == 'in_payment': + line.state = 'paid' + elif payment_state in ('not_paid', 'partial'): + line.state = 'invoiced' + elif move_state == 'cancel': + line.state = 'canceled' diff --git a/addons/membership/models/partner.py b/addons/membership/models/partner.py new file mode 100644 index 00000000..5528d7ad --- /dev/null +++ b/addons/membership/models/partner.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from datetime import date +from odoo import api, fields, models, _ +from odoo.exceptions import UserError, ValidationError +from . import membership + + +class Partner(models.Model): + _inherit = 'res.partner' + + associate_member = fields.Many2one('res.partner', string='Associate Member', + help="A member with whom you want to associate your membership." + "It will consider the membership state of the associated member.") + member_lines = fields.One2many('membership.membership_line', 'partner', string='Membership') + free_member = fields.Boolean(string='Free Member', + help="Select if you want to give free membership.") + membership_amount = fields.Float(string='Membership Amount', digits=(16, 2), + help='The price negotiated by the partner') + membership_state = fields.Selection(membership.STATE, compute='_compute_membership_state', + string='Current Membership Status', store=True, + help='It indicates the membership state.\n' + '-Non Member: A partner who has not applied for any membership.\n' + '-Cancelled Member: A member who has cancelled his membership.\n' + '-Old Member: A member whose membership date has expired.\n' + '-Waiting Member: A member who has applied for the membership and whose invoice is going to be created.\n' + '-Invoiced Member: A member whose invoice has been created.\n' + '-Paying member: A member who has paid the membership fee.') + membership_start = fields.Date(compute='_compute_membership_state', + string ='Membership Start Date', store=True, + help="Date from which membership becomes active.") + membership_stop = fields.Date(compute='_compute_membership_state', + string ='Membership End Date', store=True, + help="Date until which membership remains active.") + membership_cancel = fields.Date(compute='_compute_membership_state', + string ='Cancel Membership Date', store=True, + help="Date on which membership has been cancelled") + + @api.depends('member_lines.account_invoice_line', + 'member_lines.account_invoice_line.move_id.state', + 'member_lines.account_invoice_line.move_id.payment_state', + 'member_lines.account_invoice_line.move_id.partner_id', + 'free_member', + 'member_lines.date_to', 'member_lines.date_from', + 'associate_member') + def _compute_membership_state(self): + today = fields.Date.today() + for partner in self: + state = 'none' + + partner.membership_start = self.env['membership.membership_line'].search([ + ('partner', '=', partner.associate_member.id or partner.id), ('date_cancel','=',False) + ], limit=1, order='date_from').date_from + partner.membership_stop = self.env['membership.membership_line'].search([ + ('partner', '=', partner.associate_member.id or partner.id),('date_cancel','=',False) + ], limit=1, order='date_to desc').date_to + partner.membership_cancel = self.env['membership.membership_line'].search([ + ('partner', '=', partner.id) + ], limit=1, order='date_cancel').date_cancel + + if partner.membership_cancel and today > partner.membership_cancel: + partner.membership_state = 'free' if partner.free_member else 'canceled' + continue + if partner.membership_stop and today > partner.membership_stop: + if partner.free_member: + partner.membership_state = 'free' + continue + if partner.associate_member: + partner.associate_member._compute_membership_state() + partner.membership_state = partner.associate_member.membership_state + continue + + line_states = [mline.state for mline in partner.member_lines if \ + (mline.date_to or date.min) >= today and \ + (mline.date_from or date.min) <= today and \ + mline.account_invoice_line.move_id.partner_id == partner] + + if 'paid' in line_states: + state = 'paid' + elif 'invoiced' in line_states: + state = 'invoiced' + elif 'waiting' in line_states: + state = 'waiting' + elif 'canceled' in line_states: + state = 'canceled' + + if state == 'none': + for mline in partner.member_lines: + # if there is an old invoice paid, set the state to 'old' + if ((mline.date_from or date.min) < today and (mline.date_to or date.min) < today and \ + (mline.date_from or date.min) <= (mline.date_to or date.min) and \ + mline.account_invoice_id and mline.account_invoice_id.payment_state in ('in_payment', 'paid')): + state = 'old' + break + + if partner.free_member and state != 'paid': + state = 'free' + partner.membership_state = state + + @api.constrains('associate_member') + def _check_recursion_associate_member(self): + for partner in self: + level = 100 + while partner: + partner = partner.associate_member + if not level: + raise ValidationError(_('You cannot create recursive associated members.')) + level -= 1 + + @api.model + def _cron_update_membership(self): + partners = self.search([('membership_state', 'in', ['invoiced', 'paid'])]) + # mark the field to be recomputed, and recompute it + self.env.add_to_compute(self._fields['membership_state'], partners) + + def create_membership_invoice(self, product, amount): + """ Create Customer Invoice of Membership for partners. + """ + invoice_vals_list = [] + for partner in self: + addr = partner.address_get(['invoice']) + if partner.free_member: + raise UserError(_("Partner is a free Member.")) + if not addr.get('invoice', False): + raise UserError(_("Partner doesn't have an address to make the invoice.")) + + invoice_vals_list.append({ + 'move_type': 'out_invoice', + 'partner_id': partner.id, + 'invoice_line_ids': [ + (0, None, {'product_id': product.id, 'quantity': 1, 'price_unit': amount, 'tax_ids': [(6, 0, product.taxes_id.ids)]}) + ] + }) + + return self.env['account.move'].create(invoice_vals_list) diff --git a/addons/membership/models/product.py b/addons/membership/models/product.py new file mode 100644 index 00000000..6107ad3b --- /dev/null +++ b/addons/membership/models/product.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class Product(models.Model): + _inherit = 'product.template' + + membership = fields.Boolean(help='Check if the product is eligible for membership.') + membership_date_from = fields.Date(string='Membership Start Date', + help='Date from which membership becomes active.') + membership_date_to = fields.Date(string='Membership End Date', + help='Date until which membership remains active.') + + _sql_constraints = [ + ('membership_date_greater', 'check(membership_date_to >= membership_date_from)', 'Error ! Ending Date cannot be set before Beginning Date.') + ] + + @api.model + def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): + if self._context.get('product') == 'membership_product': + if view_type == 'form': + view_id = self.env.ref('membership.membership_products_form').id + else: + view_id = self.env.ref('membership.membership_products_tree').id + return super(Product, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu) |
