Esempio n. 1
0
class _CostCenter(GenericModel, AccountingYearLinked, AgepolyEditableModel, SearchableModel):

    class MetaRightsAgepoly(AgepolyEditableModel.MetaRightsAgepoly):
        access = ['TRESORERIE', 'SECRETARIAT']

    name = models.CharField(_(u'Nom du centre de coût'), max_length=255, default='---')
    account_number = models.CharField(_(u'Numéro associé au centre de coût'), max_length=10)
    unit = FalseFK('units.models.Unit', verbose_name=_(u'Appartient à'))
    description = models.TextField(_('Description'), blank=True, null=True)

    class Meta:
        abstract = True
        unique_together = (("name", "accounting_year"), ("account_number", "accounting_year"))

    class MetaData:
        list_display = [
            ('account_number', _(u'Numéro')),
            ('name', _(u'Nom du centre de coût')),
            ('unit', _(u'Appartient à'))
        ]

        default_sort = "[1, 'asc']"  # account_number

        details_display = list_display + [('description', _(u'Description')), ('accounting_year', _(u'Année comptable'))]
        filter_fields = ('name', 'account_number', 'unit__name')

        base_title = _(u'Centres de coût')
        list_title = _(u'Liste des centres de coût')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-suitcase'

        menu_id = 'menu-compta-centrecouts'

        help_list = _(u"""Les centres de coût sont les différents comptes qui appartiennent aux unités de l'AGEPoly (commissions, équipes, sous-commissions, Comité de Direction).""")

    class MetaAccounting:
        copiable = True

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u"centre cout"

        fields = [
            'name',
            'account_number',
            'description',
        ]

    def __str__(self):
        return u"{} - {}".format(self.account_number, self.name)

    def genericFormExtraClean(self, data, form):
        """Check that unique_together is fulfiled"""
        from accounting_core.models import CostCenter

        if CostCenter.objects.exclude(pk=self.pk).filter(accounting_year=get_current_year(form.truffe_request), name=data['name']).count():
            raise forms.ValidationError(_(u'Un centre de coûts avec ce nom existe déjà pour cette année comptable.'))  # Potentiellement parmi les supprimées

        if CostCenter.objects.exclude(pk=self.pk).filter(accounting_year=get_current_year(form.truffe_request), account_number=data['account_number']).count():
            raise forms.ValidationError(_(u'Un centre de coûts avec ce numéro de compte existe déjà pour cette année comptable.'))  # Potentiellement parmi les supprimées
Esempio n. 2
0
class _Card(GenericModel, AgepolyEditableModel, SearchableModel):
    class MetaRightsAgepoly(AgepolyEditableModel.MetaRightsAgepoly):
        access = ['LOGISTIQUE', 'SECRETARIAT']
        world_ro_access = False

    provider = FalseFK('vehicles.models.Provider',
                       verbose_name=_('Fournisseur'))
    name = models.CharField(_('Nom'), max_length=255)
    number = models.CharField(_(u'Numéro'), max_length=255)
    description = models.TextField(_('Description'))
    exclusif = models.BooleanField(
        _('Usage exclusif'),
        default=True,
        help_text=_(
            u'Ne peut pas être utilisé plusieurs fois en même temps ?'))

    class MetaData:
        list_display = [
            ('name', _(u'Nom')),
            ('provider', _(u'Fournisseur')),
            ('number', _(u'Numéro')),
        ]
        details_display = list_display + [('description', _(u'Description')),
                                          ('exclusif', _(u'Usage exclusif'))]

        default_sort = "[1, 'asc']"  # name

        yes_or_no_fields = ['exclusif']
        filter_fields = ('name', 'number', 'description', 'provider__name')

        base_title = _(u'Cartes')
        list_title = _(u'Liste des cartes')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-credit-card'

        menu_id = 'menu-vehicles-cards'

        help_list = _(
            u"""Les différentes cartes utilisées pour les réservations""")

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u'mobility véhicule'

        fields = [
            'name',
            'description',
            'provider',
            'number',
        ]

    class Meta:
        abstract = True

    def __unicode__(self):
        return u'{} ({})'.format(self.name, self.number)
Esempio n. 3
0
class _VehicleType(GenericModel, AgepolyEditableModel, SearchableModel):
    class MetaRightsAgepoly(AgepolyEditableModel.MetaRightsAgepoly):
        access = ['LOGISTIQUE', 'SECRETARIAT']
        world_ro_access = False

    provider = FalseFK('vehicles.models.Provider',
                       verbose_name=_('Fournisseur'))
    name = models.CharField(_('Nom'), max_length=255)
    description = models.TextField(_('Description'))

    class MetaData:
        list_display = [
            ('name', _(u'Nom')),
            ('provider', _(u'Fournisseur')),
        ]
        details_display = list_display + [
            ('description', _(u'Description')),
        ]

        default_sort = "[1, 'asc']"  # name

        filter_fields = ('name', 'description', 'provider__name')

        base_title = _(u'Types de véhicule')
        list_title = _(u'Liste des types de véhicules')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-truck'

        menu_id = 'menu-vehicles-type'

        help_list = _(
            u"""Les différents types de véhicules, par fournisseur""")

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u'mobility véhicule'

        fields = [
            'name',
            'description',
            'provider',
        ]

    class Meta:
        abstract = True

    def __unicode__(self):
        return self.name
Esempio n. 4
0
class _AccessDelegation(GenericModel, UnitEditableModel):
    unit = FalseFK('units.models.Unit')

    access = MultiSelectField(choices=_Role.ACCESS_CHOICES,
                              blank=True,
                              null=True)
    valid_for_sub_units = models.BooleanField(
        _(u'Valide pour les sous-unités'),
        default=False,
        help_text=
        _(u'Si sélectionné, les accès supplémentaires dans l\'unité courante seront aussi valides dans les sous-unités'
          ))

    user = models.ForeignKey(
        TruffeUser,
        blank=True,
        null=True,
        help_text=
        _(u'(Optionnel !) L\'utilisateur concerné. L\'utilisateur doit disposer d\'une accréditation dans l\'unité.'
          ))
    role = FalseFK('units.models.Role',
                   blank=True,
                   null=True,
                   help_text=_(u'(Optionnel !) Le rôle concerné.'))

    class MetaRightsUnit(UnitEditableModel.MetaRightsUnit):
        unit_ro_access = True
        access = 'ACCREDITATION'

    class MetaData:
        list_display = [('get_display_list', ''), ('user', _('Utilisateur')),
                        ('role', _(u'Rôle')), ('get_access', _(u'Accès'))]

        details_display = [('user', _('Utilisateur')), ('role', _('Rôle')),
                           ('get_access', _(u'Accès supplémentaires')),
                           ('valid_for_sub_units',
                            _(u'Valide pour les sous-unités'))]

        default_sort = "[0, 'asc']"  # id

        filter_fields = ('user__first_name', 'user__last_name',
                         'user__username', 'role__name', 'access')
        not_sortable_columns = [
            'get_display_list',
        ]

        base_title = _(u'Délégation d\'accès')
        list_title = _(u'Liste de toutes les délégations d\'accès')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-group'

        menu_id = 'menu-units-delegations'

        yes_or_no_fields = ['valid_for_sub_units']

        has_unit = True

        help_list = _(
            u"""Les délégations d'accès permettent de donner des accès supplémentaires dans une unité.

Les accès sont normalement déterminés en fonction des accréditations, au niveau global.
Par exemple, une personne accréditée en temps que 'Trésorier' dans une unité disposera de l'accès TRESORERIE pour l'unité.

Avec les délégations d'accês, il est par exemple possible de donner l'accès "COMMUNICATION" à tout les membres d'une unité en créant une délégations d'accès.

Il est aussi possible de restreindre une délégation â un utilisateur ou à un rôle particulier."""
        )

    class Meta:
        abstract = True

    def get_access(self):
        if self.access:
            return u', '.join(list(self.access))

    def __str__(self):
        return _(u'Accês supplémentaire n°%s' % (self.pk, ))

    def delete_signal(self, request):
        self.save_signal()

    def save_signal(self):
        """Cleanup rights"""

        for user in self.unit.get_users():
            user.clear_rights_cache()

    def get_display_list(self):
        return _(u'Délégation #{}'.format(self.pk))
Esempio n. 5
0
class _Supply(GenericModel, GenericGroupsModel, UnitEditableModel,
              GenericDelayValidableInfo, SearchableModel):
    class MetaRightsUnit(UnitEditableModel.MetaRightsUnit):
        access = 'LOGISTIQUE'

    title = models.CharField(_('Titre'), max_length=255)
    description = models.TextField()
    unit = FalseFK('units.models.Unit')

    quantity = models.PositiveIntegerField(
        _(u'Quantité'),
        help_text=_(u'Le nombre de fois que tu as l\'objet à disposition'),
        default=1)
    price = models.PositiveIntegerField(
        _(u'Prix'),
        help_text=
        _(u'Le prix en CHF de remplacement du matériel, en cas de casse ou de perte'
          ),
        default=0)

    active = models.BooleanField(
        _('Actif'),
        help_text=_(
            u'Pour désactiver temporairement la posibilité de réserver.'),
        default=True)

    conditions = models.TextField(
        _(u'Conditions de réservation'),
        help_text=
        _(u'Si tu veux préciser les conditions de réservations pour le matériel. Tu peux par exemple mettre un lien vers un contrat.'
          ),
        blank=True)

    allow_externals = models.BooleanField(
        _(u'Autoriser les externes'),
        help_text=
        _(u'Permet aux externes (pas dans l\'AGEPoly) de réserver le matériel.'
          ),
        default=False)
    conditions_externals = models.TextField(
        _(u'Conditions de réservation pour les externes'),
        help_text=
        _(u'Si tu veux préciser des informations sur la réservation du matériel pour les externes. Remplace le champ \'Conditions\' pour les externe si rempli.'
          ),
        blank=True)

    allow_calendar = models.BooleanField(
        _(u'Autoriser tout le monde à voir le calendrier'),
        help_text=
        _(u'Permet à tout le monde d\'afficher le calendrier des réservations du matériel'
          ),
        default=True)
    allow_external_calendar = models.BooleanField(
        _(u'Autoriser les externes à voir le calendrier'),
        help_text=
        _(u'Permet aux externes d\'afficher le calendrier des réservations du matériel. Le calendrier doit être visible.'
          ),
        default=True)

    class MetaData:
        list_display = [
            ('title', _('Titre')),
            ('price', _('Prix')),
            ('active', _('Actif')),
            ('allow_externals', _('Autoriser les externes')),
        ]

        details_display = list_display + [
            ('quantity', _(u'Quantité')),
            ('description', _('Description')),
            ('conditions', _('Conditions')),
            ('conditions_externals', _('Conditions pour les externes')),
            ('max_days', _(u'Nombre maximum de jours de réservation')),
            ('max_days_externals',
             _(u'Nombre maximum de jours de réservation (externes)')),
            ('minimum_days_before',
             _(u'Nombre de jours minimum avant réservation')),
            ('minimum_days_before_externals',
             _(u'Nombre de jours minimum avant réservation (externes)')),
            ('maximum_days_before',
             _(u'Nombre de jours maximum avant réservation')),
            ('maximum_days_before_externals',
             _(u'Nombre de jours maximum avant réservation (externes)')),
        ]
        filter_fields = ('title', 'description', 'conditions',
                         'conditions_externals')

        base_title = _(u'Matériel')
        list_title = _(u'Liste de tout le matériel réservable')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-umbrella'

        default_sort = "[1, 'asc']"  # title

        menu_id = 'menu-logistics-supply'

        yes_or_no_fields = ['active', 'allow_externals']
        html_fields = ('description', 'conditions', 'conditions_externals')

        has_unit = True

        help_list = _(
            u"""La liste du matériel réservable, gérés par l'unité active.

N'importe quelle unité peut mettre à disposition du matériel et est responsable de la modération des réservations."""
        )

    class MetaEdit:
        html_fields = ('description', 'conditions', 'conditions_externals')

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u""

        fields = [
            'title',
            'description',
            'conditions',
        ]

    class Meta:
        abstract = True

    def __str__(self):
        return self.title
