1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
23 from osv import fields, osv
24 from tools import config
28 ('none', 'Non Member'),
29 ('canceled', 'Canceled Member'),
30 ('old', 'Old Member'),
31 ('waiting', 'Waiting Member'),
32 ('invoiced', 'Invoiced Member'),
33 ('free', 'Free Member'),
34 ('paid', 'Paid Member'),
47 #~ REQUETE = '''SELECT partner, state FROM (
48 #~ SELECT members.partner AS partner,
49 #~ CASE WHEN MAX(members.state) = 0 THEN 'none'
50 #~ ELSE CASE WHEN MAX(members.state) = 1 THEN 'canceled'
51 #~ ELSE CASE WHEN MAX(members.state) = 2 THEN 'old'
52 #~ ELSE CASE WHEN MAX(members.state) = 3 THEN 'waiting'
53 #~ ELSE CASE WHEN MAX(members.state) = 4 THEN 'invoiced'
54 #~ ELSE CASE WHEN MAX(members.state) = 6 THEN 'free'
55 #~ ELSE CASE WHEN MAX(members.state) = 7 THEN 'paid'
56 #~ END END END END END END END END
59 #~ CASE WHEN MAX(inv_digit.state) = 4 THEN 7
60 #~ ELSE CASE WHEN MAX(inv_digit.state) = 3 THEN 4
61 #~ ELSE CASE WHEN MAX(inv_digit.state) = 2 THEN 3
62 #~ ELSE CASE WHEN MAX(inv_digit.state) = 1 THEN 1
66 #~ SELECT p.id as partner,
67 #~ CASE WHEN ai.state = 'paid' THEN 4
68 #~ ELSE CASE WHEN ai.state = 'open' THEN 3
69 #~ ELSE CASE WHEN ai.state = 'proforma' THEN 2
70 #~ ELSE CASE WHEN ai.state = 'draft' THEN 2
71 #~ ELSE CASE WHEN ai.state = 'cancel' THEN 1
72 #~ END END END END END
75 #~ JOIN account_invoice ai ON (
76 #~ p.id = ai.partner_id
78 #~ JOIN account_invoice_line ail ON (
79 #~ ail.invoice_id = ai.id
81 #~ JOIN membership_membership_line ml ON (
82 #~ ml.account_invoice_line = ail.id
84 #~ WHERE ml.date_from <= '%s'
85 #~ AND ml.date_to >= '%s'
93 #~ SELECT p.id AS partner,
94 #~ CASE WHEN p.free_member THEN 6
95 #~ ELSE CASE WHEN p.associate_member IN (
96 #~ SELECT ai.partner_id FROM account_invoice ai JOIN
97 #~ account_invoice_line ail ON (ail.invoice_id = ai.id AND ai.state = 'paid')
98 #~ JOIN membership_membership_line ml ON (ml.account_invoice_line = ail.id)
99 #~ WHERE ml.date_from <= '%s'
100 #~ AND ml.date_to >= '%s'
105 #~ FROM res_partner p
106 #~ WHERE p.free_member
107 #~ OR p.associate_member > 0
109 #~ SELECT p.id as partner,
110 #~ MAX(CASE WHEN ai.state = 'paid' THEN 2
114 #~ FROM res_partner p
115 #~ JOIN account_invoice ai ON (
116 #~ p.id = ai.partner_id
118 #~ JOIN account_invoice_line ail ON (
119 #~ ail.invoice_id = ai.id
121 #~ JOIN membership_membership_line ml ON (
122 #~ ml.account_invoice_line = ail.id
124 #~ WHERE ml.date_from < '%s'
125 #~ AND ml.date_to < '%s'
126 #~ AND ml.date_from <= ml.date_to
131 #~ GROUP BY members.partner
137 class membership_line(osv.osv):
140 def _check_membership_date(self, cr, uid, ids, context=None):
141 '''Check if membership product is not in the past'''
144 SELECT MIN(ml.date_to - ai.date_invoice)
145 FROM membership_membership_line ml
146 JOIN account_invoice_line ail ON (
147 ml.account_invoice_line = ail.id
149 JOIN account_invoice ai ON (
150 ai.id = ail.invoice_id)
156 if r[0] and r[0] < 0:
160 def _state(self, cr, uid, ids, name, args, context=None):
161 '''Compute the state lines'''
163 for line in self.browse(cr, uid, ids):
165 SELECT i.state, i.id FROM
169 SELECT l.invoice_id FROM
170 account_invoice_line l WHERE
172 SELECT ml.account_invoice_line FROM
173 membership_membership_line ml WHERE
178 fetched = cr.fetchone()
180 res[line.id] = 'canceled'
184 if (istate == 'draft') | (istate == 'proforma'):
186 elif istate == 'open':
188 elif istate == 'paid':
190 inv = self.pool.get('account.invoice').browse(cr, uid, fetched[1])
191 for payment in inv.payment_ids:
192 if payment.invoice and payment.invoice.type == 'out_refund':
194 elif istate == 'cancel':
200 _description = __doc__
201 _name = 'membership.membership_line'
203 'partner': fields.many2one('res.partner', 'Partner', ondelete='cascade', select=1),
204 'date_from': fields.date('From'),
205 'date_to': fields.date('To'),
206 'date_cancel' : fields.date('Cancel date'),
207 'account_invoice_line': fields.many2one('account.invoice.line', 'Account Invoice line'),
208 'state': fields.function(_state, method=True, string='State', type='selection', selection=STATE),
210 _rec_name = 'partner'
213 (_check_membership_date, 'Error, this membership product is out of date', [])
219 class Partner(osv.osv):
221 _inherit = 'res.partner'
223 def _get_partner_id(self, cr, uid, ids, context=None):
224 data_inv = self.pool.get('membership.membership_line').browse(cr, uid, ids, context)
226 for data in data_inv:
227 list_partner.append(data.partner.id)
230 ids2 = self.pool.get('res.partner').search(cr, uid, [('associate_member','in',ids2)], context=context)
234 def _get_invoice_partner(self, cr, uid, ids, context=None):
235 data_inv = self.pool.get('account.invoice').browse(cr, uid, ids, context)
237 for data in data_inv:
238 list_partner.append(data.partner_id.id)
241 ids2 = self.pool.get('res.partner').search(cr, uid, [('associate_member','in',ids2)], context=context)
245 def _membership_state(self, cr, uid, ids, name, args, context=None):
249 today = time.strftime('%Y-%m-%d')
251 partner_data = self.browse(cr,uid,id)
252 if partner_data.membership_cancel and today > partner_data.membership_cancel:
255 if partner_data.membership_stop and today > partner_data.membership_stop:
259 if partner_data.member_lines:
260 for mline in partner_data.member_lines:
261 if mline.date_to >= today:
262 if mline.account_invoice_line and mline.account_invoice_line.invoice_id:
263 mstate = mline.account_invoice_line.invoice_id.state
266 inv = mline.account_invoice_line.invoice_id
267 for payment in inv.payment_ids:
268 if payment.invoice.type == 'out_refund':
271 elif mstate == 'open' and s!=0:
273 elif mstate == 'cancel' and s!=0 and s!=1:
275 elif (mstate == 'draft' or mstate == 'proforma') and s!=0 and s!=1:
278 for mline in partner_data.member_lines:
279 if mline.date_from < today and mline.date_to < today and mline.date_from<=mline.date_to and (mline.account_invoice_line and mline.account_invoice_line.invoice_id.state) == 'paid':
295 if partner_data.free_member and s!=0:
297 if partner_data.associate_member:
298 res_state = self._membership_state(cr, uid, [partner_data.associate_member.id], name, args, context)
299 res[id] = res_state[partner_data.associate_member.id]
302 def _membership_start(self, cr, uid, ids, name, args, context=None):
303 '''Return the start date of membership'''
305 member_line_obj = self.pool.get('membership.membership_line')
306 for partner in self.browse(cr, uid, ids):
307 if partner.associate_member:
308 partner_id = partner.associate_member.id
310 partner_id = partner.id
311 line_id = member_line_obj.search(cr, uid, [('partner', '=', partner_id)],
312 limit=1, order='date_from')
314 res[partner.id] = member_line_obj.read(cr, uid, line_id[0],
315 ['date_from'])['date_from']
317 res[partner.id] = False
320 def _membership_stop(self, cr, uid, ids, name, args, context=None):
321 '''Return the stop date of membership'''
323 member_line_obj = self.pool.get('membership.membership_line')
324 for partner in self.browse(cr, uid, ids):
325 cr.execute('select membership_state from res_partner where id=%s', (partner.id,))
326 data_state = cr.fetchall()
327 if partner.associate_member:
328 partner_id = partner.associate_member.id
330 partner_id = partner.id
331 line_id = member_line_obj.search(cr, uid, [('partner', '=', partner_id)],
332 limit=1, order='date_to desc')
334 res[partner.id] = member_line_obj.read(cr, uid, line_id[0],
335 ['date_to'])['date_to']
337 res[partner.id] = False
340 def _membership_cancel(self, cr, uid, ids, name, args, context=None):
341 '''Return the cancel date of membership'''
343 member_line_obj = self.pool.get('membership.membership_line')
344 for partner in self.browse(cr, uid, ids, context=context):
345 if partner.membership_state != 'canceled':
346 res[partner.id] = False
348 line_id = member_line_obj.search(cr, uid, [('partner', '=', partner.id)],
349 limit=1, order='date_cancel')
351 res[partner.id] = member_line_obj.read(cr, uid, line_id[0],
352 ['date_cancel'])['date_cancel']
354 res[partner.id] = False
357 def _get_partners(self, cr, uid, ids, context={}):
360 ids2 = self.search(cr, uid, [('associate_member','in',ids2)], context=context)
364 def __get_membership_state(self, *args, **kwargs):
365 return self._membership_state(*args, **kwargs)
368 'associate_member': fields.many2one('res.partner', 'Associate member'),
369 'member_lines': fields.one2many('membership.membership_line', 'partner', 'Membership'),
370 'free_member': fields.boolean('Free member'),
371 'membership_amount': fields.float(
372 'Membership amount', digits=(16, 2),
373 help='The price negociated by the partner'),
374 'membership_state': fields.function(
375 __get_membership_state, method = True,
376 string = 'Current membership state', type = 'selection',
377 selection = STATE ,store = {
378 'account.invoice':(_get_invoice_partner,['state'], 10),
379 'membership.membership_line':(_get_partner_id,['state'], 10),
380 'res.partner':(_get_partners, ['free_member', 'membership_state', 'associate_member'], 10)
383 'membership_start': fields.function(
384 _membership_start, method=True,
385 string = 'Start membership date', type = 'date',
387 'account.invoice':(_get_invoice_partner,['state'], 10),
388 'membership.membership_line':(_get_partner_id,['state'], 10),
389 'res.partner':(lambda self,cr,uid,ids,c={}:ids, ['free_member'], 10)
392 'membership_stop': fields.function(
393 _membership_stop, method = True,
394 string = 'Stop membership date', type = 'date',
396 'account.invoice':(_get_invoice_partner,['state'], 10),
397 'membership.membership_line':(_get_partner_id,['state'], 10),
398 'res.partner':(lambda self,cr,uid,ids,c={}:ids, ['free_member'], 10)
402 'membership_cancel': fields.function(
403 _membership_cancel, method = True,
404 string = 'Cancel membership date', type='date',
406 'account.invoice':(_get_invoice_partner,['state'], 11),
407 'membership.membership_line':(_get_partner_id,['state'], 10),
408 'res.partner':(lambda self,cr,uid,ids,c={}:ids, ['free_member'], 10)
413 'free_member': lambda *a: False,
414 'membership_cancel' : lambda *d : False,
417 def _check_recursion(self, cr, uid, ids):
420 cr.execute('select distinct associate_member from res_partner where id in %s', (tuple(ids),))
421 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
428 (_check_recursion, 'Error ! You can not create recursive associated members.', ['associate_member'])
431 def copy(self, cr, uid, id, default=None, context=None):
436 default = default.copy()
437 default['member_lines'] = []
438 return super(Partner, self).copy(cr, uid, id, default, context)
442 class product_template(osv.osv):
443 _inherit = 'product.template'
445 'member_price':fields.float('Member Price', digits=(16, int(config['price_accuracy']))),
449 class Product(osv.osv):
451 def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False):
452 if ('product' in context) and (context['product']=='membership_product'):
453 model_data_ids_form = self.pool.get('ir.model.data').search(cr,user,[('model','=','ir.ui.view'),('name','in',['membership_products_form','membership_products_tree'])])
454 resource_id_form = self.pool.get('ir.model.data').read(cr,user,model_data_ids_form,fields=['res_id','name'])
456 for i in resource_id_form:
457 dict_model[i['name']]=i['res_id']
458 if view_type=='form':
459 view_id = dict_model['membership_products_form']
461 view_id = dict_model['membership_products_tree']
462 return super(Product,self).fields_view_get(cr, user, view_id, view_type, context, toolbar)
465 _inherit = 'product.product'
466 _description = 'product.product'
469 'membership': fields.boolean('Membership', help='Specify if this product is a membership product'),
470 'membership_date_from': fields.date('Date from'),
471 'membership_date_to': fields.date('Date to'),
472 # 'member_price':fields.float('Member Price'),
476 'membership': lambda *args: False
481 class Invoice(osv.osv):
484 _inherit = 'account.invoice'
486 def action_cancel(self, cr, uid, ids, context=None):
487 '''Create a 'date_cancel' on the membership_line object'''
490 member_line_obj = self.pool.get('membership.membership_line')
491 today = time.strftime('%Y-%m-%d')
492 for invoice in self.browse(cr, uid, ids):
493 mlines = member_line_obj.search(cr,uid,
494 [('account_invoice_line','in',
495 [ l.id for l in invoice.invoice_line])], context)
496 member_line_obj.write(cr,uid,mlines, {'date_cancel':today}, context)
497 return super(Invoice, self).action_cancel(cr, uid, ids, context)
501 class ReportPartnerMemberYear(osv.osv):
502 '''Membership by Years'''
504 _name = 'report.partner_member.year'
505 _description = __doc__
509 'year': fields.char('Year', size=4, readonly=True, select=1),
510 'canceled_number': fields.integer('Canceled', readonly=True),
511 'waiting_number': fields.integer('Waiting', readonly=True),
512 'invoiced_number': fields.integer('Invoiced', readonly=True),
513 'paid_number': fields.integer('Paid', readonly=True),
514 'canceled_amount': fields.float('Canceled', digits=(16, 2), readonly=True),
515 'waiting_amount': fields.float('Waiting', digits=(16, 2), readonly=True),
516 'invoiced_amount': fields.float('Invoiced', digits=(16, 2), readonly=True),
517 'paid_amount': fields.float('Paid', digits=(16, 2), readonly=True),
518 'currency': fields.many2one('res.currency', 'Currency', readonly=True,
523 '''Create the view'''
525 CREATE OR REPLACE VIEW report_partner_member_year AS (
528 COUNT(ncanceled) as canceled_number,
529 COUNT(npaid) as paid_number,
530 COUNT(ninvoiced) as invoiced_number,
531 COUNT(nwaiting) as waiting_number,
532 SUM(acanceled) as canceled_amount,
533 SUM(apaid) as paid_amount,
534 SUM(ainvoiced) as invoiced_amount,
535 SUM(awaiting) as waiting_amount,
539 CASE WHEN ai.state = 'cancel' THEN ml.id END AS ncanceled,
540 CASE WHEN ai.state = 'paid' THEN ml.id END AS npaid,
541 CASE WHEN ai.state = 'open' THEN ml.id END AS ninvoiced,
542 CASE WHEN (ai.state = 'draft' OR ai.state = 'proforma')
543 THEN ml.id END AS nwaiting,
544 CASE WHEN ai.state = 'cancel'
545 THEN SUM(ail.price_unit * ail.quantity * (1 - ail.discount / 100))
546 ELSE 0 END AS acanceled,
547 CASE WHEN ai.state = 'paid'
548 THEN SUM(ail.price_unit * ail.quantity * (1 - ail.discount / 100))
550 CASE WHEN ai.state = 'open'
551 THEN SUM(ail.price_unit * ail.quantity * (1 - ail.discount / 100))
552 ELSE 0 END AS ainvoiced,
553 CASE WHEN (ai.state = 'draft' OR ai.state = 'proforma')
554 THEN SUM(ail.price_unit * ail.quantity * (1 - ail.discount / 100))
555 ELSE 0 END AS awaiting,
556 TO_CHAR(ml.date_from, 'YYYY') AS year,
557 ai.currency_id AS currency,
559 FROM membership_membership_line ml
560 JOIN (account_invoice_line ail
561 LEFT JOIN account_invoice ai
562 ON (ail.invoice_id = ai.id))
563 ON (ml.account_invoice_line = ail.id)
565 ON (ml.partner = p.id)
566 GROUP BY TO_CHAR(ml.date_from, 'YYYY'), ai.state,
567 ai.currency_id, ml.id) AS foo
568 GROUP BY year, currency)
571 ReportPartnerMemberYear()
574 class ReportPartnerMemberYearNew(osv.osv):
575 '''New Membership by Years'''
577 _name = 'report.partner_member.year_new'
578 _description = __doc__
582 'year': fields.char('Year', size=4, readonly=True, select=1),
583 'canceled_number': fields.integer('Canceled', readonly=True),
584 'waiting_number': fields.integer('Waiting', readonly=True),
585 'invoiced_number': fields.integer('Invoiced', readonly=True),
586 'paid_number': fields.integer('Paid', readonly=True),
587 'canceled_amount': fields.float('Canceled', digits=(16, 2), readonly=True),
588 'waiting_amount': fields.float('Waiting', digits=(16, 2), readonly=True),
589 'invoiced_amount': fields.float('Invoiced', digits=(16, 2), readonly=True),
590 'paid_amount': fields.float('Paid', digits=(16, 2), readonly=True),
591 'currency': fields.many2one('res.currency', 'Currency', readonly=True,
595 def init(self, cursor):
596 '''Create the view'''
598 CREATE OR REPLACE VIEW report_partner_member_year_new AS (
601 COUNT(ncanceled) AS canceled_number,
602 COUNT(npaid) AS paid_number,
603 COUNT(ninvoiced) AS invoiced_number,
604 COUNT(nwaiting) AS waiting_number,
605 SUM(acanceled) AS canceled_amount,
606 SUM(apaid) AS paid_amount,
607 SUM(ainvoiced) AS invoiced_amount,
608 SUM(awaiting) AS waiting_amount,
612 CASE WHEN ai.state = 'cancel' THEN ml2.id END AS ncanceled,
613 CASE WHEN ai.state = 'paid' THEN ml2.id END AS npaid,
614 CASE WHEN ai.state = 'open' THEN ml2.id END AS ninvoiced,
615 CASE WHEN (ai.state = 'draft' OR ai.state = 'proforma')
616 THEN ml2.id END AS nwaiting,
617 CASE WHEN ai.state = 'cancel'
618 THEN SUM(ail.price_unit * ail.quantity * (1 - ail.discount / 100))
619 ELSE 0 END AS acanceled,
620 CASE WHEN ai.state = 'paid'
621 THEN SUM(ail.price_unit * ail.quantity * (1 - ail.discount / 100))
623 CASE WHEN ai.state = 'open'
624 THEN SUM(ail.price_unit * ail.quantity * (1 - ail.discount / 100))
625 ELSE 0 END AS ainvoiced,
626 CASE WHEN (ai.state = 'draft' OR ai.state = 'proforma')
627 THEN SUM(ail.price_unit * ail.quantity * (1 - ail.discount / 100))
628 ELSE 0 END AS awaiting,
629 TO_CHAR(ml2.date_from, 'YYYY') AS year,
630 ai.currency_id AS currency,
634 MIN(date_from) AS date_from
635 FROM membership_membership_line
638 JOIN membership_membership_line ml2
639 JOIN (account_invoice_line ail
640 LEFT JOIN account_invoice ai
641 ON (ail.invoice_id = ai.id))
642 ON (ml2.account_invoice_line = ail.id)
643 ON (ml1.id = ml2.partner AND ml1.date_from = ml2.date_from)
645 ON (ml2.partner = p.id)
646 GROUP BY TO_CHAR(ml2.date_from, 'YYYY'), ai.state,
647 ai.currency_id, ml2.id) AS foo
648 GROUP BY year, currency
652 ReportPartnerMemberYearNew()
654 class account_invoice_line(osv.osv):
655 _inherit='account.invoice.line'
656 def write(self, cr, uid, ids, vals, context=None):
659 res = super(account_invoice_line, self).write(cr, uid, ids, vals, context=context)
660 member_line_obj = self.pool.get('membership.membership_line')
661 for line in self.browse(cr, uid, ids):
662 if line.invoice_id.type == 'out_invoice':
663 ml_ids = member_line_obj.search(cr, uid, [('account_invoice_line','=',line.id)])
664 if line.product_id and line.product_id.membership and not ml_ids:
665 # Product line has changed to a membership product
666 date_from = line.product_id.membership_date_from
667 date_to = line.product_id.membership_date_to
668 if line.invoice_id.date_invoice > date_from and line.invoice_id.date_invoice < date_to:
669 date_from = line.invoice_id.date_invoice
670 line_id = member_line_obj.create(cr, uid, {
671 'partner': line.invoice_id.partner_id.id,
672 'date_from': date_from,
674 'account_invoice_line': line.id,
676 if line.product_id and not line.product_id.membership and ml_ids:
677 # Product line has changed to a non membership product
678 member_line_obj.unlink(cr, uid, ml_ids, context=context)
681 def unlink(self, cr, uid, ids, context=None):
684 member_line_obj = self.pool.get('membership.membership_line')
686 ml_ids = member_line_obj.search(cr, uid, [('account_invoice_line','=',id)])
687 member_line_obj.unlink(cr, uid, ml_ids, context=context)
688 return super(account_invoice_line, self).unlink(cr, uid, ids, context=context)
690 def create(self, cr, uid, vals, context={}):
691 result = super(account_invoice_line, self).create(cr, uid, vals, context)
692 line = self.browse(cr, uid, result)
693 if line.invoice_id.type == 'out_invoice':
694 member_line_obj = self.pool.get('membership.membership_line')
695 ml_ids = member_line_obj.search(cr, uid, [('account_invoice_line','=',line.id)])
696 if line.product_id and line.product_id.membership and not ml_ids:
697 # Product line is a membership product
698 date_from = line.product_id.membership_date_from
699 date_to = line.product_id.membership_date_to
700 if line.invoice_id.date_invoice > date_from and line.invoice_id.date_invoice < date_to:
701 date_from = line.invoice_id.date_invoice
702 line_id = member_line_obj.create(cr, uid, {
703 'partner': line.invoice_id.partner_id and line.invoice_id.partner_id.id or False,
704 'date_from': date_from,
706 'account_invoice_line': line.id,
710 account_invoice_line()
711 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: