[MERGE] forward port of branch 8.0 up to 2b192be
[odoo/odoo.git] / addons / event / event.py
1 # -*- coding: utf-8 -*-
2
3 import pytz
4
5 from openerp import models, fields, api, _
6 from openerp.exceptions import Warning
7
8
9 class event_type(models.Model):
10     """ Event Type """
11     _name = 'event.type'
12     _description = 'Event Type'
13
14     name = fields.Char(string='Event Type', required=True)
15     default_reply_to = fields.Char(
16         string='Default Reply-To',
17         help="The email address of the organizer which is put in the 'Reply-To' of all emails sent automatically at event or registrations confirmation. You can also put your email address of your mail gateway if you use one.")
18     default_email_event = fields.Many2one(
19         'email.template', string='Event Confirmation Email',
20         help="It will select this default confirmation event mail value when you choose this event")
21     default_email_registration = fields.Many2one(
22         'email.template', string='Registration Confirmation Email',
23         help="It will select this default confirmation registration mail value when you choose this event")
24     default_registration_min = fields.Integer(
25         string='Default Minimum Registration', default=0,
26         help="It will select this default minimum value when you choose this event")
27     default_registration_max = fields.Integer(
28         string='Default Maximum Registration', default=0,
29         help="It will select this default maximum value when you choose this event")
30
31
32 class event_event(models.Model):
33     """Event"""
34     _name = 'event.event'
35     _description = 'Event'
36     _inherit = ['mail.thread', 'ir.needaction_mixin']
37     _order = 'date_begin'
38
39     name = fields.Char(
40         string='Name', translate=True, required=True,
41         readonly=False, states={'done': [('readonly', True)]})
42     user_id = fields.Many2one(
43         'res.users', string='Responsible',
44         default=lambda self: self.env.user,
45         readonly=False, states={'done': [('readonly', True)]})
46     company_id = fields.Many2one(
47         'res.company', string='Company', change_default=True,
48         default=lambda self: self.env['res.company']._company_default_get('event.event'),
49         required=False, readonly=False, states={'done': [('readonly', True)]})
50     organizer_id = fields.Many2one(
51         'res.partner', string='Organizer',
52         default=lambda self: self.env.user.company_id.partner_id)
53     type = fields.Many2one(
54         'event.type', string='Category',
55         readonly=False, states={'done': [('readonly', True)]})
56     color = fields.Integer('Kanban Color Index')
57
58     # Seats and computation
59     seats_max = fields.Integer(
60         string='Maximum Available Seats', oldname='register_max',
61         readonly=True, states={'draft': [('readonly', False)]},
62         help="You can for each event define a maximum registration level. If you have too much registrations you are not able to confirm your event. (put 0 to ignore this rule )")
63     seats_availability = fields.Selection(
64         [('limited', 'Limited'), ('unlimited', 'Unlimited')],
65         'Available Seat', required=True, default='unlimited')
66     seats_min = fields.Integer(
67         string='Minimum Reserved Seats', oldname='register_min',
68         readonly=True, states={'draft': [('readonly', False)]},
69         help="You can for each event define a minimum registration level. If you do not enough registrations you are not able to confirm your event. (put 0 to ignore this rule )")
70     seats_reserved = fields.Integer(
71         oldname='register_current', string='Reserved Seats',
72         store=True, readonly=True, compute='_compute_seats')
73     seats_available = fields.Integer(
74         oldname='register_avail', string='Available Seats',
75         store=True, readonly=True, compute='_compute_seats')
76     seats_unconfirmed = fields.Integer(
77         oldname='register_prospect', string='Unconfirmed Seat Reservations',
78         store=True, readonly=True, compute='_compute_seats')
79     seats_used = fields.Integer(
80         oldname='register_attended', string='Number of Participations',
81         store=True, readonly=True, compute='_compute_seats')
82
83     @api.multi
84     @api.depends('seats_max', 'registration_ids.state')
85     def _compute_seats(self):
86         """ Determine reserved, available, reserved but unconfirmed and used seats. """
87         # initialize fields to 0
88         for event in self:
89             event.seats_unconfirmed = event.seats_reserved = event.seats_used = event.seats_available = 0
90         # aggregate registrations by event and by state
91         if self.ids:
92             state_field = {
93                 'draft': 'seats_unconfirmed',
94                 'open': 'seats_reserved',
95                 'done': 'seats_used',
96             }
97             query = """ SELECT event_id, state, count(event_id)
98                         FROM event_registration
99                         WHERE event_id IN %s AND state IN ('draft', 'open', 'done')
100                         GROUP BY event_id, state
101                     """
102             self._cr.execute(query, (tuple(self.ids),))
103             for event_id, state, num in self._cr.fetchall():
104                 event = self.browse(event_id)
105                 event[state_field[state]] += num
106         # compute seats_available
107         for event in self:
108             if event.seats_max > 0:
109                 event.seats_available = event.seats_max - (event.seats_reserved + event.seats_used)
110
111     # Registration fields
112     registration_ids = fields.One2many(
113         'event.registration', 'event_id', string='Attendees',
114         readonly=False, states={'done': [('readonly', True)]})
115     count_registrations = fields.Integer(string='Registrations', compute='_count_registrations')
116
117     @api.one
118     @api.depends('registration_ids')
119     def _count_registrations(self):
120         self.count_registrations = len(self.registration_ids)
121
122     # Date fields
123     date_tz = fields.Selection('_tz_get', string='Timezone', default=lambda self: self.env.user.tz)
124     date_begin = fields.Datetime(
125         string='Start Date', required=True,
126         readonly=True, states={'draft': [('readonly', False)]})
127     date_end = fields.Datetime(
128         string='End Date', required=True,
129         readonly=True, states={'draft': [('readonly', False)]})
130     date_begin_located = fields.Datetime(string='Start Date Located', compute='_compute_date_begin_tz')
131     date_end_located = fields.Datetime(string='End Date Located', compute='_compute_date_end_tz')
132
133     @api.model
134     def _tz_get(self):
135         return [(x, x) for x in pytz.all_timezones]
136
137     @api.one
138     @api.depends('date_tz', 'date_begin')
139     def _compute_date_begin_tz(self):
140         if self.date_begin:
141             self_in_tz = self.with_context(tz=(self.date_tz or 'UTC'))
142             date_begin = fields.Datetime.from_string(self.date_begin)
143             self.date_begin_located = fields.Datetime.to_string(fields.Datetime.context_timestamp(self_in_tz, date_begin))
144         else:
145             self.date_begin_located = False
146
147     @api.one
148     @api.depends('date_tz', 'date_end')
149     def _compute_date_end_tz(self):
150         if self.date_end:
151             self_in_tz = self.with_context(tz=(self.date_tz or 'UTC'))
152             date_end = fields.Datetime.from_string(self.date_end)
153             self.date_end_located = fields.Datetime.to_string(fields.Datetime.context_timestamp(self_in_tz, date_end))
154         else:
155             self.date_end_located = False
156
157     @api.one
158     @api.depends('address_id')
159     def _compute_country(self):
160         self.country_id = self.address_id.country_id
161
162     state = fields.Selection([
163         ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'),
164         ('confirm', 'Confirmed'), ('done', 'Done')],
165         string='Status', default='draft', readonly=True, required=True, copy=False,
166         help="If event is created, the status is 'Draft'. If event is confirmed for the particular dates the status is set to 'Confirmed'. If the event is over, the status is set to 'Done'. If event is cancelled the status is set to 'Cancelled'.")
167     auto_confirm = fields.Boolean(string='Auto Confirmation Activated', compute='_compute_auto_confirm')
168
169     @api.one
170     def _compute_auto_confirm(self):
171         self.auto_confirm = self.env['ir.values'].get_default('marketing.config.settings', 'auto_confirmation')
172
173     # Mailing
174     email_registration_id = fields.Many2one(
175         'email.template', string='Registration Confirmation Email',
176         domain=[('model', '=', 'event.registration')],
177         help='This field contains the template of the mail that will be automatically sent each time a registration for this event is confirmed.')
178     email_confirmation_id = fields.Many2one(
179         'email.template', string='Event Confirmation Email',
180         domain=[('model', '=', 'event.registration')],
181         help="If you set an email template, each participant will receive this email announcing the confirmation of the event.")
182     reply_to = fields.Char(
183         string='Reply-To Email', readonly=False, states={'done': [('readonly', True)]},
184         help="The email address of the organizer is likely to be put here, with the effect to be in the 'Reply-To' of the mails sent automatically at event or registrations confirmation. You can also put the email address of your mail gateway if you use one.")
185     address_id = fields.Many2one(
186         'res.partner', string='Location', default=lambda self: self.env.user.company_id.partner_id,
187         readonly=False, states={'done': [('readonly', True)]})
188     country_id = fields.Many2one('res.country', string='Country',
189         store=True, compute='_compute_country')
190     description = fields.Html(
191         string='Description', oldname='note', translate=True,
192         readonly=False, states={'done': [('readonly', True)]})
193
194     @api.multi
195     @api.depends('name', 'date_begin', 'date_end')
196     def name_get(self):
197         result = []
198         for event in self:
199             dates = [dt.split(' ')[0] for dt in [event.date_begin, event.date_end] if dt]
200             dates = sorted(set(dates))
201             result.append((event.id, '%s (%s)' % (event.name, ' - '.join(dates))))
202         return result
203
204     @api.one
205     @api.constrains('seats_max', 'seats_available')
206     def _check_seats_limit(self):
207         if self.seats_max and self.seats_available < 0:
208             raise Warning(_('No more available seats.'))
209
210     @api.one
211     @api.constrains('date_begin', 'date_end')
212     def _check_closing_date(self):
213         if self.date_end < self.date_begin:
214             raise Warning(_('Closing Date cannot be set before Beginning Date.'))
215
216     @api.model
217     def create(self, vals):
218         res = super(event_event, self).create(vals)
219         if res.auto_confirm:
220             res.confirm_event()
221         return res
222
223     @api.one
224     def button_draft(self):
225         self.state = 'draft'
226
227     @api.one
228     def button_cancel(self):
229         for event_reg in self.registration_ids:
230             if event_reg.state == 'done':
231                 raise Warning(_("You have already set a registration for this event as 'Attended'. Please reset it to draft if you want to cancel this event."))
232         self.registration_ids.write({'state': 'cancel'})
233         self.state = 'cancel'
234
235     @api.one
236     def button_done(self):
237         self.state = 'done'
238
239     @api.one
240     def confirm_event(self):
241         if self.email_confirmation_id:
242             # send reminder that will confirm the event for all the people that were already confirmed
243             regs = self.registration_ids.filtered(lambda reg: reg.state not in ('draft', 'cancel'))
244             regs.mail_user_confirm()
245         self.state = 'confirm'
246
247     @api.one
248     def button_confirm(self):
249         """ Confirm Event and send confirmation email to all register peoples """
250         self.confirm_event()
251
252     @api.onchange('type')
253     def _onchange_type(self):
254         if self.type:
255             self.reply_to = self.type.default_reply_to
256             self.email_registration_id = self.type.default_email_registration
257             self.email_confirmation_id = self.type.default_email_event
258             self.seats_min = self.type.default_registration_min
259             self.seats_max = self.type.default_registration_max
260
261     @api.multi
262     def action_event_registration_report(self):
263         res = self.env['ir.actions.act_window'].for_xml_id('event', 'action_report_event_registration')
264         res['context'] = {
265             "search_default_event_id": self.id,
266             "group_by": ['create_date:day'],
267         }
268         return res
269
270
271 class event_registration(models.Model):
272     _name = 'event.registration'
273     _description = 'Attendee'
274     _inherit = ['mail.thread', 'ir.needaction_mixin']
275     _order = 'name, create_date desc'
276
277     origin = fields.Char(
278         string='Source Document', readonly=True,
279         help="Reference of the document that created the registration, for example a sale order")
280     event_id = fields.Many2one(
281         'event.event', string='Event', required=True,
282         readonly=True, states={'draft': [('readonly', False)]})
283     partner_id = fields.Many2one(
284         'res.partner', string='Contact',
285         states={'done': [('readonly', True)]})
286     date_open = fields.Datetime(string='Registration Date', readonly=True)
287     date_closed = fields.Datetime(string='Attended Date', readonly=True)
288     reply_to = fields.Char(string='Reply-to Email', related='event_id.reply_to', readonly=True)
289     event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin', readonly=True)
290     event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end', readonly=True)
291     company_id = fields.Many2one(
292         'res.company', string='Company', related='event_id.company_id',
293         store=True, readonly=True, states={'draft': [('readonly', False)]})
294     state = fields.Selection([
295         ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'),
296         ('open', 'Confirmed'), ('done', 'Attended')],
297         string='Status', default='draft', readonly=True, copy=False, track_visibility='onchange')
298     email = fields.Char(string='Email')
299     phone = fields.Char(string='Phone')
300     name = fields.Char(string='Attendee Name', select=True)
301
302     @api.one
303     @api.constrains('event_id', 'state')
304     def _check_seats_limit(self):
305         if self.event_id.seats_max and self.event_id.seats_available < (1 if self.state == 'draft' else 0):
306             raise Warning(_('No more seats available for this event.'))
307
308     @api.one
309     def _check_auto_confirmation(self):
310         if self._context.get('registration_force_draft'):
311             return False
312         if self.event_id and self.event_id.state == 'confirm' and self.event_id.auto_confirm and self.event_id.seats_available:
313             return True
314         return False
315
316     @api.model
317     def create(self, vals):
318         res = super(event_registration, self).create(vals)
319         if res._check_auto_confirmation()[0]:
320             res.sudo().confirm_registration()
321         return res
322
323     @api.one
324     def do_draft(self):
325         self.state = 'draft'
326
327     @api.one
328     def confirm_registration(self):
329         self.event_id.message_post(
330             body=_('New registration confirmed: %s.') % (self.name or ''),
331             subtype="event.mt_event_registration")
332         self.state = 'open'
333
334     @api.one
335     def registration_open(self):
336         """ Open Registration """
337         self.confirm_registration()
338         self.mail_user()
339
340     @api.one
341     def button_reg_close(self):
342         """ Close Registration """
343         today = fields.Datetime.now()
344         if self.event_id.date_begin <= today:
345             self.write({'state': 'done', 'date_closed': today})
346         else:
347             raise Warning(_("You must wait for the starting day of the event to do this action."))
348
349     @api.one
350     def button_reg_cancel(self):
351         self.state = 'cancel'
352
353     @api.one
354     def mail_user(self):
355         """Send email to user with email_template when registration is done """
356         if self.event_id.state == 'confirm' and self.event_id.email_confirmation_id:
357             self.mail_user_confirm()
358         else:
359             template = self.event_id.email_registration_id
360             if template:
361                 template.send_mail(self.id)
362
363     @api.one
364     def mail_user_confirm(self):
365         """Send email to user when the event is confirmed """
366         template = self.event_id.email_confirmation_id
367         if template:
368             template.send_mail(self.id)
369
370     @api.onchange('partner_id')
371     def _onchange_partner(self):
372         if self.partner_id:
373             contact_id = self.partner_id.address_get().get('default', False)
374             if contact_id:
375                 contact = self.env['res.partner'].browse(contact_id)
376                 self.name = self.name or contact.name
377                 self.email = self.email or contact.email
378                 self.phone = self.phone or contact.phone