Esempio n. 6
0
class _RoomReservation(GenericModel, GenericDelayValidable,
                       GenericGroupsValidableModel, GenericGroupsModel,
                       GenericContactableModel, GenericStateUnitValidable,
                       GenericStateModel, GenericExternalUnitAllowed,
                       UnitExternalEditableModel, SearchableModel):
    class MetaRightsUnit(UnitExternalEditableModel.MetaRightsUnit):
        access = 'LOGISTIQUE'
        moderation_access = 'LOGISTIQUE'

    room = FalseFK('logistics.models.Room', verbose_name=_('Salle'))

    title = models.CharField(_('Titre'), max_length=255)

    start_date = models.DateTimeField(_(u'Date de début'))
    end_date = models.DateTimeField(_(u'Date de fin'))

    reason = models.TextField(
        _('Raison'), help_text=_(u'Explique brièvement ce que tu vas faire'))
    remarks = models.TextField(_('Remarques'), blank=True, null=True)

    class MetaData:

        list_display_base = [
            ('title', _('Titre')),
            ('get_unit_name', _(u'Nom de l\'unité')),
            ('start_date', _(u'Date début')),
            ('end_date', _('Date fin')),
            ('status', _('Statut')),
        ]

        list_display = [list_display_base[0]] + [
            ('room', _(u'Salle')),
        ] + list_display_base[1:]
        list_display_related = [list_display_base[0]] + [
            ('get_room_link', _(u'Salle')),
        ] + list_display_base[1:] + [
            ('get_conflits_list', _(u'Conflits')),
        ]

        forced_widths = {
            '1': '15%',
            '2': '25%',
            '4': '150px',  # start date
            '5': '150px',  # end date
        }

        forced_widths_related = {
            '1': '15%',
            '2': '25%',
            '4': '90px',  # start date on two lines
            '5': '90px',  # end date on two lines
            '7': '80px',  # conflicts list nicely wide
        }

        details_display = list_display_base + [('get_room_infos', _('Salle')),
                                               ('reason', _('Raison')),
                                               ('remarks', _('Remarques')),
                                               ('get_conflits', _('Conflits'))]
        filter_fields = ('title', 'status', 'room__title')

        base_title = _(u'Réservation de salle')
        list_title = _(u'Liste de toutes les réservations de salles')
        list_related_title = _(
            u'Liste de toutes les réservations des salles de mon unité')
        calendar_title = _(u'Calendrier de mes réservations de salles')
        calendar_related_title = _(
            u'Calendrier des réservations des salles de mon unité')
        calendar_specific_title = _(u'Calendrier des réservations de la salle')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-hospital'

        safe_fields = ['get_unit_name', 'get_room_link', 'get_conflits_list']

        default_sort = "[4, 'desc']"  # end_date

        menu_id = 'menu-logistics-room-reservation'
        menu_id_related = 'menu-logistics-room-reservation-related'
        menu_id_calendar = 'menu-logistics-room-reservation-calendar'
        menu_id_calendar_related = 'menu-logistics-room-reservation-calendar-related'
        menu_id_directory = 'menu-logistics-room-reservation-directory'

        has_unit = True

        html_fields = ('get_room_infos', 'get_conflits')
        datetime_fields = ('start_date', 'end_date')

        related_name = _('Salle')

        help_list = _(u"""Les réservation de salles.

Les réservations sont soumises à modération par l'unité liée à la salle.

Tu peux gérer ici la liste de tes réservations pour l'unité active (ou une unité externe)."""
                      )

        help_list_related = _(u"""Les réservation des salles de l'unité.

Les réservations sont soumises à modération par l'unité liée à la salle.

Tu peux gérer ici la liste de réservation des salles de l'unité active.""")

        help_calendar_specific = _(
            u"""Les réservation d'un type de salle particulier.""")

        trans_sort = {
            'get_unit_name': 'unit__name',
            'get_room_link': 'room__title'
        }
        not_sortable_columns = [
            'get_conflits_list',
        ]

    class MetaEdit:
        datetime_fields = ('start_date', 'end_date')

        only_if = {
            'remarks':
            lambda obj, user: obj.status == '2_online' and obj.rights_can(
                'VALIDATE', user),
            'room':
            lambda obj, user: obj.status == '0_draft',
        }

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u""

        fields = [
            'room',
            'title',
            'reason',
            'remarks',
        ]

    class Meta:
        abstract = True

    class MetaState(GenericStateUnitValidable.MetaState):
        unit_field = 'room.unit'
        linked_model = 'logistics.models.Room'

    def __str__(self):
        return self.title

    def genericFormExtraClean(self, data, form):
        """Check if select room is available"""

        from django import forms

        if 'room' in form.fields:

            if 'room' not in data or not data['room'].active or data[
                    'room'].deleted:
                raise forms.ValidationError(_('Salle non disponible'))

            if not self.unit and not data['room'].allow_externals:
                raise forms.ValidationError(_('Salle non disponible'))

        if 'start_date' in data and 'end_date' in data and data[
                'start_date'] > data['end_date']:
            raise forms.ValidationError(
                _(u'La date de fin ne peut pas être avant la date de début !'))

    def get_room_infos(self):
        """Affiche les infos sur la salle pour une réservation"""

        tpl = mark_safe(
            u'<div style="margin-top: 5px;">{}, {} <span class="label label-info">{}</span></div>'
            .format(escape(self.room.title), _(u'gérée par'),
                    escape(self.room.unit.name)))

        return tpl

    def get_conflits(self):

        liste = self.room.roomreservation_set.exclude(pk=self.pk).exclude(
            deleted=True).filter(status__in=['1_asking', '2_online'],
                                 end_date__gt=self.start_date,
                                 start_date__lt=self.end_date)

        if not liste:
            return mark_safe(
                u'<span class="txt-color-green"><i class="fa fa-check"></i> {}</span>'
                .format(_(u'Pas de conflits !')))
        else:
            retour = u'<span class="txt-color-red"><i class="fa fa-warning"></i> {}</span><ul>'.format(
                _(u'Il y a d\'autres réservations en même temps !'))

            for elem in liste:
                retour = u'{}<li><span class="label label-{}"><i class="{}"></i>'.format(
                    retour, elem.status_color(), elem.status_icon())
                retour = u'{} {}</span> {} pour l\'unité {}'.format(
                    retour, elem.get_status_display(), elem,
                    elem.get_unit_name())
                retour = u'{}<span data-toggle="tooltip" data-placement="right" title="Du {} au {}"> <i class="fa fa-clock-o"></i></span></li>'.format(
                    retour, localtime(elem.start_date),
                    localtime(elem.end_date))

            retour = u'{}</ul>'.format(retour)

            return retour

    def get_room_link(self):
        return u'<a href="{}">{}</a>'.format(
            reverse('logistics-views-room_show', args=(self.room.pk, )),
            self.room)

    def get_conflits_list(self):

        liste = self.room.roomreservation_set.exclude(pk=self.pk).exclude(
            deleted=True).filter(status__in=['1_asking', '2_online'],
                                 end_date__gt=self.start_date,
                                 start_date__lt=self.end_date)

        if not liste:
            return u'<span class="txt-color-green"><i class="fa fa-check"></i></span>'
        else:

            retour = u'<ul>'

            for elem in liste:
                unit = escape(elem.unit) if elem.unit else escape(
                    elem.unit_blank_name)

                retour = u'{}<li><span>{} ({}) [{}]<br>'.format(
                    retour, escape(elem), unit, elem.get_status_display())
                retour = u'{}du {}<br>au {}</span></li>'.format(
                    retour, localtime(elem.start_date),
                    localtime(elem.end_date))

            retour = u'{}</ul>'.format(retour)

            return u'<span class="txt-color-red conflicts-tooltip-parent" rel="tooltip" data-html="true" title="{}"><i class="fa fa-warning"></i></span>'.format(
                retour)
