adce860b51a70d1fea69fb7f790cc57240bceedb
[odoo/odoo.git] / addons / event / event.py
1 # -*- coding: utf-8 -*-
2
3 import pytz
4
5 from openerp import _, api, fields, models
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('Event Type', required=True)
15     default_reply_to = fields.Char('Reply To')
16     default_registration_min = fields.Integer(
17         'Default Minimum Registration', default=0,
18         help="It will select this default minimum value when you choose this event")
19     default_registration_max = fields.Integer(
20         'Default Maximum Registration', default=0,
21         help="It will select this default maximum value when you choose this event")
22
23
24 class event_event(models.Model):
25     """Event"""
26     _name = 'event.event'
27     _description = 'Event'
28     _inherit = ['mail.thread', 'ir.needaction_mixin']
29     _order = 'date_begin'
30
31     name = fields.Char(
32         string='Name', translate=True, required=True,
33         readonly=False, states={'done': [('readonly', True)]})
34     user_id = fields.Many2one(
35         'res.users', string='Responsible',
36         default=lambda self: self.env.user,
37         readonly=False, states={'done': [('readonly', True)]})
38     company_id = fields.Many2one(
39         'res.company', string='Company', change_default=True,
40         default=lambda self: self.env['res.company']._company_default_get('event.event'),
41         required=False, readonly=False, states={'done': [('readonly', True)]})
42     organizer_id = fields.Many2one(
43         'res.partner', string='Organizer',
44         default=lambda self: self.env.user.company_id.partner_id)
45     type = fields.Many2one(
46         'event.type', string='Category',
47         readonly=False, states={'done': [('readonly', True)]})
48     color = fields.Integer('Kanban Color Index')
49     event_mail_ids = fields.One2many('event.mail', 'event_id', string='Mail Schedule', default=lambda self: self._default_event_mail_ids())
50
51     @api.model
52     def _default_event_mail_ids(self):
53         return [(0, 0, {
54             'interval_unit': 'now',
55             'interval_type': 'after_sub',
56             'template_id': self.env['ir.model.data'].xmlid_to_res_id('event.event_subscription')
57         })]
58
59     # Seats and computation
60     seats_max = fields.Integer(
61         string='Maximum Available Seats', oldname='register_max',
62         readonly=True, states={'draft': [('readonly', False)]},
63         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 )")
64     seats_availability = fields.Selection(
65         [('limited', 'Limited'), ('unlimited', 'Unlimited')],
66         'Available Seat', required=True, default='unlimited')
67     seats_min = fields.Integer(
68         string='Minimum Reserved Seats', oldname='register_min',
69         readonly=True, states={'draft': [('readonly', False)]},
70         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 )")
71     seats_reserved = fields.Integer(
72         oldname='register_current', string='Reserved Seats',
73         store=True, readonly=True, compute='_compute_seats')
74     seats_available = fields.Integer(
75         oldname='register_avail', string='Available Seats',
76         store=True, readonly=True, compute='_compute_seats')
77     seats_unconfirmed = fields.Integer(
78         oldname='register_prospect', string='Unconfirmed Seat Reservations',
79         store=True, readonly=True, compute='_compute_seats')
80     seats_used = fields.Integer(
81         oldname='register_attended', string='Number of Participations',
82         store=True, readonly=True, compute='_compute_seats')
83
84     @api.multi
85     @api.depends('seats_max', 'registration_ids.state')
86     def _compute_seats(self):
87         """ Determine reserved, available, reserved but unconfirmed and used seats. """
88         # initialize fields to 0
89         for event in self:
90             event.seats_unconfirmed = event.seats_reserved = event.seats_used = event.seats_available = 0
91         # aggregate registrations by event and by state
92         if self.ids:
93             state_field = {
94                 'draft': 'seats_unconfirmed',
95                 'open': 'seats_reserved',
96                 'done': 'seats_used',
97             }
98             query = """ SELECT event_id, state, count(event_id)
99                         FROM event_registration
100                         WHERE event_id IN %s AND state IN ('draft', 'open', 'done')
101                         GROUP BY event_id, state
102                     """
103             self._cr.execute(query, (tuple(self.ids),))
104             for event_id, state, num in self._cr.fetchall():
105                 event = self.browse(event_id)
106                 event[state_field[state]] += num
107         # compute seats_available
108         for event in self:
109             if event.seats_max > 0:
110                 event.seats_available = event.seats_max - (event.seats_reserved + event.seats_used)
111
112     # Registration fields
113     registration_ids = fields.One2many(
114         'event.registration', 'event_id', string='Attendees',
115         readonly=False, states={'done': [('readonly', True)]})
116     # Date fields
117     date_tz = fields.Selection('_tz_get', string='Timezone', default=lambda self: self.env.user.tz)
118     date_begin = fields.Datetime(
119         string='Start Date', required=True,
120         readonly=True, states={'draft': [('readonly', False)]})
121     date_end = fields.Datetime(
122         string='End Date', required=True,
123         readonly=True, states={'draft': [('readonly', False)]})
124     date_begin_located = fields.Datetime(string='Start Date Located', compute='_compute_date_begin_tz')
125     date_end_located = fields.Datetime(string='End Date Located', compute='_compute_date_end_tz')
126
127     @api.model
128     def _tz_get(self):
129         return [(x, x) for x in pytz.all_timezones]
130
131     @api.one
132     @api.depends('date_tz', 'date_begin')
133     def _compute_date_begin_tz(self):
134         if self.date_begin:
135             self_in_tz = self.with_context(tz=(self.date_tz or 'UTC'))
136             date_begin = fields.Datetime.from_string(self.date_begin)
137             self.date_begin_located = fields.Datetime.to_string(fields.Datetime.context_timestamp(self_in_tz, date_begin))
138         else:
139             self.date_begin_located = False
140
141     @api.one
142     @api.depends('date_tz', 'date_end')
143     def _compute_date_end_tz(self):
144         if self.date_end:
145             self_in_tz = self.with_context(tz=(self.date_tz or 'UTC'))
146             date_end = fields.Datetime.from_string(self.date_end)
147             self.date_end_located = fields.Datetime.to_string(fields.Datetime.context_timestamp(self_in_tz, date_end))
148         else:
149             self.date_end_located = False
150
151     state = fields.Selection([
152         ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'),
153         ('confirm', 'Confirmed'), ('done', 'Done')],
154         string='Status', default='draft', readonly=True, required=True, copy=False,
155         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'.")
156     auto_confirm = fields.Boolean(string='Auto Confirmation Activated', compute='_compute_auto_confirm')
157
158     @api.one
159     def _compute_auto_confirm(self):
160         self.auto_confirm = self.env['ir.values'].get_default('marketing.config.settings', 'auto_confirmation')
161
162     reply_to = fields.Char(
163         'Reply-To Email', readonly=False, states={'done': [('readonly', True)]},
164         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.")
165     address_id = fields.Many2one(
166         'res.partner', string='Location', default=lambda self: self.env.user.company_id.partner_id,
167         readonly=False, states={'done': [('readonly', True)]})
168     country_id = fields.Many2one('res.country', 'Country',  related='address_id.country_id', store=True)
169     description = fields.Html(
170         string='Description', oldname='note', translate=True,
171         readonly=False, states={'done': [('readonly', True)]})
172
173     @api.multi
174     @api.depends('name', 'date_begin', 'date_end')
175     def name_get(self):
176         result = []
177         for event in self:
178             dates = [dt.split(' ')[0] for dt in [event.date_begin, event.date_end] if dt]
179             dates = sorted(set(dates))
180             result.append((event.id, '%s (%s)' % (event.name, ' - '.join(dates))))
181         return result
182
183     @api.one
184     @api.constrains('seats_max', 'seats_available')
185     def _check_seats_limit(self):
186         if self.seats_max and self.seats_available < 0:
187             raise Warning(_('No more available seats.'))
188
189     @api.one
190     @api.constrains('date_begin', 'date_end')
191     def _check_closing_date(self):
192         if self.date_end < self.date_begin:
193             raise Warning(_('Closing Date cannot be set before Beginning Date.'))
194
195     @api.model
196     def create(self, vals):
197         res = super(event_event, self).create(vals)
198         if res.auto_confirm:
199             res.button_confirm()
200         return res
201
202     @api.one
203     def button_draft(self):
204         self.state = 'draft'
205
206     @api.one
207     def button_cancel(self):
208         for event_reg in self.registration_ids:
209             if event_reg.state == 'done':
210                 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."))
211         self.registration_ids.write({'state': 'cancel'})
212         self.state = 'cancel'
213
214     @api.one
215     def button_done(self):
216         self.state = 'done'
217
218     @api.one
219     def button_confirm(self):
220         self.state = 'confirm'
221
222     @api.onchange('type')
223     def _onchange_type(self):
224         if self.type:
225             self.seats_min = self.type.default_registration_min
226             self.seats_max = self.type.default_registration_max
227             self.reply_to = self.type.default_reply_to
228
229     @api.multi
230     def action_event_registration_report(self):
231         res = self.env['ir.actions.act_window'].for_xml_id('event', 'action_report_event_registration')
232         res['context'] = {
233             "search_default_event_id": self.id,
234             "group_by": ['create_date:day'],
235         }
236         return res
237
238     @api.one
239     def mail_attendees(self, template_id, force_send=False, filter_func=lambda self: True):
240         for attendee in self.registration_ids.filtered(filter_func):
241             self.env['email.template'].browse(template_id).send_mail(attendee.id, force_send=force_send)
242
243
244 class event_registration(models.Model):
245     _name = 'event.registration'
246     _description = 'Attendee'
247     _inherit = ['mail.thread', 'ir.needaction_mixin']
248     _order = 'name, create_date desc'
249
250     origin = fields.Char(
251         string='Source Document', readonly=True,
252         help="Reference of the document that created the registration, for example a sale order")
253     event_id = fields.Many2one(
254         'event.event', string='Event', required=True,
255         readonly=True, states={'draft': [('readonly', False)]})
256     partner_id = fields.Many2one(
257         'res.partner', string='Contact',
258         states={'done': [('readonly', True)]})
259     date_open = fields.Datetime(string='Registration Date', readonly=True, default=lambda self: fields.datetime.now())  # weird crash is directly now
260     date_closed = fields.Datetime(string='Attended Date', readonly=True)
261     event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin', readonly=True)
262     event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end', readonly=True)
263     company_id = fields.Many2one(
264         'res.company', string='Company', related='event_id.company_id',
265         store=True, readonly=True, states={'draft': [('readonly', False)]})
266     state = fields.Selection([
267         ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'),
268         ('open', 'Confirmed'), ('done', 'Attended')],
269         string='Status', default='draft', readonly=True, copy=False, track_visibility='onchange')
270     email = fields.Char(string='Email')
271     phone = fields.Char(string='Phone')
272     name = fields.Char(string='Attendee Name', select=True)
273
274     @api.one
275     @api.constrains('event_id', 'state')
276     def _check_seats_limit(self):
277         if self.event_id.seats_max and self.event_id.seats_available < (1 if self.state == 'draft' else 0):
278             raise Warning(_('No more seats available for this event.'))
279
280     @api.one
281     def _check_auto_confirmation(self):
282         if self._context.get('registration_force_draft'):
283             return False
284         if self.event_id and self.event_id.state == 'confirm' and self.event_id.auto_confirm and self.event_id.seats_available:
285             return True
286         return False
287
288     @api.model
289     def create(self, vals):
290         registration = super(event_registration, self).create(vals)
291         if registration._check_auto_confirmation():
292             registration.sudo().confirm_registration()
293         return registration
294
295     @api.one
296     def do_draft(self):
297         self.state = 'draft'
298
299     @api.one
300     def confirm_registration(self):
301         self.event_id.message_post(
302             body=_('New registration confirmed: %s.') % (self.name or ''),
303             subtype="event.mt_event_registration")
304         self.state = 'open'
305
306     @api.one
307     def button_reg_close(self):
308         """ Close Registration """
309         today = fields.Datetime.now()
310         if self.event_id.date_begin <= today:
311             self.write({'state': 'done', 'date_closed': today})
312         else:
313             raise Warning(_("You must wait for the starting day of the event to do this action."))
314
315     @api.one
316     def button_reg_cancel(self):
317         self.state = 'cancel'
318
319     @api.onchange('partner_id')
320     def _onchange_partner(self):
321         if self.partner_id:
322             contact_id = self.partner_id.address_get().get('default', False)
323             if contact_id:
324                 contact = self.env['res.partner'].browse(contact_id)
325                 self.name = self.name or contact.name
326                 self.email = self.email or contact.email
327                 self.phone = self.phone or contact.phone