source: main/waeup.kofa/trunk/src/waeup/kofa/hostels/hostel.py @ 16703

Last change on this file since 16703 was 15944, checked in by Henrik Bettermann, 5 years ago

Add logging message to releaseBedIfMaintenanceNotPaid.

  • Property svn:keywords set to Id
File size: 13.7 KB
Line 
1## $Id: hostel.py 15944 2020-01-23 12:27:12Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""
19These are the hostels.
20"""
21import grok
22from zope.event import notify
23from zope.component import getUtility, createObject
24from zope.component.interfaces import IFactory, ComponentLookupError
25from zope.catalog.interfaces import ICatalog
26from zope.catalog.field import FieldIndex
27from datetime import datetime
28from waeup.kofa.utils.helpers import attrs_to_fields
29from waeup.kofa.hostels.vocabularies import NOT_OCCUPIED
30from waeup.kofa.hostels.interfaces import IHostel, IBed
31from waeup.kofa.students.interfaces import IBedTicket
32from waeup.kofa.interfaces import IKofaUtils, IKofaPluggable
33from waeup.kofa.interfaces import MessageFactory as _
34from waeup.kofa.utils.helpers import now, reindex_cat
35
36class Hostel(grok.Container):
37    """This is a hostel.
38    """
39    grok.implements(IHostel)
40    grok.provides(IHostel)
41
42    @property
43    def bed_statistics(self):
44        total = len(self.keys())
45        booked = 0
46        for value in self.values():
47            if value.owner != NOT_OCCUPIED:
48                booked += 1
49        return {'booked':booked, 'total':total}
50
51    def clearHostel(self):
52        """Remove all beds
53        """
54        keys = [i for i in self.keys()] # create deep copy
55        for bed in keys:
56            del self[bed]
57        return
58
59    def addBed(self, bed):
60        """Add a bed.
61        """
62        if not IBed.providedBy(bed):
63            raise TypeError(
64                'Hostels contain only IBed instances')
65        self[bed.bed_id] = bed
66        return
67
68    def updateBeds(self):
69        """Fill hostel with beds or update beds.
70        """
71        added_counter = 0
72        modified_counter = 0
73        removed_counter = 0
74        modified_beds = u''
75
76        # Remove all empty beds. Occupied beds remain in hostel!
77        keys = list(self.keys()) # create list copy
78        for key in keys:
79            if self[key].owner == NOT_OCCUPIED:
80                del self[key]
81                self._p_changed = True
82                removed_counter += 1
83            else:
84                self[key].bed_number = 9999
85        remaining = len(keys) - removed_counter
86
87        blocks_for_female = getattr(self,'blocks_for_female',[])
88        blocks_for_male = getattr(self,'blocks_for_male',[])
89        beds_for_fresh = getattr(self,'beds_for_fresh',[])
90        beds_for_pre = getattr(self,'beds_for_pre',[])
91        beds_for_returning = getattr(self,'beds_for_returning',[])
92        beds_for_final = getattr(self,'beds_for_final',[])
93        beds_for_all = getattr(self,'beds_for_all',[])
94        all_blocks = blocks_for_female + blocks_for_male
95        all_beds = (beds_for_pre + beds_for_fresh +
96            beds_for_returning + beds_for_final + beds_for_all)
97        floor_base = 100
98        if self.rooms_per_floor > 99:
99            floor_base = 1000
100        for block in all_blocks:
101            sex = 'male'
102            if block in blocks_for_female:
103                sex = 'female'
104            for floor in range(1,int(self.floors_per_block)+1):
105                for room in range(1,int(self.rooms_per_floor)+1):
106                    for bed in all_beds:
107                        room_nr = floor*floor_base + room
108                        bt = 'all'
109                        if bed in beds_for_fresh:
110                            bt = 'fr'
111                        elif bed in beds_for_pre:
112                            bt = 'pr'
113                        elif bed in beds_for_final:
114                            bt = 'fi'
115                        elif bed in beds_for_returning:
116                            bt = 're'
117                        bt = u'%s_%s_%s' % (self.special_handling,sex,bt)
118                        uid = u'%s_%s_%d_%s' % (
119                            self.hostel_id,block,room_nr,bed)
120                        if uid in self:
121                            bed = self[uid]
122                            # Renumber remaining bed
123                            bed.bed_number = len(self) + 1 - remaining
124                            remaining -= 1
125                            if bed.bed_type != bt:
126                                bed.bed_type = bt
127                                modified_counter += 1
128                                modified_beds += '%s, ' % uid
129                                notify(grok.ObjectModifiedEvent(bed))
130                        else:
131                            bed = createObject(u'waeup.Bed')
132                            bed.bed_id = uid
133                            bed.bed_type = bt
134                            bed.bed_number = len(self) + 1 - remaining
135                            bed.owner = NOT_OCCUPIED
136                            self.addBed(bed)
137                            added_counter +=1
138        return removed_counter, added_counter, modified_counter, modified_beds
139
140    def writeLogMessage(self, view, message):
141        ob_class = view.__implemented__.__name__.replace('waeup.kofa.','')
142        self.__parent__.logger.info(
143            '%s - %s - %s' % (ob_class, self.__name__, message))
144        return
145
146Hostel = attrs_to_fields(Hostel)
147
148class Bed(grok.Container):
149    """This is a bed.
150    """
151    grok.implements(IBed)
152    grok.provides(IBed)
153
154    @property
155    def coordinates(self):
156        """Determine the coordinates from the bed_id.
157        """
158        return self.bed_id.split('_')
159
160    # The following property attributes are only needed
161    # for the exporter to ease evaluation with Excel.
162
163    @property
164    def hall(self):
165        return self.coordinates[0]
166
167    @property
168    def block(self):
169        return self.coordinates[1]
170
171    @property
172    def room(self):
173        return self.coordinates[2]
174
175    @property
176    def bed(self):
177        return self.coordinates[3]
178
179    @property
180    def special_handling(self):
181        return self.bed_type.split('_')[0]
182
183    @property
184    def sex(self):
185        return self.bed_type.split('_')[1]
186
187    @property
188    def bt(self):
189        return self.bed_type.split('_')[2]
190
191
192    def bookBed(self, student_id):
193        if self.owner == NOT_OCCUPIED:
194            self.owner = student_id
195            notify(grok.ObjectModifiedEvent(self))
196            return None
197        else:
198            return self.owner
199
200    def switchReservation(self):
201        """Reserves or unreserve bed respectively.
202        """
203        sh, sex, bt = self.bed_type.split('_')
204        hostel_id, block, room_nr, bed = self.coordinates
205        hostel = self.__parent__
206        beds_for_fresh = getattr(hostel,'beds_for_fresh',[])
207        beds_for_pre = getattr(hostel,'beds_for_pre',[])
208        beds_for_returning = getattr(hostel,'beds_for_returning',[])
209        beds_for_final = getattr(hostel,'beds_for_final',[])
210        bed_string = u'%s_%s_%s' % (block, room_nr, bed)
211        if bt == 'reserved':
212            bt = 'all'
213            if bed in beds_for_fresh:
214                bt = 'fr'
215            elif bed in beds_for_pre:
216                bt = 'pr'
217            elif bed in beds_for_final:
218                bt = 'fi'
219            elif bed in beds_for_returning:
220                bt = 're'
221            bt = u'%s_%s_%s' % (sh, sex, bt)
222            message = _(u'unreserved')
223        else:
224            bt = u'%s_%s_reserved' % (sh, sex)
225            message = _(u'reserved')
226        self.bed_type = bt
227        notify(grok.ObjectModifiedEvent(self))
228        return message
229
230    def releaseBed(self):
231        """Release bed.
232        """
233        if self.owner == NOT_OCCUPIED:
234            return
235        old_owner = self.owner
236        self.owner = NOT_OCCUPIED
237        notify(grok.ObjectModifiedEvent(self))
238        accommodation_session = grok.getSite()[
239            'hostels'].accommodation_session
240        try:
241            bedticket = grok.getSite()['students'][old_owner][
242                          'accommodation'][str(accommodation_session)]
243        except KeyError:
244            return '%s without bed ticket' % old_owner
245        bedticket.bed = None
246        tz = getUtility(IKofaUtils).tzinfo
247        timestamp = now(tz).strftime("%Y-%m-%d %H:%M:%S %Z")
248        bedticket.bed_coordinates = u'-- booking cancelled on %s --' % (
249            timestamp,)
250        return old_owner
251
252    def releaseBedIfMaintenanceNotPaid(self, n=7):
253        """Release bed if maintenance fee has not been paid on time or
254        if bed ticket does not exist. Reserve bed so that it cannot be
255        automatically booked by someone else.
256        """
257        if self.owner == NOT_OCCUPIED:
258            return
259        accommodation_session = grok.getSite()[
260            'hostels'].accommodation_session
261        try:
262            bedticket = grok.getSite()['students'][self.owner][
263                          'accommodation'][str(accommodation_session)]
264        except KeyError:
265            old_owner = self.owner
266            self.owner = NOT_OCCUPIED
267            sh, sex, bt = self.bed_type.split('_')
268            bt = u'%s_%s_reserved' % (sh, sex)
269            self.bed_type = bt
270            notify(grok.ObjectModifiedEvent(self))
271            ##: Add logging message
272            return "%s wob" % old_owner # owner without bed ticket
273        if bedticket.maint_payment_made:
274            return
275        jetzt = datetime.utcnow()
276        days_ago = getattr(jetzt - bedticket.booking_date, 'days')
277        if days_ago >= n:
278            old_owner = self.owner
279            self.owner = NOT_OCCUPIED
280            sh, sex, bt = self.bed_type.split('_')
281            bt = u'%s_%s_reserved' % (sh, sex)
282            self.bed_type = bt
283            notify(grok.ObjectModifiedEvent(self))
284            bedticket.bed = None
285            tz = getUtility(IKofaUtils).tzinfo
286            timestamp = now(tz).strftime("%Y-%m-%d %H:%M:%S %Z")
287            bedticket.bed_coordinates = u'-- booking expired (%s) --' % (
288                timestamp,)
289            return old_owner
290        return
291
292    def writeLogMessage(self, view, message):
293        ob_class = view.__implemented__.__name__.replace('waeup.kofa.','')
294        self.__parent__.__parent__.logger.info(
295            '%s - %s - %s' % (ob_class, self.__name__, message))
296        return
297
298Bed = attrs_to_fields(Bed)
299
300class HostelFactory(grok.GlobalUtility):
301    """A factory for hostels.
302
303    We need this factory for the hostel processor.
304    """
305    grok.implements(IFactory)
306    grok.name(u'waeup.Hostel')
307    title = u"Create a new hostel.",
308    description = u"This factory instantiates new hostel instances."
309
310    def __call__(self, *args, **kw):
311        return Hostel()
312
313    def getInterfaces(self):
314        return implementedBy(Hostel)
315
316class BedFactory(grok.GlobalUtility):
317    """A factory for  beds.
318
319    We need this factory to ease customization.
320    """
321    grok.implements(IFactory)
322    grok.name(u'waeup.Bed')
323    title = u"Create a new bed.",
324    description = u"This factory instantiates new bed instances."
325
326    def __call__(self, *args, **kw):
327        return Bed()
328
329    def getInterfaces(self):
330        return implementedBy(Bed)
331
332class HostelsPlugin(grok.GlobalUtility):
333    """A plugin to update beds_catalog
334    """
335    grok.implements(IKofaPluggable)
336    grok.name('hostels')
337    log_prefix = 'HostelsPlugin'
338
339    def setup(self, site, name, logger):
340        return
341
342    def update(self, site, name, logger):
343        site_name = getattr(site, '__name__', '<Unnamed Site>')
344        nothing_to_do = True
345        # Add bed_id index
346        try:
347            cat = getUtility(ICatalog, name='beds_catalog')
348            if 'bed_id' not in cat.keys():
349                nothing_to_do = False
350                # replace original `updateIndex` method
351                def updateIndexReplacement(index):
352                    reindex_cat(cat)
353                cat._updateIndex = cat.updateIndex
354                cat.updateIndex = updateIndexReplacement
355                # setup catalog
356                cat[u'bed_id'] = FieldIndex(field_name=u'bed_id')
357                cat.updateIndex = cat._updateIndex  # undo changes
358                logger.info(
359                    '%s: bed_id index added to beds_catalog.'
360                    % self.log_prefix)
361                reindex_cat(cat)
362                logger.info(
363                    '%s: beds_catalog updated.'
364                    % self.log_prefix)
365        except ComponentLookupError: # in unit tests
366            pass
367        if nothing_to_do:
368            logger.info(
369                '%s: Updating site at %s: Nothing to do.' % (
370                    self.log_prefix, site_name,)
371                )
372        return
373
374
375@grok.subscribe(IBedTicket, grok.IObjectRemovedEvent)
376def handle_bedticket_removed(bedticket, event):
377    """If a bed ticket is deleted, we make sure that also the owner attribute
378    of the bed is cleared (set to NOT_OCCUPIED).
379    """
380    if bedticket.bed != None:
381        bedticket.bed.owner = NOT_OCCUPIED
382        notify(grok.ObjectModifiedEvent(bedticket.bed))
383
384@grok.subscribe(IBed, grok.IObjectRemovedEvent)
385def handle_bed_removed(bed, event):
386    """If a bed is deleted, we make sure that the bed object is
387    removed also from the owner's bed ticket.
388    """
389    if bed.owner == NOT_OCCUPIED:
390        return
391    accommodation_session = grok.getSite()['hostels'].accommodation_session
392    try:
393        bedticket = grok.getSite()['students'][bed.owner][
394                      'accommodation'][str(accommodation_session)]
395    except KeyError:
396        return
397    bedticket.bed = None
Note: See TracBrowser for help on using the repository browser.