Esempio n. 7
0
class _AgepSlide(GenericModel, GenericGroupsModerableModel, GenericGroupsModel,
                 GenericContactableModel, GenericStateRootModerable,
                 GenericStateModel, UnitEditableModel, SearchableModel):
    class MetaRightsUnit(UnitEditableModel.MetaRightsUnit):
        access = 'COMMUNICATION'
        moderation_access = 'COMMUNICATION'

    title = models.CharField(_(u'Titre'), max_length=255)
    picture = models.ImageField(
        _(u'Image'),
        help_text=
        _(u'Pour des raisons de qualité, il est fortement recommandé d\'envoyer une image en HD (1920x1080)'
          ),
        upload_to='uploads/slides/')
    unit = FalseFK('units.models.Unit')

    start_date = models.DateTimeField(_(u'Date de début'),
                                      blank=True,
                                      null=True)
    end_date = models.DateTimeField(_(u'Date de fin'), blank=True, null=True)

    class MetaData:
        list_display = [
            ('title', _('Titre')),
            ('start_date', _(u'Date début')),
            ('end_date', _('Date fin')),
            ('status', _('Statut')),
        ]
        details_display = list_display + [('picture', _('Image')),
                                          ('get_image_warning', '')]
        filter_fields = ('title', 'status')

        base_title = _(u'Slide à l\'AGEPoly')
        list_title = _(u'Liste de tous les slides à l\'AGEPoly')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-bullhorn'

        default_sort = "[3, 'desc']"  # end_date

        menu_id = 'menu-communication-agepslide'

        datetime_fields = ['start_date', 'end_date']
        images_fields = [
            'picture',
        ]
        safe_fields = [
            'get_image_warning',
        ]

        has_unit = True

        help_list = _(
            u"""Les slides à l'AGEPoly sont affichés de manière aléatoire sur les écrans à l'AGEPoly.
            Ils sont soumis à modération par le responsable communication de l'AGEPoly avant d'être visibles."""
        )

    class MetaEdit:
        datetime_fields = ('start_date', 'end_date')

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u"écrans"

        fields = [
            'title',
        ]

    class Meta:
        abstract = True

    def __unicode__(self):
        return self.title

    def get_image_warning(self):
        if self.picture.height < 1080 or self.picture.width < 1920:
            return _(
                u'<span class="text-warning"><i class="fa fa-warning"></i> Les dimensions de l\'image sont trop petites ! ({}x{} contre 1920x1080 recommandé)'
                .format(self.picture.width, self.picture.height))
Esempio n. 8
0
class _Display(GenericModel, GenericGroupsModel, UnitEditableModel,
               GenericDelayValidableInfo, SearchableModel):
    class MetaRightsUnit(UnitEditableModel.MetaRightsUnit):
        access = 'COMMUNICATION'

    title = models.CharField(_('Titre'), max_length=255)
    description = models.TextField()
    unit = FalseFK('units.models.Unit')

    active = models.BooleanField(
        _('Actif'),
        help_text=_(
            u'Pour désactiver temporairement la posibilité de réserver.'),
        default=True)

    conditions = models.TextField(
        _(u'Conditions de réservation'),
        help_text=
        _(u'Si tu veux préciser les conditions de réservations pour l\'affichage. Tu peux par exemple mettre un lien vers un contrat.'
          ),
        blank=True)

    allow_externals = models.BooleanField(
        _(u'Autoriser les externes'),
        help_text=
        _(u'Permet aux externes (pas dans l\'AGEPoly) de réserver l\'affichage.'
          ),
        default=False)
    conditions_externals = models.TextField(
        _(u'Conditions de réservation pour les externes'),
        help_text=
        _(u'Si tu veux préciser des informations sur la réservation de l\'affichage pour les externes. Remplace le champ \'Conditions\' pour les externe si rempli.'
          ),
        blank=True)

    allow_calendar = models.BooleanField(
        _(u'Autoriser tout le monde à voir le calendrier'),
        help_text=
        _(u'Permet à tout le monde d\'afficher le calendrier des réservations de l\'affichage'
          ),
        default=True)
    allow_external_calendar = models.BooleanField(
        _(u'Autoriser les externes à voir le calendrier'),
        help_text=
        _(u'Permet aux externes d\'afficher le calendrier des réservations de l\'affichage. Le calendrier doit être visible.'
          ),
        default=True)

    class MetaData:
        list_display = [
            ('title', _('Titre')),
            ('active', _('Actif')),
            ('allow_externals', _('Autoriser les externes')),
        ]

        details_display = list_display + [
            ('description', _('Description')),
            ('conditions', _('Conditions')),
            ('conditions_externals', _('Conditions pour les externes')),
            ('max_days', _(u'Nombre maximum de jours de réservation')),
            ('max_days_externals',
             _(u'Nombre maximum de jours de réservation (externes)')),
            ('minimum_days_before',
             _(u'Nombre de jours minimum avant réservation')),
            ('minimum_days_before_externals',
             _(u'Nombre de jours minimum avant réservation (externes)')),
            ('maximum_days_before',
             _(u'Nombre de jours maximum avant réservation')),
            ('maximum_days_before_externals',
             _(u'Nombre de jours maximum avant réservation (externes)')),
        ]
        filter_fields = ('title', 'description', 'conditions',
                         'conditions_externals')

        base_title = _(u'Affichage')
        list_title = _(u'Liste de tout les affichages réservables')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-clipboard'

        default_sort = "[1, 'asc']"  # title

        menu_id = 'menu-communication-display'

        yes_or_no_fields = ['active', 'allow_externals']
        html_fields = ('description', 'conditions', 'conditions_externals')

        has_unit = True

        help_list = _(
            u"""La liste des affichages réservables, gérés par l'unité active. N'importe quelle unité peut mettre à disposition des affichages et est responsable de la modération des réservations."""
        )

    class MetaEdit:
        html_fields = ('description', 'conditions', 'conditions_externals')

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u"display"

        fields = [
            'title',
            'description',
            'conditions',
        ]

    class Meta:
        abstract = True

    def __unicode__(self):
        return self.title
Esempio n. 9
0
class _AccountingError(GenericModel, GenericStateModel, AccountingYearLinked, CostCenterLinked, GenericGroupsModel, AccountingGroupModels, GenericContactableModel, UnitEditableModel, SearchableModel):

    class MetaRightsUnit(UnitEditableModel.MetaRightsUnit):
        access = ['TRESORERIE', 'SECRETARIAT']
        world_ro_access = False

    class MetaRights(UnitEditableModel.MetaRights):
        linked_unit_property = 'costcenter.unit'

    linked_line = FalseFK('accounting_main.models.AccountingLine', verbose_name=_(u'Ligne liée'), blank=True, null=True)
    linked_line_cache = models.CharField(max_length=4096)

    initial_remark = models.TextField(_(u'Remarque initiale'), help_text=_(u'Décrit le problème'))

    class Meta:
        abstract = True

    class MetaEdit:
        pass

    class MetaGroups(AccountingGroupModels.MetaGroups):
        pass

    class MetaData:
        list_display = [
            ('get_line_title', _(u'Erreur')),
            ('costcenter', _(u'Centre de coûts')),
            ('get_linked_line', _(u'Ligne')),
            ('status', _(u'Statut')),
        ]

        default_sort = "[0, 'desc']"  # pk
        filter_fields = ('linked_line__text', 'linked_line_cache')

        details_display = list_display + [
            ('initial_remark', _(u'Remarque initiale')),
        ]

        base_title = _(u'Erreurs')
        list_title = _(u'Liste des erreurs de la comptabilité')
        base_icon = 'fa fa-list-ol'
        elem_icon = 'fa fa-warning'

        menu_id = 'menu-compta-errors'
        not_sortable_columns = ['get_line_title', 'costcenter']
        trans_sort = {'get_linked_line': 'linked_line_cache'}
        safe_fields = []

        has_unit = True

        help_list = _(u"""Les erreurs signalées dans la compta de l'AGEPoly.""")

    class MetaState:

        states = {
            '0_drafting': _(u'Établisement du problème'),
            '1_fixing': _(u'En attente de correction'),
            '2_fixed': _(u'Correction effectuée'),
        }

        default = '0_drafting'

        states_texts = {
            '0_drafting': _(u'L\'erreur a été signalée, les détails sont en cours d\'élaboration.'),
            '1_fixing': _(u'L\'erreur a été déterminée et une correction est en attente.'),
            '2_fixed': _(u'L\'erreur a été corrigée.'),
        }

        states_links = {
            '0_drafting': ['1_fixing', '2_fixed'],
            '1_fixing': ['0_drafting', '2_fixed'],
            '2_fixed': ['1_fixing'],
        }

        states_colors = {
            '0_drafting': 'warning',
            '1_fixing': 'danger',
            '2_fixed': 'success',
        }

        states_icons = {
        }

        list_quick_switch = {
            '0_drafting': [('1_fixing', 'fa fa-warning', _(u'Marquer comme \'En attente de correction\'')), ('2_fixed', 'fa fa-check', _(u'Marquer comme corrigé')), ],
            '1_fixing': [('2_fixed', 'fa fa-check', _(u'Marquer comme corrigé')), ],
            '2_fixed': [('1_fixing', 'fa fa-warning', _(u'Marquer comme \'En attente de correction\'')), ],
        }

        states_default_filter = '0_drafting,1_fixing'
        status_col_id = 4

        forced_pos = {
            '0_drafting': (0.1, 0.25),
            '1_fixing': (0.5, 0.75),
            '2_fixed': (0.9, 0.25),
        }

        class FormFixed(Form):
            fix_errors = BooleanField(label=_(u'Mettre la ligne liée comme valide'), help_text=_(u'Met automatiquement la ligne liée comme valide. Attention, la ligne n\'est plus forcément liée !'), required=False, initial=True)

        states_bonus_form = {
            '2_fixed': FormFixed
        }

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u"compta"

        fields = [
            'initial_remark',
            'get_line_title',
        ]

    def may_switch_to(self, user, dest_state):

        return super(_AccountingError, self).rights_can_EDIT(user) and super(_AccountingError, self).may_switch_to(user, dest_state)

    def can_switch_to(self, user, dest_state):

        if not super(_AccountingError, self).rights_can_EDIT(user):
            return (False, _('Pas les droits.'))

        return super(_AccountingError, self).can_switch_to(user, dest_state)

    def rights_can_EDIT(self, user):
        if self.status == '2_fixed':
            return False  # Never !

        return super(_AccountingError, self).rights_can_EDIT(user)

    def rights_can_ADD_COMMENT(self, user):
        return super(_AccountingError, self).rights_can_EDIT(user)

    def rights_can_DISPLAY_LOG(self, user):
        """Always display log, even if current state dosen't allow edit"""
        return super(_AccountingError, self).rights_can_EDIT(user)

    def __str__(self):
        return u'Erreur: {}'.format(self.get_linked_line())

    def genericFormExtraInit(self, form, current_user, *args, **kwargs):
        del form.fields['linked_line_cache']
        del form.fields['linked_line']

    def get_linked_line(self):
        if self.linked_line:
            return self.linked_line.__str__()
        elif self.linked_line_cache:
            return u'{} (Cache)'.format(self.linked_line_cache)
        else:
            return _(u'(Aucune ligne liée)')

    def save(self, *args, **kwargs):

        if not self.linked_line_cache and self.linked_line:
            self.linked_line_cache = self.linked_line.__str__()

        return super(_AccountingError, self).save(*args, **kwargs)

    def __init__(self, *args, **kwargs):
        super(_AccountingError, self).__init__(*args, **kwargs)

        self.MetaRights = type("MetaRights", (self.MetaRights,), {})
        self.MetaRights.rights_update({
            'ADD_COMMENT': _(u'Peut ajouter un commentaire'),
        })

        self.MetaGroups.groups_update({
            'compta_everyone_with_messages': _(u'Toutes les personnes liées via la compta (Admin et secrétaires AGEP, trésorier unité, éditeurs de l\'objet) + les personnes ayant commenté'),
        })

    def get_line_title(self):
        return _(u'Erreur #{} du {} signalée par {}'.format(self.pk, str(self.get_creation_date())[:10], self.get_creator()))

    def get_messages(self):
        return self.accountingerrormessage_set.order_by('when')

    def switch_status_signal(self, request, old_status, dest_status):

        s = super(_AccountingError, self)

        if hasattr(s, 'switch_status_signal'):
            s.switch_status_signal(request, old_status, dest_status)

        if dest_status == '2_fixed':

            if request.POST.get('fix_errors'):

                if self.linked_line and not self.linked_line.deleted and self.linked_line.status == '2_error':

                    from accounting_main.models import AccountingLineLogging

                    old_status = self.linked_line.status
                    self.linked_line.status = '1_validated'
                    self.linked_line.save()

                    AccountingLineLogging(who=request.user, what='state_changed', object=self.linked_line, extra_data=json.dumps({'old': str(self.linked_line.MetaState.states.get(old_status)), 'new': str(self.linked_line.MetaState.states.get('1_validated'))})).save()

                    unotify_people(u'AccountingLine.{}.error'.format(self.costcenter.unit), self.linked_line)
                    notify_people(request, u'AccountingLine.{}.fixed'.format(self.costcenter.unit), 'accounting_line_fixed', self.linked_line, self.linked_line.build_group_members_for_compta_everyone())

            unotify_people(u'AccountingError.{}.created'.format(self.costcenter.unit), self)
            notify_people(request, u'AccountingError.{}.fixed'.format(self.costcenter.unit), 'accounting_error_fixed', self, self.build_group_members_for_compta_everyone_with_messages())

    def create_signal(self, request):
        notify_people(request, u'AccountingError.{}.created'.format(self.costcenter.unit), 'accounting_error_created', self, self.build_group_members_for_compta_everyone_with_messages())

    def build_group_members_for_compta_everyone_with_messages(self):

        retour = self.build_group_members_for_compta_everyone()

        for message in self.accountingerrormessage_set.all():
            if message.author not in retour:
                retour.append(message.author)

        return retour
Esempio n. 10
0
class _WebsiteNews(GenericModel, GenericGroupsModerableModel,
                   GenericGroupsModel, GenericContactableModel,
                   GenericStateRootModerable, GenericStateModel,
                   UnitEditableModel, SearchableModel):
    class MetaRightsUnit(UnitEditableModel.MetaRightsUnit):
        access = 'COMMUNICATION'
        moderation_access = 'COMMUNICATION'

    title = models.CharField(_(u'Titre'), max_length=255)
    title_en = models.CharField(_(u'Titre anglais'),
                                max_length=255,
                                blank=True,
                                null=True)
    content = models.TextField(_(u'Contenu'))
    content_en = models.TextField(_(u'Contenu anglais'), blank=True, null=True)
    url = models.URLField(max_length=255)
    unit = FalseFK('units.models.Unit')

    start_date = models.DateTimeField(_(u'Date début'), blank=True, null=True)
    end_date = models.DateTimeField(_(u'Date fin'), blank=True, null=True)

    class MetaData:
        list_display = [
            ('title', _('Titre')),
            ('start_date', _(u'Date début')),
            ('end_date', _('Date fin')),
            ('status', _('Statut')),
        ]
        details_display = list_display + [('content', _('Contenu')),
                                          ('url', _('URL')),
                                          ('title_en', _('Titre anglais')),
                                          ('content_en', _('Contenu anglais'))]
        filter_fields = ('title', 'status')

        base_title = _('News AGEPoly')
        list_title = _(u'Liste de toutes les news sur le site de l\'AGEPoly')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-bullhorn'

        default_sort = "[3, 'desc']"  # end_date

        menu_id = 'menu-communication-websitenews'

        datetime_fields = ['start_date', 'end_date']

        has_unit = True

        help_list = _(
            u"""Les news du site de l'AGEPoly sont les nouvelles affichées sur toutes les pages du site de l'AGEPoly.
                        Elles sont soumises à modération par le responsable communication de l'AGEPoly avant d'être visibles."""
        )

    class MetaEdit:
        datetime_fields = ('start_date', 'end_date')

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u""

        fields = [
            'title',
            'title_en',
            'content',
            'content_en',
            'url',
        ]

    class Meta:
        abstract = True

    def __unicode__(self):
        return self.title
Esempio n. 11
0
class _Link(GenericModel, UnitEditableModel, SearchableModel):
    class MetaRightsUniyt(UnitEditableModel.MetaRightsUnit):
        access = ['PRESIDENCE', 'INFORMATIQUE', 'COMMUNICATION']
        world_ro_access = False

    title = models.CharField(_(u'Titre'), max_length=255)
    description = models.TextField(_(u'Description'), blank=True, null=True)
    url = models.URLField()

    unit = FalseFK('units.models.Unit')

    LEFTMENU_CHOICES = (
        ('/main/top', _(u'Principal / En haut')),
        ('/main/bottom', _(u'Principal / En bas')),
        ('/admin/', _(u'Admin')),
        ('/gens/', _(u'Gens')),
        ('/communication/', _(u'Communication')),
        ('/logistics/', _(u'Logistique')),
        ('/logistics/vehicles', _(u'Logistique / Véhicules')),
        ('/logistics/rooms', _(u'Logistique / Salles')),
        ('/logistics/supply', _(u'Logistique / Matériel')),
        ('/units/', _(u'Unités et Accreds')),
        ('/accounting/', _(u'Finances')),
        ('/accounting/accounting', _(u'Finances / Compta')),
        ('/accounting/tools', _(u'Finances / Outils')),
        ('/accounting/proofs', _(u'Finances / Justifications')),
        ('/accounting/gestion', _(u'Finances / Gestion')),
        ('/cs/', _(u'Informatique')),
        ('/misc/', _(u'Divers')),
    )

    leftmenu = models.CharField(
        _(u'Position dans le menu de gauche'),
        max_length=128,
        choices=LEFTMENU_CHOICES,
        blank=True,
        null=True,
        help_text=
        _(u'Laisser blanc pour faire un lien normal. Réservé au comité de l\'AGEPoly. Attention, cache de 15 minutes !'
          ))
    icon = models.CharField(_(u'Icone FontAwesome'),
                            max_length=128,
                            default='fa-external-link-square')

    class MetaData:
        list_display = [
            ('title', _('Titre')),
            ('get_url', _(u'URL')),
        ]
        details_display = list_display + [
            ('description', _('Description')),
            ('get_leftmenu_display', _('Menu de gauche')),
            ('icon', _('Icone')),
        ]
        filter_fields = ('title', 'url', 'description')
        safe_fields = ['get_url']

        default_sort = "[1, 'asc']"  # title

        base_title = _('Liens')
        list_title = _(u'Liste de toutes les liens')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-link'

        menu_id = 'menu-misc-links'

        has_unit = True

        help_list = _(
            u"""Les liens sont affichés dans la banque de liens pour les différentes unités. Tu peux par exemple lister les différents services interne à ta commission.
"
Le comité de l'AGEPoly peut aussi afficher un lien dans le menu de gauche.""")

        extra_right_display = {
            'get_leftmenu_display': lambda obj, user: obj.leftmenu,
            'icon': lambda obj, user: obj.leftmenu,
        }

    class MetaEdit:

        only_if = {
            'leftmenu':
            lambda instance, user: user.rights_in_root_unit(
                user, instance.MetaRightsUnit.access),
            'icon':
            lambda instance, user: user.rights_in_root_unit(
                user, instance.MetaRightsUnit.access),
        }

    class MetaSearch(SearchableModel.MetaSearch):

        fields = [
            'title',
            'description',
            'url',
        ]

    class Meta:
        abstract = True

    def __str__(self):
        return u'{} ({})'.format(self.title, self.url)

    def get_url(self):
        if self.url:
            return u'<a href="{}" target="_blank">{}</a>'.format(
                self.url, self.url)

    def __init__(self, *args, **kwargs):
        super(_Link, self).__init__(*args, **kwargs)

        self.MetaRights = type("MetaRights", (self.MetaRights, ), {})
        self.MetaRights.rights_update({
            'SHOW_BASE':
            _(u'Peut afficher la base de liens'),
        })

    def rights_can_SHOW_BASE(self, user):
        return self.rights_in_linked_unit(user)
Esempio n. 12
0
class _TVA(GenericModel, AgepolyEditableModel, SearchableModel):

    class MetaRightsAgepoly(AgepolyEditableModel.MetaRightsAgepoly):
        access = ['TRESORERIE', 'SECRETARIAT']

    name = models.CharField(_(u'Nom de la TVA'), max_length=255, default='---')
    value = models.DecimalField(_('Valeur (%)'), max_digits=20, decimal_places=2)
    agepoly_only = models.BooleanField(_(u'Limiter l\'usage au comité de l\'AGEPoly'), default=False)
    account = FalseFK('accounting_core.models.Account', verbose_name=_('Compte de TVA'))
    code = models.CharField(verbose_name=_('Code de TVA'), max_length=255)


    class Meta:
        abstract = True

    class MetaData:
        list_display = [
            ('name', _(u'Nom')),
            ('value', _(u'Valeur (%)')),
            ('agepoly_only', _(u'Limité AGEPoly ?')),
        ]

        default_sort = "[1, 'asc']"  # name

        details_display = list_display
        filter_fields = ('name', 'value',)

        base_title = _(u'TVA')
        list_title = _(u'Liste des taux de TVA')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-filter'

        menu_id = 'menu-compta-tva'

        yes_or_no_fields = ['agepoly_only',]

        help_list = _(u"""Les TVA sélectionnables dans les champs de TVA. Il est possible de restrainre l'usage de certaines TVA au CDD.

Les TVA ne sont pas liées aux autres objets comptables, il est possible de les modifier à tout moment sans risques.""")

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u""

        fields = [
            'name',
            'value',
        ]

    def __init__(self, *args, **kwargs):
        super(_TVA, self).__init__(*args, **kwargs)

        self.MetaRights = type("MetaRights", (self.MetaRights,), {})
        self.MetaRights.rights_update({
            'ANYTVA': _(u'Peut utiliser n\'importe quelle valeure de TVA.'),
        })

    def __str__(self):
        return u"{}% ({})".format(self.value, self.name)

    def rights_can_ANYTVA(self, user):
        return self.rights_in_root_unit(user, 'TRESORERIE')

    @staticmethod
    def tva_format(tva):

        from accounting_core.models import TVA

        try:
            tva_object = TVA.objects.get(value=tva)
        except:
            tva_object = None

        return u'{}% ({})'.format(tva, tva_object.name if tva_object else u'TVA Spéciale')
Esempio n. 13
0
class _Account(GenericModel, AccountingYearLinked, AgepolyEditableModel, SearchableModel):

    class MetaRightsAgepoly(AgepolyEditableModel.MetaRightsAgepoly):
        access = ['TRESORERIE', 'SECRETARIAT']

    VISIBILITY_CHOICES = (
        ('all', _(u'Visible à tous')),
        ('cdd', _(u'Visible au Comité de Direction uniquement')),
        ('root', _(u'Visible aux personnes qui gère la comptabilité générale')),
        ('none', _(u'Visible à personne')),
    )

    name = models.CharField(_('Nom du compte'), max_length=255, default='---')
    account_number = models.CharField(_(u'Numéro du compte'), max_length=10)
    visibility = models.CharField(_(u'Visibilité dans les documents comptables'), max_length=50, choices=VISIBILITY_CHOICES)
    category = FalseFK('accounting_core.models.AccountCategory', verbose_name=_(u'Catégorie'))
    description = models.TextField(_('Description'), blank=True, null=True)

    class Meta:
        abstract = True
        unique_together = (("name", "accounting_year"), ("account_number", "accounting_year"))

    class MetaData:
        list_display = [
            ('account_number', _(u'Numéro')),
            ('name', _('Nom du compte')),
            ('category', _(u'Catégorie'))
        ]

        default_sort = "[2, 'asc']"  # name

        details_display = list_display + [('description', _(u'Description')), ('get_visibility_display', _(u'Visibilité')), ('accounting_year', _(u'Année comptable'))]
        filter_fields = ('name', 'account_number', 'category__name')

        base_title = _(u'Comptes de Comptabilité Générale')
        list_title = _(u'Liste des comptes')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-money'

        menu_id = 'menu-compta-comptesCG'

        help_list = _(u"""Les comptes de comptabilité générale sont les différents comptes qui apparaissent dans la comptabilité de l'AGEPoly.
Ils permettent de séparer les recettes et les dépenses par catégories.""")

    class MetaAccounting:
        copiable = True
        foreign = (('category', 'AccountCategory'),)

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u""

        fields = [
            'name',
            'description',
            'account_number',
            'category',
        ]

    def __str__(self):
        return u"{} - {}".format(self.account_number, self.name)

    def genericFormExtraInit(self, form, current_user, *args, **kwargs):
        """Reduce the list of possible categories to the leaves of the hierarchical tree."""
        from accounting_core.models import AccountCategory

        yeared_account_categories = AccountCategory.objects.filter(accounting_year=self.accounting_year)
        yeared_account_categories = filter(lambda qs: qs.get_children_categories().count() == 0, yeared_account_categories)
        ids_yac = [yac.id for yac in yeared_account_categories]
        form.fields['category'].queryset = AccountCategory.objects.filter(id__in=ids_yac)

    def genericFormExtraClean(self, data, form):
        """Check that unique_together is fulfiled and that category is in the right accounting_year"""
        from accounting_core.models import Account

        if Account.objects.exclude(pk=self.pk).filter(accounting_year=get_current_year(form.truffe_request), name=data['name']).count():
            raise forms.ValidationError(_(u'Un compte de CG avec ce nom existe déjà pour cette année comptable.'))  # Potentiellement parmi les supprimées

        if Account.objects.exclude(pk=self.pk).filter(accounting_year=get_current_year(form.truffe_request), account_number=data['account_number']).count():
            raise forms.ValidationError(_(u'Un compte de CG avec ce numéro de compte existe déjà pour cette année comptable.'))  # Potentiellement parmi les supprimées

        if data['category'].accounting_year != get_current_year(form.truffe_request):
            raise forms.ValidationError(_(u'La catégorie choisie n\'appartient pas à la bonne année comptable.'))

    def rights_can_SHOW(self, user):

        if not self.pk:
            return super(_Account, self).rights_can_SHOW(user)
        elif self.visibility == 'none':
            return user.is_superuser
        elif self.visibility == 'root':
            return self.rights_in_root_unit(user, 'TRESORERIE')
        elif self.visibility == 'cdd':
            return user in self.people_in_root_unit()
        elif self.visibility == 'all':
            return not user.is_external()
Esempio n. 14
0
class _MemberSet(GenericModel, GenericStateModel, GenericGroupsModel,
                 UnitEditableModel, SearchableModel):
    class MetaRightsUnit(UnitEditableModel.MetaRightsUnit):
        access = ['INFORMATIQUE', 'PRESIDENCE']
        world_ro_access = True

    name = models.CharField(_('Nom'), max_length=255)
    unit = FalseFK('units.models.Unit', verbose_name=_(u'Unité'))
    generates_accred = models.BooleanField(_(u'Génère des accreds'),
                                           default=True)
    generated_accred_type = FalseFK(
        'units.models.Role',
        blank=True,
        null=True,
        verbose_name=_(u'Accréditation générée pour les membres'))
    ldap_visible = models.BooleanField(
        _(u'Rend les accreds visibles dans l\'annuaire'), default=False)
    handle_fees = models.BooleanField(_(u'Gestion des cotisations'),
                                      default=False)

    api_secret_key = models.CharField(_(u'Clé secrète pour l\'API'),
                                      max_length=128,
                                      blank=True,
                                      null=True)

    class MetaData:
        list_display = [
            ('name', _('Nom du groupe de membres')),
            ('generates_accred', _(u'Génère une accréditation')),
            ('ldap_visible', _(u'Visible dans l\'annuaire EPFL')),
            ('handle_fees', _(u'Gestion des cotisations')),
            ('status', _('Statut')),
        ]
        details_display = list_display
        details_display.insert(2, ('generated_accred_type', _(u'Type généré')))

        default_sort = "[1, 'asc']"  # name

        filter_fields = ('name', 'status')

        base_title = _('Groupes de Membres')
        list_title = _('Liste des groupes de membre')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-male'

        menu_id = 'menu-members-memberset'

        yes_or_no_fields = ['generates_accred', 'ldap_visible', 'handle_fees']

        has_unit = True

        help_list = _(
            u"""Les groupes de membres représentent l'ensemble des membres des différentes unités de l'AGEPoly.
Par exemple, ils peuvent contenir les membres d'honneurs d'une unité ou les membres qui cotisent.
Les groupes peuvent générer une accréditation EPFL pour leurs membres et gérer les cotisations suivant leur état."""
        )

    class MetaState:

        states = {
            '0_preparing': _(u'En préparation'),
            '1_active': _(u'Actif'),
            '2_archived': _(u'Archivé'),
        }

        default = '0_preparing'

        states_texts = {
            '0_preparing':
            _(u'Le groupe de membres est en cours de création et n\'est pas public.'
              ),
            '1_active':
            _(u'Le groupe de membres est actif.'),
            '2_archived':
            _(u'Le groupe de membres est archivé.'),
        }

        states_links = {
            '0_preparing': ['1_active'],
            '1_active': ['2_archived'],
            '2_archived': [],
        }

        states_colors = {
            '0_preparing': 'primary',
            '1_active': 'success',
            '2_archived': 'default',
        }

        states_icons = {
            '0_preparing': '',
            '1_active': '',
            '2_archived': '',
        }

        list_quick_switch = {
            '0_preparing': [
                ('1_active', 'fa fa-check',
                 _(u'Rendre le groupe de membres actif')),
            ],
            '1_active': [
                ('2_archived', 'fa fa-check',
                 _(u'Archiver le groupe de membres')),
            ],
            '2_archived': [],
        }

        forced_pos = {
            '0_preparing': (0.1, 0.5),
            '1_active': (0.5, 0.5),
            '2_archived': (0.9, 0.5),
        }

        states_default_filter = '0_preparing,1_active'
        states_default_filter_related = '1_active,2_archived'
        status_col_id = 3

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u'set staff anciens'

        fields = [
            'name',
        ]

    class MetaEdit:
        only_if = {'api_secret_key': lambda (x, user): user.is_superuser}

    def genericFormExtraClean(self, data, form):
        """Check if accred corresponds to generation constraints & that unique_together is fulfiled"""
        from members.models import MemberSet

        if 'generates_accred' in form.fields:
            if data['generates_accred'] and data[
                    'generated_accred_type'] is None:
                raise forms.ValidationError(
                    _(u'Accréditation nécessaire pour l\'attribuer aux membres.'
                      ))

            if 'generates_accred' not in data:  # If no accred generation, both other fields are Blank/False
                data['generated_accred_type'] = ''
                if 'ldap_visible' in data:
                    del data['ldap_visible']

        if MemberSet.objects.exclude(pk=self.pk).filter(
                unit=get_current_unit(form.truffe_request),
                name=data['name']).count():
            raise forms.ValidationError(
                _(u'L\'unité possède déjà un groupe avec ce nom.')
            )  # Potentiellement parmi les supprimées

    def genericFormExtraInit(self, form, current_user, *args, **kwargs):
        """Reduce the list of possible accreds to the official ones at EPFL"""
        from units.models import Role

        form.fields['generated_accred_type'].queryset = Role.objects.exclude(
            id_epfl='')

    def may_switch_to(self, user, dest_state):

        return self.rights_can('EDIT', user)

    def can_switch_to(self, user, dest_state):

        if self.status == '2_archived' and not user.is_superuser:
            return (
                False,
                _(u'Seul un super utilisateur peut sortir cet élément de l\'état archivé'
                  ))

        if int(dest_state[0]) - int(
                self.status[0]) != 1 and not user.is_superuser:
            return (
                False,
                _(u'Seul un super utilisateur peut sauter des étapes ou revenir en arrière.'
                  ))

        if not self.rights_can('EDIT', user):
            return (False, _('Pas les droits.'))

        return super(_MemberSet, self).can_switch_to(user, dest_state)

    class Meta:
        abstract = True
        unique_together = ("name", "unit")

    def __unicode__(self):
        return u"{} ({})".format(self.name, self.unit)

    def rights_can_EDIT(self, user):
        # On ne peut pas éditer/supprimer les groupes archivés.

        if self.status == '2_archived':
            return False
        return super(_MemberSet, self).rights_can_EDIT(user)
Esempio n. 15
0
class _Budget(GenericModel, GenericStateModel, AccountingYearLinked, CostCenterLinked, UnitEditableModel, GenericContactableModel, GenericTaggableObject, AccountingGroupModels, SearchableModel):
    """Modèle pour les budgets"""

    class MetaRightsUnit(UnitEditableModel.MetaRightsUnit):
        access = ['TRESORERIE', 'SECRETARIAT']
        unit_ro_access = True

    name = models.CharField(_(u'Titre du budget'), max_length=255, default='---')
    unit = FalseFK('units.models.Unit')

    class MetaData:
        list_display = [
            ('name', _('Titre')),
            ('costcenter', _(u'Centre de coûts')),
            ('status', _('Statut')),
        ]

        details_display = list_display + [('accounting_year', _(u'Année comptable'))]
        filter_fields = ('name', 'costcenter__name', 'costcenter__account_number')

        default_sort = "[0, 'desc']"  # Creation date (pk) descending

        base_title = _(u'Budgets')
        list_title = _(u'Liste des budgets')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-briefcase'

        has_unit = True

        menu_id = 'menu-compta-budget'

        help_list = _(u"""Les budgets permettent de prévoir les dépenses de l'unité sur l'année.

Il est obligatoire de fournir un budget au plus tard 6 semaines après le début du semestre d'automne à l'AGEPoly.""")

    class Meta:
        abstract = True

    class MetaEdit:

        @staticmethod
        def do_extra_post_actions(obj, request, post_request, form_is_valid):
            """Edit budget lines on edit"""
            from accounting_core.models import Account
            from accounting_main.models import BudgetLine

            old_lines = collections.defaultdict(list)  # {account: [{'amount', 'description'}, ...], ...}
            list(map(lambda line: old_lines[line.account.pk].append({'amount': line.amount, 'description': line.description}), obj.budgetline_set.all()))

            lines = collections.defaultdict(dict)  # {id: {type, account_pk, entries: {id1: {description, amount}, id2:{}, ...}}, ...}
            for (field, value) in post_request.items():

                if field.startswith('account-'):
                    field_arr = field.split('-')
                    lines[field_arr[2]]['type'] = 1 if field_arr[1] == 'incomes' else -1
                    lines[field_arr[2]]['account_pk'] = value

                if field.startswith('description-'):
                    field_arr = field.split('-')
                    if not lines[field_arr[2]] or 'entries' not in lines[field_arr[2]]:
                        lines[field_arr[2]]['entries'] = {}
                    if field_arr[3] not in lines[field_arr[2]]['entries']:
                        lines[field_arr[2]]['entries'][field_arr[3]] = {}
                    lines[field_arr[2]]['entries'][field_arr[3]]['description'] = value

                if field.startswith('amount-'):
                    field_arr = field.split('-')
                    if not lines[field_arr[2]] or 'entries' not in lines[field_arr[2]]:
                        lines[field_arr[2]]['entries'] = {}
                    if field_arr[3] not in lines[field_arr[2]]['entries']:
                        lines[field_arr[2]]['entries'][field_arr[3]] = {}
                    lines[field_arr[2]]['entries'][field_arr[3]]['amount'] = value

            new_lines = collections.defaultdict(list)  # {account: [{'amount', 'description'}, ...], ...}
            for line_object in lines.values():
                if 'account_pk' in line_object:
                    try:
                        account = Account.objects.get(pk=line_object['account_pk'])
                        coeff = line_object['type']  # -1 for outcomes, 1 for incomes
                        entries = sorted(line_object['entries'].items(), key=lambda x, y: x)
                        for entry in entries:
                            if entry[1]['amount']:
                                new_lines[account.pk].append({'amount': coeff * abs(float(entry[1]['amount'])), 'description': entry[1].get('description', '')})
                            else:
                                del line_object['entries'][entry[0]]
                    except Account.DoesNotExist:
                        messages.warning(request, _(u"Le compte de CG {} n'existe pas dans cette année comptable.".format(line_object['account_pk'])))

            modif_lines = collections.defaultdict(list)  # {account: [{'amount', 'description'}, ...], ...}
            for (account, entries) in old_lines.items():
                new_acc_descriptions = map(lambda new_ent: new_ent['description'], new_lines[account])
                for old_ent in deepcopy(entries):
                    if old_ent in new_lines[account]:
                        # Line was already in previous budget as is
                        old_lines[account].remove(old_ent)
                        new_lines[account].remove(old_ent)
                        new_acc_descriptions.remove(old_ent['description'])

                    elif old_ent['description'] in new_acc_descriptions:
                        # Line was already in previous budget but amount changed
                        idx = new_acc_descriptions.index(old_ent['description'])
                        new_acc_descriptions.pop(idx)
                        modif_lines[account].append(new_lines[account].pop(idx))
                        old_lines[account].remove(old_ent)

            result = {'display': dict(lines)}

            if form_is_valid:
                for account, entries in modif_lines.items():
                    acc = Account.objects.get(pk=account)
                    for entry in entries:
                        bline = BudgetLine.objects.filter(budget=obj, account=acc, description=entry['description']).first()
                        entry['old_amount'] = bline.amount
                        bline.amount = copysign(entry['amount'], entry['old_amount'])
                        bline.save()

                for account, entries in new_lines.items():
                    acc = Account.objects.get(pk=account)
                    for entry in entries:
                        BudgetLine(budget=obj, account=acc, description=entry['description'], amount=entry['amount']).save()

                for account, entries in old_lines.items():
                    acc = Account.objects.get(pk=account)
                    for entry in entries:
                        BudgetLine.objects.filter(budget=obj, account=acc, description=entry['description'], amount=entry['amount']).first().delete()

                for (title, lines) in [('log_add', new_lines), ('log_update', modif_lines), ('log_delete', old_lines)]:
                    list(map(lambda key: lines.pop(key), list(filter(lambda key: not lines[key], lines.keys()))))
                    if title == 'log_update':
                        result[title] = dict(map(lambda line_item: (u'{}'.format(Account.objects.get(pk=line_item[0])),
                                                                    (u', '.join(map(lambda ent: u'{} : {}'.format(ent['description'], ent['old_amount']), line_item[1])),
                                                                     u', '.join(map(lambda ent: u'{} : {}'.format(ent['description'], ent['amount']), line_item[1])))), lines.items()))
                    else:
                        result[title] = dict(map(lambda line_item: (u'{}'.format(Account.objects.get(pk=line_item[0])),
                                                                    u', '.join(map(lambda ent: u'{} : {}'.format(ent['description'], ent['amount']), line_item[1]))), lines.items()))
            return result

    class MetaState:
        states = {
            '0_draft': _(u'Brouillon'),
            '0_correct': _(u'A corriger'),
            '1_submited': _(u'Budget soumis'),
            '1_private': _(u'Budget privé'),
            '2_treated': _(u'Budget validé'),
        }

        default = '0_draft'

        states_texts = {
            '0_draft': _(u'Le budget est en cours de création et n\'est pas public.'),
            '0_correct': _(u'Le budget doit être corrigé.'),
            '1_submited': _(u'Le budget a été soumis.'),
            '1_private': _(u'Le budget est terminé et privé.'),
            '2_treated': _(u'Le budget a été validé.'),
        }

        states_links = {
            '0_draft': ['1_submited', '1_private'],
            '0_correct': ['1_submited'],
            '1_submited': ['2_treated', '0_correct'],
            '1_private': ['0_draft'],
            '2_treated': [],
        }

        states_colors = {
            '0_draft': 'primary',
            '0_correct': 'warning',
            '1_submited': 'default',
            '1_private': 'info',
            '2_treated': 'success',
        }

        states_icons = {
            '0_draft': '',
            '0_correct': '',
            '1_submited': '',
            '1_private': '',
            '2_treated': '',
        }

        list_quick_switch = {
            '0_draft': [('1_submited', 'fa fa-check', _(u'Soumettre le budget'))],
            '0_correct': [('1_submited', 'fa fa-check', _(u'Soumettre le budget'))],
            '1_submited': [('2_treated', 'fa fa-check', _(u'Marquer le budget comme validé')), ('0_correct', 'fa fa-exclamation', _(u'Demander des corrections'))],
            '1_private': [('0_draft', 'fa fa-mail-reply', _(u'Remodifier'))],
            '2_treated': [],
        }

        states_default_filter = '0_draft,0_correct'
        status_col_id = 3

        forced_pos = {
            '0_draft': (0.2, 0.25),
            '0_correct': (0.5, 0.75),
            '1_submited': (0.5, 0.25),
            '1_private': (0.2, 0.75),
            '2_treated': (0.8, 0.25),
        }

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u""

        fields = [
            'name',
        ]

        linked_lines = {
            'budgetline_set': ['description']
        }

    def may_switch_to(self, user, dest_state):
        return super(_Budget, self).rights_can_EDIT(user) and super(_Budget, self).may_switch_to(user, dest_state)

    def can_switch_to(self, user, dest_state):
        if self.status == '2_treated' and not user.is_superuser:
            return (False, _(u'Seul un super utilisateur peut sortir cet élément de l\'état traité'))

        if int(dest_state[0]) - int(self.status[0]) != 1 and not user.is_superuser:
            if not (self.status == '1_submited' and dest_state == '0_correct'):  # Exception faite de la correction
                return (False, _(u'Seul un super utilisateur peut sauter des étapes ou revenir en arrière.'))

        if self.status == '1_submited' and not self.rights_in_root_unit(user, self.MetaRightsUnit.access):
            return (False, _(u'Seul un membre du Comité de Direction peut marquer la demande comme validée ou à corriger.'))

        if not self.rights_can('EDIT', user):
            return (False, _('Pas les droits.'))

        return super(_Budget, self).can_switch_to(user, dest_state)

    def rights_can_EDIT(self, user):
        if int(self.status[0]) > 0:
            return self.rights_in_root_unit(user, self.MetaRightsUnit.access)

        return super(_Budget, self).rights_can_EDIT(user)

    def rights_can_SHOW(self, user):
        if self.status == '1_private' and not self.rights_in_unit(user, self.unit, access=self.MetaRightsUnit.access, no_parent=True):
            return False

        return super(_Budget, self).rights_can_SHOW(user)

    def __str__(self):
        return u"{} ({})".format(self.name, self.costcenter)

    def get_total_incomes(self):
        total = 0.0

        for line in self.budgetline_set.all():
            if line.amount > 0:
                total += float(line.amount)
        return total

    def get_total_outcomes(self):
        total = 0.0

        for line in self.budgetline_set.all():
            if line.amount < 0:
                total += abs(float(line.amount))
        return total

    def get_total(self):
        return sum(map(lambda line: line.amount, list(self.budgetline_set.all())))
Esempio n. 16
0
class _Booking(GenericModel, GenericGroupsModerableModel, GenericGroupsModel,
               GenericContactableModel, GenericStateRootValidable,
               GenericStateModel, UnitEditableModel, SearchableModel):
    class MetaRightsUnit(UnitEditableModel.MetaRightsUnit):
        access = 'LOGISTIQUE'
        moderation_access = 'SECRETARIAT'

    unit = FalseFK('units.models.Unit')

    title = models.CharField(_(u'Titre'), max_length=255)
    responsible = models.ForeignKey(TruffeUser, verbose_name=_(u'Responsable'))
    reason = models.TextField(_(u'Motif'))
    remark = models.TextField(_(u'Remarques'), blank=True, null=True)
    remark_agepoly = models.TextField(_(u'Remarques AGEPoly'),
                                      blank=True,
                                      null=True)

    provider = FalseFK('vehicles.models.Provider',
                       verbose_name=_(u'Fournisseur'))
    vehicletype = FalseFK('vehicles.models.VehicleType',
                          verbose_name=_(u'Type de véhicule'))
    card = FalseFK('vehicles.models.Card',
                   verbose_name=_(u'Carte'),
                   blank=True,
                   null=True)
    location = FalseFK('vehicles.models.Location',
                       verbose_name=_(u'Lieu'),
                       blank=True,
                       null=True)

    start_date = models.DateTimeField(_(u'Début de la réservation'))
    end_date = models.DateTimeField(_(u'Fin de la réservation'))

    class MetaData:
        list_display = [
            ('title', _('Titre')),
            ('start_date', _(u'Date début')),
            ('end_date', _('Date fin')),
            ('provider', _('Fournisseur')),
            ('vehicletype', _(u'Type de véhicule')),
            ('status', _('Statut')),
        ]
        details_display = list_display + [
            ('responsible', _('Responsable')),
            ('reason', _('Motif')),
            ('remark', _('Remarques')),
            ('remark_agepoly', _('Remarques AGEPoly')),
            ('card', _('Carte')),
            ('get_location', _('Lieu')),
        ]

        filter_fields = ('title', 'status')

        base_title = _(u'Réservations de véhicule')
        list_title = _(u'Liste de toutes les réservations de véhicules')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-ambulance'

        default_sort = "[3, 'desc']"  # end_date

        forced_widths = {
            '1': '25%',
            '2': '140px',  # start date
            '3': '140px',  # end date
        }

        forced_widths_related = {
            '1': '15%',
            '2': '25%',
            '4': '150px',  # start date
            '5': '150px',  # end date
        }

        menu_id = 'menu-vehicles-booking'
        menu_id_calendar = 'menu-vehicles-booking-calendar'
        menu_id_calendar_related = 'menu-vehicles-booking-calendar-related'

        datetime_fields = ['start_date', 'end_date']
        safe_fields = ['get_location']

        has_unit = True

        help_list = _(
            u"""Les réservations de véhicules te permettent de demander la location d'un véhicule pour ton unité.

Ils sont soumis à validation par le secrétariat de l'AGEPoly. Il faut toujours faire les réservations le plus tôt possible !"""
        )

        help_list_related = _(
            u"""La liste de toutes les réservations de véhicules.""")

        @staticmethod
        def extra_args_for_edit(request, current_unit, current_year):
            from vehicles.models import Provider
            return {
                'providers':
                Provider.objects.filter(deleted=False).order_by('name')
            }

    class MetaEdit:
        datetime_fields = ('start_date', 'end_date')

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u'mobility véhicule réservation'

        fields = [
            'title',
            'card',
            'provider',
            'location',
            'vehicletype',
            'responsible',
            'remark',
            'reason',
            'remark_agepoly',
        ]

    class MetaState(GenericStateRootValidable.MetaState):

        states_texts = {
            '0_draft':
            _(u'La réservation est en cours de création et n\'est pas publique.'
              ),
            '1_asking':
            _(u'La réservation est en cours de modération. Elle n\'est pas éditable. Sélectionner ce statut pour demander une modération !'
              ),
            '2_online':
            _(u'La résevation est validée. Elle n\'est pas éditable.'),
            '3_archive':
            _(u'La réservation est archivée. Elle n\'est plus modifiable.'),
            '4_deny':
            _(u'La modération a été refusée. Le véhicule n\'était probablement pas disponible.'
              ),
        }

        def build_form_validation(request, obj):
            from vehicles.models import Location

            class FormValidation(forms.Form):
                remark_agepoly = forms.CharField(label=_('Remarque'),
                                                 widget=forms.Textarea,
                                                 required=False)
                card = forms.ModelChoiceField(
                    label=_(u'Carte'),
                    queryset=obj.provider.get_cards(),
                    required=False)
                location = forms.ModelChoiceField(
                    label=_(u'Lieu'),
                    queryset=Location.objects.filter(
                        deleted=False).order_by('name'),
                    required=False)

            return FormValidation

        states_bonus_form = {'2_online': build_form_validation}

    def switch_status_signal(self, request, old_status, dest_status):

        from vehicles.models import Location, Card

        if dest_status == '2_online':

            if request.POST.get('remark_agepoly'):
                if self.remark_agepoly:
                    self.remark_agepoly += '\n' + request.POST.get(
                        'remark_agepoly')
                else:
                    self.remark_agepoly = request.POST.get('remark_agepoly')
                self.save()

            if request.POST.get('card'):
                self.card = get_object_or_404(Card,
                                              pk=request.POST.get('card'),
                                              provider=self.provider,
                                              deleted=False)
                self.save()

            if request.POST.get('location'):
                self.location = get_object_or_404(
                    Location, pk=request.POST.get('location'), deleted=False)
                self.save()

        s = super(_Booking, self)

        if hasattr(s, 'switch_status_signal'):
            s.switch_status_signal(request, old_status, dest_status)

    class Meta:
        abstract = True

    def __unicode__(self):
        return self.title

    def get_location(self):
        if self.location:
            if self.location.url_location:
                return u'<a href="{}">{}</a>'.format(
                    self.location.url_location, self.location)
            else:
                return self.location.__unicode__()
        else:
            return ''

    def genericFormExtraInit(self, form, current_user, *args, **kwargs):
        """Remove fields that should be edited by SECRETARIAT CDD only."""

        if not self.rights_in_root_unit(current_user, 'SECRETARIAT'):
            del form.fields['card']
            del form.fields['location']
            del form.fields['remark_agepoly']

        unit_users_pk = map(lambda user: user.pk,
                            self.unit.users_with_access())
        form.fields['responsible'].queryset = TruffeUser.objects.filter(
            pk__in=unit_users_pk).order_by('first_name', 'last_name')

    def genericFormExtraClean(self, data, form):

        if 'provider' in data:
            if 'card' in data and data['card']:
                if data['card'].provider != data['provider']:
                    raise forms.ValidationError(
                        _(u'La carte n\'est pas lié au fournisseur sélectionné'
                          ))
            if 'vehiculetype' in data and data['vehiculetype']:
                if data['vehiculetype'].provider != data['provider']:
                    raise forms.ValidationError(
                        _(u'Le type de véhicule n\'est pas lié au fournisseur sélectionné'
                          ))

    def conflicting_reservation(self):
        return self.__class__.objects.exclude(pk=self.pk, deleted=True).filter(
            status__in=['2_online'],
            end_date__gt=self.start_date,
            start_date__lt=self.end_date)
Esempio n. 17
0
class _Logo(GenericModel, GenericModelWithFiles, AutoVisibilityLevel,
            UnitEditableModel, SearchableModel):
    class MetaRightsUnit(UnitEditableModel.MetaRightsUnit):
        access = 'COMMUNICATION'

    name = models.CharField(max_length=255)
    unit = FalseFK('units.models.Unit')

    class MetaData:
        list_display = [
            ('name', _('Nom')),
        ]
        details_display = list_display + [
            ('get_visibility_level_display', _(u'Visibilité')),
        ]
        filter_fields = ('name', )

        base_title = _(u'Logo')
        list_title = _(u'Liste de tous les logos')
        files_title = _(u'Fichiers')
        base_icon = 'fa fa-list'
        elem_icon = 'fa fa-picture-o'

        default_sort = "[1, 'asc']"  # name

        menu_id = 'menu-communication-logo'

        has_unit = True

        help_list = _(u"""Les logos de ton unité.
            Tu peux rendre public les logos, ce qui est recommandé afin d'aider les autres unités lors de constructions graphiques (ex: agenda) ou ton propre comité.
            Un logo peut comporter plusieurs fichiers : ceci te permet d'uploader différents formats pour un même fichier !"""
                      )

    class MetaEdit:
        files_title = _(u'Fichiers')
        files_help = _(
            u'Envoie le ou les fichiers de ton logo. Le système te permet d\'envoyer plusieurs fichiers pour te permettre d\'envoyer des formats différents.'
        )

    class MetaSearch(SearchableModel.MetaSearch):
        extra_text = u""
        index_files = True

        fields = [
            'name',
        ]

    class Meta:
        abstract = True

    def __unicode__(self):
        return self.name

    def get_best_image(self):
        """Try to find a suitable file for thumbnail"""

        for f in self.files.all():
            if f.is_picture():
                return f

        return f
Esempio n. 18
0
class _AccountingLine(GenericModel, GenericStateModel, AccountingYearLinked, CostCenterLinked, GenericGroupsModel, AccountingGroupModels, GenericContactableModel, UnitEditableModel, SearchableModel):

    class MetaRightsUnit(UnitEditableModel.MetaRightsUnit):
        access = ['TRESORERIE', 'SECRETARIAT']
        world_ro_access = False

    class MetaRights(UnitEditableModel.MetaRights):
        linked_unit_property = 'costcenter.unit'

    account = FalseFK('accounting_core.models.Account', verbose_name=_(u'Compte de CG'))
    date = models.DateField()
    tva = models.DecimalField(_('TVA'), max_digits=20, decimal_places=2)
    text = models.CharField(max_length=2048)
    output = models.DecimalField(_(u'Débit'), max_digits=20, decimal_places=2)
    input = models.DecimalField(_(u'Crédit'), max_digits=20, decimal_places=2)
    current_sum = models.DecimalField(_('Situation'), max_digits=20, decimal_places=2)
    document_id = models.PositiveIntegerField(_(u'Numéro de pièce comptable'), blank=True, null=True)
    order = models.PositiveIntegerField(default=0)

    class Meta:
        abstract = True

    class MetaEdit:
        pass

    class MetaData:
        list_display = [
            ('date', _(u'Date')),
            ('account', _(u'Compte de CG')),
            ('document_id', _(u'Pièce comptable')),
            ('text', _(u'Texte')),
            ('tva', _(u'% TVA')),
            ('get_output_display', _(u'Débit')),
            ('get_input_display', _(u'Crédit')),
            ('get_current_sum_display', _(u'Situation')),
            ('status', _(u'Statut')),
        ]

        forced_widths = {
            '1': '100px',
            '2': '300px',
            '3': '75px',
            '5': '75px',
            '6': '75px',
            '7': '75px',
            '8': '75px',
            '9': '75px',
            '10': '75px',
        }

        default_sort = "[0, 'desc']"  # order
        filter_fields = ('text', 'tva', 'output', 'input', 'current_sum', 'account__name', 'account__account_number')

        details_display = list_display + [
            ('costcenter', _(u'Centre de coûts')),
        ]

        base_title = _(u'Comptabilité')
        list_title = _(u'Liste des entrées de la comptabilité')
        base_icon = 'fa fa-list-ol'
        elem_icon = 'fa fa-ellipsis-horizontal'

        menu_id = 'menu-compta-compta'
        trans_sort = {'get_output_display': 'output', 'get_input_display': 'input', 'get_current_sum_display': 'current_sum', 'pk': 'order'}
        safe_fields = ['get_output_display', 'get_input_display', 'get_current_sum_display']
        datetime_fields = ['date']

        has_unit = True

        extradata = 'cost_center_extradata'

        help_list = _(u"""Les lignes de la compta de l'AGEPoly.

Tu peux (et tu dois) valider les lignes ou signaler les erreurs via les boutons corespondants.""")

        @staticmethod
        def extra_args_for_list(request, current_unit, current_year):
            from accounting_core.models import CostCenter

            base = CostCenter.objects.filter(accounting_year=current_year, deleted=False)

            if current_unit and current_unit.pk > 0:
                base = base.filter(Q(unit=current_unit) | (Q(unit__parent_hierarchique=current_unit) & Q(unit__is_commission=False)))
            return {'costcenters': base.order_by('account_number')}

        @staticmethod
        def extra_filter_for_list(request, current_unit, current_year, filtering):
            from accounting_core.models import CostCenter
            try:
                cc = get_object_or_404(CostCenter, pk=request.GET.get('costcenter'))
            except:
                cc = None
            return lambda x: filtering(x).filter(costcenter=cc)

    class MetaState:

        states = {
            '0_imported': _(u'En attente'),
            '1_validated': _(u'Validé'),
            '2_error': _(u'Erreur'),
        }

        default = '0_imported'

        states_texts = {
            '0_imported': _(u'La ligne vient d\'être importée'),
            '1_validated': _(u'La ligne est validée'),
            '2_error': _(u'La ligne est fausse et nécessite une correction'),
        }

        states_links = {
            '0_imported': ['1_validated', '2_error'],
            '1_validated': ['2_error'],
            '2_error': ['1_validated'],
        }

        states_colors = {
            '0_imported': 'primary',
            '1_validated': 'success',
            '2_error': 'danger',
        }

        states_icons = {
        }

        list_quick_switch = {
            '0_imported': [('2_error', 'fa fa-warning', _(u'Signaler une erreur')), ('1_validated', 'fa fa-check', _(u'Marquer comme validé')), ],
            '1_validated': [('2_error', 'fa fa-warning', _(u'Signaler une erreur')), ],
            '2_error': [('1_validated', 'fa fa-check', _(u'Marquer comme validé')), ],
        }

        states_default_filter = '0_imported,1_validated,2_error'
        states_default_filter_related = '0_imported,1_validated,2_error'
        status_col_id = 9
        dont_sort_list = True

        forced_pos = {
            '0_imported': (0.1, 0.25),
            '1_validated': (0.9, 0.25),
            '2_error': (0.5, 0.75),
        }

        class FormError(Form):
            error = CharField(label=_('Description de l\'erreur'), help_text=_(u'Une erreur sera crée automatiquement, liée à la ligne. Laisse le champ vide si tu ne veux pas créer une erreur (mais ceci est fortement déconseillé)'), required=False, widget=Textarea)

        class FormValid(Form):
            fix_errors = BooleanField(label=_(u'Résoudre les erreurs liées'), help_text=_(u'Fix automatiquement les erreurs liées à la ligne. Attention, des erreurs peuvent être dissociées !'), required=False, initial=True)

        states_bonus_form = {
            '2_error': FormError,
            ('2_error', '1_validated'): FormValid
        }

    class MetaSearch(SearchableModel.MetaSearch):

        extra_text = u"compta"

        fields = [
            'account',
            'date',
            'document_id',
            'input',
            'output',
            'text',
        ]

    def may_switch_to(self, user, dest_state):
        return super(_AccountingLine, self).rights_can_EDIT(user) and super(_AccountingLine, self).may_switch_to(user, dest_state)

    def can_switch_to(self, user, dest_state):

        if not super(_AccountingLine, self).rights_can_EDIT(user):
            return (False, _('Pas les droits.'))

        return super(_AccountingLine, self).can_switch_to(user, dest_state)

    def rights_can_EDIT(self, user):
        return False  # Never !

    def rights_can_DISPLAY_LOG(self, user):
        """Always display log, even if current state dosen't allow edit"""
        return super(_AccountingLine, self).rights_can_EDIT(user)

    def __str__(self):
        if self.output and self.input:
            return u'{}: {} (-{}/+{})'.format(self.date, self.text, intcomma(floatformat(self.output, 2)), intcomma(floatformat(self.input, 2)))
        elif self.output:
            return u'{}: {} (-{})'.format(self.date, self.text, intcomma(floatformat(self.output, 2)))
        else:
            return u'{}: {} (+{})'.format(self.date, self.text, intcomma(floatformat(self.input, 2)))

    def get_output_display(self):
        if self.output:
            return '<span class="txt-color-red">-{}</span>'.format(intcomma(floatformat(self.output, 2)))
        else:
            return ''

    def get_input_display(self):
        if self.input:
            return '<span class="txt-color-green">{}</span>'.format(intcomma(floatformat(self.input, 2)))
        else:
            return ''

    def get_current_sum_display(self):
        if self.current_sum < 0:
            return '<span class="txt-color-green">{}</span>'.format(intcomma(floatformat(-self.current_sum, 2)))
        elif self.current_sum > 0:
            return '<span class="txt-color-red">{}</span>'.format(intcomma(floatformat(-self.current_sum, 2)))
        else:
            return '0.00'

    def switch_status_signal(self, request, old_status, dest_status):

        from accounting_main.models import AccountingError, AccountingErrorLogging

        s = super(_AccountingLine, self)

        if hasattr(s, 'switch_status_signal'):
            s.switch_status_signal(request, old_status, dest_status)

        if dest_status == '2_error':

            if request.POST.get('error'):
                ae = AccountingError(initial_remark=request.POST.get('error'), linked_line=self, accounting_year=self.accounting_year, costcenter=self.costcenter)
                ae.save()
                AccountingErrorLogging(who=request.user, what='created', object=ae).save()

                notify_people(request, u'AccountingError.{}.created'.format(self.costcenter.unit), 'accounting_error_created', ae, ae.build_group_members_for_compta_everyone_with_messages())

            unotify_people(u'AccountingLine.{}.fixed'.format(self.costcenter.unit), self)
            notify_people(request, u'AccountingLine.{}.error'.format(self.costcenter.unit), 'accounting_line_error', self, self.build_group_members_for_compta_everyone())

        if dest_status == '1_validated':

            if old_status == '2_error':
                unotify_people(u'AccountingLine.{}.error'.format(self.costcenter.unit), self)
                notify_people(request, u'AccountingLine.{}.fixed'.format(self.costcenter.unit), 'accounting_line_fixed', self, self.build_group_members_for_compta_everyone())

            if request.POST.get('fix_errors'):

                for error in self.accountingerror_set.filter(deleted=False).exclude(status='2_fixed'):
                    old_status = error.status
                    error.status = '2_fixed'
                    error.save()

                    AccountingErrorLogging(who=request.user, what='state_changed', object=error, extra_data=json.dumps({'old': str(error.MetaState.states.get(old_status)), 'new': str(error.MetaState.states.get('2_fixed'))})).save()

                    unotify_people(u'AccountingError.{}.created'.format(self.costcenter.unit), error)
                    notify_people(request, u'AccountingError.{}.fixed'.format(self.costcenter.unit), 'accounting_error_fixed', error, error.build_group_members_for_compta_everyone_with_messages())

    def get_errors(self):
        return self.accountingerror_set.filter(deleted=False).order_by('status')

    def __init__(self, *args, **kwargs):
        super(_AccountingLine, self).__init__(*args, **kwargs)

        self.MetaRights = type("MetaRights", (self.MetaRights,), {})
        self.MetaRights.rights_update({
            'IMPORT': _(u'Peut importer la compta'),
        })

    def rights_can_IMPORT(self, user):
        return self.rights_in_root_unit(user, ['TRESORERIE', 'SECRETARIAT'])