diff --git a/CHANGELOG.md b/CHANGELOG.md
index cdacea61..40ca06d6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -164,3 +164,17 @@ Collec new statics
```bash
python3 manage.py collectstatic
```
+
+## MR 391: Document templates and subscription vouchers
+
+Re2o can now use templates for generated invoices. To load default templates run
+
+```bash
+./install update
+```
+
+Be carefull, you need the proper rights to edit a DocumentTemplate.
+
+Re2o now sends subscription voucher when an invoice is controlled. It uses one
+of the templates. You also need to set the name of the president of your association
+to be set in your settings.
diff --git a/api/acl.py b/api/acl.py
index 9107a25d..0c336281 100644
--- a/api/acl.py
+++ b/api/acl.py
@@ -28,7 +28,7 @@ done.
from django.conf import settings
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import ugettext as _
def _create_api_permission():
@@ -71,4 +71,5 @@ def can_view(user):
'codename': settings.API_PERMISSION_CODENAME
}
can = user.has_perm('%(app_label)s.%(codename)s' % kwargs)
- return can, None if can else _("You cannot see this application.")
+ return can, None if can else _("You don't have the right to see this"
+ " application.")
diff --git a/api/authentication.py b/api/authentication.py
index cbc72b76..d426db24 100644
--- a/api/authentication.py
+++ b/api/authentication.py
@@ -46,6 +46,6 @@ class ExpiringTokenAuthentication(TokenAuthentication):
)
utc_now = datetime.datetime.now(datetime.timezone.utc)
if token.created < utc_now - token_duration:
- raise exceptions.AuthenticationFailed(_('Token has expired'))
+ raise exceptions.AuthenticationFailed(_("The token has expired."))
return token.user, token
diff --git a/api/locale/fr/LC_MESSAGES/django.po b/api/locale/fr/LC_MESSAGES/django.po
new file mode 100644
index 00000000..f2d6755e
--- /dev/null
+++ b/api/locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,40 @@
+# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
+# se veut agnostique au réseau considéré, de manière à être installable en
+# quelques clics.
+#
+# Copyright © 2018 Maël Kervella
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+msgid ""
+msgstr ""
+"Project-Id-Version: 2.5\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-01-08 23:06+0100\n"
+"PO-Revision-Date: 2019-01-07 01:37+0100\n"
+"Last-Translator: Laouen Fernet \n"
+"Language-Team: \n"
+"Language: fr_FR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: acl.py:74
+msgid "You don't have the right to see this application."
+msgstr "Vous n'avez pas le droit de voir cette application."
+
+#: authentication.py:49
+msgid "The token has expired."
+msgstr "Le jeton a expiré."
diff --git a/api/serializers.py b/api/serializers.py
index e7b23f32..8c22ed21 100644
--- a/api/serializers.py
+++ b/api/serializers.py
@@ -391,13 +391,25 @@ class OptionalTopologieSerializer(NamespacedHMSerializer):
class Meta:
model = preferences.OptionalTopologie
- fields = ('radius_general_policy', 'vlan_decision_ok',
- 'vlan_decision_nok', 'switchs_ip_type', 'switchs_web_management',
+ fields = ('switchs_ip_type', 'switchs_web_management',
'switchs_web_management_ssl', 'switchs_rest_management',
'switchs_management_utils', 'switchs_management_interface_ip',
'provision_switchs_enabled', 'switchs_provision', 'switchs_management_sftp_creds')
+class RadiusOptionSerializer(NamespacedHMSerializer):
+ """Serialize `preferences.models.RadiusOption` objects
+ """
+
+ class Meta:
+ model = preferences.RadiusOption
+ fields = ('radius_general_policy', 'unknown_machine',
+ 'unknown_machine_vlan', 'unknown_port',
+ 'unknown_port_vlan', 'unknown_room', 'unknown_room_vlan',
+ 'non_member', 'non_member_vlan', 'banned', 'banned_vlan',
+ 'vlan_decision_ok')
+
+
class GeneralOptionSerializer(NamespacedHMSerializer):
"""Serialize `preferences.models.GeneralOption` objects.
"""
@@ -811,7 +823,8 @@ class SwitchPortSerializer(serializers.ModelSerializer):
model = topologie.Switch
fields = ('short_name', 'model', 'switchbay', 'ports', 'ipv4', 'ipv6',
'interfaces_subnet', 'interfaces6_subnet', 'automatic_provision', 'rest_enabled',
- 'web_management_enabled', 'get_radius_key_value', 'get_management_cred_value')
+ 'web_management_enabled', 'get_radius_key_value', 'get_management_cred_value',
+ 'list_modules')
# LOCAL EMAILS
diff --git a/api/urls.py b/api/urls.py
index 723ca78c..4a34c1de 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -67,6 +67,7 @@ router.register_viewset(r'machines/role', views.RoleViewSet)
router.register_view(r'preferences/optionaluser', views.OptionalUserView),
router.register_view(r'preferences/optionalmachine', views.OptionalMachineView),
router.register_view(r'preferences/optionaltopologie', views.OptionalTopologieView),
+router.register_view(r'preferences/radiusoption', views.RadiusOptionView),
router.register_view(r'preferences/generaloption', views.GeneralOptionView),
router.register_viewset(r'preferences/service', views.HomeServiceViewSet, base_name='homeservice'),
router.register_view(r'preferences/assooption', views.AssoOptionView),
diff --git a/api/views.py b/api/views.py
index 21f7b438..3108f9f3 100644
--- a/api/views.py
+++ b/api/views.py
@@ -292,6 +292,17 @@ class OptionalTopologieView(generics.RetrieveAPIView):
return preferences.OptionalTopologie.objects.first()
+class RadiusOptionView(generics.RetrieveAPIView):
+ """Exposes details of `preferences.models.OptionalTopologie` settings.
+ """
+ permission_classes = (ACLPermission,)
+ perms_map = {'GET': [preferences.RadiusOption.can_view_all]}
+ serializer_class = serializers.RadiusOptionSerializer
+
+ def get_object(self):
+ return preferences.RadiusOption.objects.first()
+
+
class GeneralOptionView(generics.RetrieveAPIView):
"""Exposes details of `preferences.models.GeneralOption` settings.
"""
@@ -600,9 +611,11 @@ class HostMacIpView(generics.ListAPIView):
"""Exposes the associations between hostname, mac address and IPv4 in
order to build the DHCP lease files.
"""
- queryset = all_active_interfaces()
serializer_class = serializers.HostMacIpSerializer
+ def get_queryset(self):
+ return all_active_interfaces()
+
# Firewall
@@ -635,7 +648,7 @@ class DNSZonesView(generics.ListAPIView):
class DNSReverseZonesView(generics.ListAPIView):
- """Exposes the detailed information about each extension (hostnames,
+ """Exposes the detailed information about each extension (hostnames,
IPs, DNS records, etc.) in order to build the DNS zone files.
"""
queryset = (machines.IpType.objects.all())
diff --git a/cotisations/admin.py b/cotisations/admin.py
index afe4621c..4b47ccc8 100644
--- a/cotisations/admin.py
+++ b/cotisations/admin.py
@@ -30,7 +30,7 @@ from django.contrib import admin
from reversion.admin import VersionAdmin
from .models import Facture, Article, Banque, Paiement, Cotisation, Vente
-from .models import CustomInvoice
+from .models import CustomInvoice, CostEstimate
class FactureAdmin(VersionAdmin):
@@ -38,6 +38,11 @@ class FactureAdmin(VersionAdmin):
pass
+class CostEstimateAdmin(VersionAdmin):
+ """Admin class for cost estimates."""
+ pass
+
+
class CustomInvoiceAdmin(VersionAdmin):
"""Admin class for custom invoices."""
pass
@@ -76,3 +81,4 @@ admin.site.register(Paiement, PaiementAdmin)
admin.site.register(Vente, VenteAdmin)
admin.site.register(Cotisation, CotisationAdmin)
admin.site.register(CustomInvoice, CustomInvoiceAdmin)
+admin.site.register(CostEstimate, CostEstimateAdmin)
diff --git a/cotisations/forms.py b/cotisations/forms.py
index 01e52756..3f99382b 100644
--- a/cotisations/forms.py
+++ b/cotisations/forms.py
@@ -46,7 +46,10 @@ from django.shortcuts import get_object_or_404
from re2o.field_permissions import FieldPermissionFormMixin
from re2o.mixins import FormRevMixin
-from .models import Article, Paiement, Facture, Banque, CustomInvoice
+from .models import (
+ Article, Paiement, Facture, Banque,
+ CustomInvoice, Vente, CostEstimate,
+)
from .payment_methods import balance
@@ -104,7 +107,44 @@ class SelectArticleForm(FormRevMixin, Form):
user = kwargs.pop('user')
target_user = kwargs.pop('target_user', None)
super(SelectArticleForm, self).__init__(*args, **kwargs)
- self.fields['article'].queryset = Article.find_allowed_articles(user, target_user)
+ self.fields['article'].queryset = Article.find_allowed_articles(
+ user, target_user)
+
+
+class DiscountForm(Form):
+ """
+ Form used in oder to create a discount on an invoice.
+ """
+ is_relative = forms.BooleanField(
+ label=_("Discount is on percentage."),
+ required=False,
+ )
+ discount = forms.DecimalField(
+ label=_("Discount"),
+ max_value=100,
+ min_value=0,
+ max_digits=5,
+ decimal_places=2,
+ required=False,
+ )
+
+ def apply_to_invoice(self, invoice):
+ invoice_price = invoice.prix_total()
+ discount = self.cleaned_data['discount']
+ is_relative = self.cleaned_data['is_relative']
+ if is_relative:
+ amount = discount/100 * invoice_price
+ else:
+ amount = discount
+ if amount:
+ name = _("{}% discount") if is_relative else _("{}€ discount")
+ name = name.format(discount)
+ Vente.objects.create(
+ facture=invoice,
+ name=name,
+ prix=-amount,
+ number=1
+ )
class CustomInvoiceForm(FormRevMixin, ModelForm):
@@ -116,6 +156,15 @@ class CustomInvoiceForm(FormRevMixin, ModelForm):
fields = '__all__'
+class CostEstimateForm(FormRevMixin, ModelForm):
+ """
+ Form used to create a cost estimate.
+ """
+ class Meta:
+ model = CostEstimate
+ exclude = ['paid', 'final_invoice']
+
+
class ArticleForm(FormRevMixin, ModelForm):
"""
Form used to create an article.
@@ -248,7 +297,8 @@ class RechargeForm(FormRevMixin, Form):
super(RechargeForm, self).__init__(*args, **kwargs)
self.fields['payment'].empty_label = \
_("Select a payment method")
- self.fields['payment'].queryset = Paiement.find_allowed_payments(user_source).exclude(is_balance=True)
+ self.fields['payment'].queryset = Paiement.find_allowed_payments(
+ user_source).exclude(is_balance=True)
def clean(self):
"""
@@ -260,10 +310,9 @@ class RechargeForm(FormRevMixin, Form):
if balance_method.maximum_balance is not None and \
value + self.user.solde > balance_method.maximum_balance:
raise forms.ValidationError(
- _("Requested amount is too high. Your balance can't exceed \
- %(max_online_balance)s €.") % {
+ _("Requested amount is too high. Your balance can't exceed"
+ " %(max_online_balance)s €.") % {
'max_online_balance': balance_method.maximum_balance
}
)
return self.cleaned_data
-
diff --git a/cotisations/locale/fr/LC_MESSAGES/django.po b/cotisations/locale/fr/LC_MESSAGES/django.po
index 15f6a057..2c29dc8d 100644
--- a/cotisations/locale/fr/LC_MESSAGES/django.po
+++ b/cotisations/locale/fr/LC_MESSAGES/django.po
@@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-08-18 13:17+0200\n"
+"POT-Creation-Date: 2019-01-12 16:50+0100\n"
"PO-Revision-Date: 2018-03-31 16:09+0002\n"
"Last-Translator: Laouen Fernet \n"
"Language: fr_FR\n"
@@ -33,79 +33,98 @@ msgstr ""
msgid "You don't have the right to view this application."
msgstr "Vous n'avez pas le droit de voir cette application."
-#: forms.py:63 forms.py:274
+#: forms.py:66 forms.py:299
msgid "Select a payment method"
msgstr "Sélectionnez un moyen de paiement"
-#: forms.py:66 models.py:510
+#: forms.py:69 models.py:579
msgid "Member"
msgstr "Adhérent"
-#: forms.py:68
+#: forms.py:71
msgid "Select the proprietary member"
msgstr "Sélectionnez l'adhérent propriétaire"
-#: forms.py:69
+#: forms.py:72
msgid "Validated invoice"
msgstr "Facture validée"
-#: forms.py:82
+#: forms.py:85
msgid "A payment method must be specified."
msgstr "Un moyen de paiement doit être renseigné."
-#: forms.py:96 forms.py:120 templates/cotisations/aff_article.html:33
-#: templates/cotisations/facture.html:61
+#: forms.py:97 templates/cotisations/aff_article.html:33
+#: templates/cotisations/facture.html:67
msgid "Article"
msgstr "Article"
-#: forms.py:100 forms.py:124 templates/cotisations/edit_facture.html:46
+#: forms.py:101 templates/cotisations/edit_facture.html:50
msgid "Quantity"
msgstr "Quantité"
-#: forms.py:154
+#: forms.py:119
+msgid "Discount is on percentage."
+msgstr "La réduction est en pourcentage."
+
+#: forms.py:123 templates/cotisations/facture.html:78
+msgid "Discount"
+msgstr "Réduction"
+
+#: forms.py:140
+#, python-format
+msgid "{}% discount"
+msgstr "{}% de réduction"
+
+#: forms.py:140
+msgid "{}€ discount"
+msgstr "{}€ de réduction"
+
+#: forms.py:179
msgid "Article name"
msgstr "Nom de l'article"
-#: forms.py:164 templates/cotisations/sidebar.html:50
+#: forms.py:189 templates/cotisations/sidebar.html:55
msgid "Available articles"
msgstr "Articles disponibles"
-#: forms.py:192
+#: forms.py:217
msgid "Payment method name"
msgstr "Nom du moyen de paiement"
-#: forms.py:204
+#: forms.py:229
msgid "Available payment methods"
msgstr "Moyens de paiement disponibles"
-#: forms.py:230
+#: forms.py:255
msgid "Bank name"
msgstr "Nom de la banque"
-#: forms.py:242
+#: forms.py:267
msgid "Available banks"
msgstr "Banques disponibles"
-#: forms.py:261
+#: forms.py:286
msgid "Amount"
msgstr "Montant"
-#: forms.py:267 templates/cotisations/aff_cotisations.html:44
+#: forms.py:292 templates/cotisations/aff_cost_estimate.html:42
+#: templates/cotisations/aff_cotisations.html:44
#: templates/cotisations/aff_custom_invoice.html:42
#: templates/cotisations/control.html:66
msgid "Payment method"
msgstr "Moyen de paiement"
-#: forms.py:287
+#: forms.py:313
#, python-format
msgid ""
-"Requested amount is too high. Your balance can't exceed "
+"Requested amount is too high. Your balance can't exceed "
"%(max_online_balance)s €."
msgstr ""
-"Le montant demandé trop grand. Votre solde ne peut excéder "
+"Le montant demandé est trop grand. Votre solde ne peut excéder "
"%(max_online_balance)s €."
-#: models.py:60 templates/cotisations/aff_cotisations.html:48
+#: models.py:60 templates/cotisations/aff_cost_estimate.html:46
+#: templates/cotisations/aff_cotisations.html:48
#: templates/cotisations/aff_custom_invoice.html:46
#: templates/cotisations/control.html:70
msgid "Date"
@@ -133,9 +152,9 @@ msgstr "Peut voir un objet facture"
#: models.py:158
msgid "Can edit all the previous invoices"
-msgstr "Peut modifier toutes les factures existantes"
+msgstr "Peut modifier toutes les factures précédentes"
-#: models.py:160 models.py:305
+#: models.py:160 models.py:373
msgid "invoice"
msgstr "facture"
@@ -156,128 +175,149 @@ msgid ""
"You don't have the right to edit an invoice already controlled or "
"invalidated."
msgstr ""
-"Vous n'avez pas le droit de modifier une facture précedemment contrôlée ou "
+"Vous n'avez pas le droit de modifier une facture précédemment contrôlée ou "
"invalidée."
#: models.py:184
msgid "You don't have the right to delete an invoice."
msgstr "Vous n'avez pas le droit de supprimer une facture."
-#: models.py:186
+#: models.py:187
msgid "You don't have the right to delete this user's invoices."
msgstr "Vous n'avez pas le droit de supprimer les factures de cet utilisateur."
-#: models.py:189
+#: models.py:191
msgid ""
"You don't have the right to delete an invoice already controlled or "
"invalidated."
msgstr ""
-"Vous n'avez pas le droit de supprimer une facture précedement contrôlée ou "
+"Vous n'avez pas le droit de supprimer une facture précédemment contrôlée ou "
"invalidée."
-#: models.py:197
+#: models.py:199
msgid "You don't have the right to view someone else's invoices history."
msgstr ""
"Vous n'avez pas le droit de voir l'historique des factures d'un autre "
"utilisateur."
-#: models.py:200
+#: models.py:202
msgid "The invoice has been invalidated."
msgstr "La facture a été invalidée."
-#: models.py:210
+#: models.py:214
msgid "You don't have the right to edit the \"controlled\" state."
msgstr "Vous n'avez pas le droit de modifier le statut \"contrôlé\"."
-#: models.py:224
+#: models.py:228
msgid "There are no payment method which you can use."
msgstr "Il n'y a pas de moyen de paiement que vous puissiez utiliser."
-#: models.py:226
+#: models.py:230
msgid "There are no article that you can buy."
msgstr "Il n'y a pas d'article que vous puissiez acheter."
-#: models.py:261
+#: models.py:272
msgid "Can view a custom invoice object"
msgstr "Peut voir un objet facture personnalisée"
-#: models.py:265 templates/cotisations/aff_custom_invoice.html:36
+#: models.py:276 templates/cotisations/aff_cost_estimate.html:36
+#: templates/cotisations/aff_custom_invoice.html:36
msgid "Recipient"
msgstr "Destinataire"
-#: models.py:269 templates/cotisations/aff_paiement.html:33
+#: models.py:280 templates/cotisations/aff_paiement.html:33
msgid "Payment type"
msgstr "Type de paiement"
-#: models.py:273
+#: models.py:284
msgid "Address"
msgstr "Adresse"
-#: models.py:276 templates/cotisations/aff_custom_invoice.html:54
+#: models.py:287 templates/cotisations/aff_custom_invoice.html:54
msgid "Paid"
msgstr "Payé"
-#: models.py:296 models.py:516 models.py:764
+#: models.py:291
+msgid "Remark"
+msgstr "Remarque"
+
+#: models.py:300
+msgid "Can view a cost estimate object"
+msgstr "Peut voir un objet devis"
+
+#: models.py:303
+msgid "Period of validity"
+msgstr "Période de validité"
+
+#: models.py:340
+msgid "You don't have the right to delete a cost estimate."
+msgstr "Vous n'avez pas le droit de supprimer un devis."
+
+#: models.py:343
+msgid "The cost estimate has an invoice and can't be deleted."
+msgstr "Le devis a une facture et ne peut pas être supprimé."
+
+#: models.py:364 models.py:585 models.py:852
msgid "Connection"
msgstr "Connexion"
-#: models.py:297 models.py:517 models.py:765
+#: models.py:365 models.py:586 models.py:853
msgid "Membership"
msgstr "Adhésion"
-#: models.py:298 models.py:512 models.py:518 models.py:766
+#: models.py:366 models.py:581 models.py:587 models.py:854
msgid "Both of them"
msgstr "Les deux"
-#: models.py:310
+#: models.py:378
msgid "amount"
msgstr "montant"
-#: models.py:315
+#: models.py:383
msgid "article"
msgstr "article"
-#: models.py:322
+#: models.py:390
msgid "price"
msgstr "prix"
-#: models.py:327 models.py:535
+#: models.py:395 models.py:604
msgid "duration (in months)"
msgstr "durée (en mois)"
-#: models.py:335 models.py:549 models.py:780
+#: models.py:403 models.py:618 models.py:868
msgid "subscription type"
msgstr "type de cotisation"
-#: models.py:340
+#: models.py:408
msgid "Can view a purchase object"
msgstr "Peut voir un objet achat"
-#: models.py:341
+#: models.py:409
msgid "Can edit all the previous purchases"
msgstr "Peut modifier tous les achats précédents"
-#: models.py:343 models.py:774
+#: models.py:411 models.py:862
msgid "purchase"
msgstr "achat"
-#: models.py:344
+#: models.py:412
msgid "purchases"
msgstr "achats"
-#: models.py:411 models.py:573
+#: models.py:479 models.py:642
msgid "Duration must be specified for a subscription."
msgstr "La durée de la cotisation doit être indiquée."
-#: models.py:418
+#: models.py:486
msgid "You don't have the right to edit the purchases."
msgstr "Vous n'avez pas le droit de modifier les achats."
-#: models.py:423
+#: models.py:491
msgid "You don't have the right to edit this user's purchases."
msgstr "Vous n'avez pas le droit de modifier les achats de cet utilisateur."
-#: models.py:427
+#: models.py:495
msgid ""
"You don't have the right to edit a purchase already controlled or "
"invalidated."
@@ -285,150 +325,150 @@ msgstr ""
"Vous n'avez pas le droit de modifier un achat précédemment contrôlé ou "
"invalidé."
-#: models.py:434
+#: models.py:502
msgid "You don't have the right to delete a purchase."
msgstr "Vous n'avez pas le droit de supprimer un achat."
-#: models.py:436
+#: models.py:504
msgid "You don't have the right to delete this user's purchases."
msgstr "Vous n'avez pas le droit de supprimer les achats de cet utilisateur."
-#: models.py:439
+#: models.py:507
msgid ""
"You don't have the right to delete a purchase already controlled or "
"invalidated."
msgstr ""
-"Vous n'avez pas le droit de supprimer un achat précédement contrôlé ou "
+"Vous n'avez pas le droit de supprimer un achat précédemment contrôlé ou "
"invalidé."
-#: models.py:447
+#: models.py:515
msgid "You don't have the right to view someone else's purchase history."
msgstr ""
"Vous n'avez pas le droit de voir l'historique des achats d'un autre "
"utilisateur."
-#: models.py:511
+#: models.py:580
msgid "Club"
msgstr "Club"
-#: models.py:523
+#: models.py:592
msgid "designation"
msgstr "désignation"
-#: models.py:529
+#: models.py:598
msgid "unit price"
msgstr "prix unitaire"
-#: models.py:541
+#: models.py:610
msgid "type of users concerned"
msgstr "type d'utilisateurs concernés"
-#: models.py:553 models.py:649
+#: models.py:622 models.py:733
msgid "is available for every user"
msgstr "est disponible pour chaque utilisateur"
-#: models.py:560
+#: models.py:629
msgid "Can view an article object"
msgstr "Peut voir un objet article"
-#: models.py:561
+#: models.py:630
msgid "Can buy every article"
msgstr "Peut acheter chaque article"
-#: models.py:569
+#: models.py:638
msgid "Balance is a reserved article name."
msgstr "Solde est un nom d'article réservé."
-#: models.py:594
+#: models.py:663
msgid "You can't buy this article."
msgstr "Vous ne pouvez pas acheter cet article."
-#: models.py:624
+#: models.py:708
msgid "Can view a bank object"
msgstr "Peut voir un objet banque"
-#: models.py:626
+#: models.py:710
msgid "bank"
msgstr "banque"
-#: models.py:627
+#: models.py:711
msgid "banks"
msgstr "banques"
-#: models.py:645
+#: models.py:729
msgid "method"
msgstr "moyen"
-#: models.py:654
+#: models.py:738
msgid "is user balance"
msgstr "est solde utilisateur"
-#: models.py:655
+#: models.py:739
msgid "There should be only one balance payment method."
msgstr "Il ne devrait y avoir qu'un moyen de paiement solde."
-#: models.py:661
+#: models.py:745
msgid "Can view a payment method object"
msgstr "Peut voir un objet moyen de paiement"
-#: models.py:662
+#: models.py:746
msgid "Can use every payment method"
msgstr "Peut utiliser chaque moyen de paiement"
-#: models.py:664
+#: models.py:748
msgid "payment method"
msgstr "moyen de paiement"
-#: models.py:665
+#: models.py:749
msgid "payment methods"
msgstr "moyens de paiement"
-#: models.py:699 payment_methods/comnpay/views.py:63
+#: models.py:787 payment_methods/comnpay/views.py:63
#, python-format
msgid "The subscription of %(member_name)s was extended to %(end_date)s."
msgstr "La cotisation de %(member_name)s a été étendue au %(end_date)s."
-#: models.py:709
+#: models.py:797
msgid "The invoice was created."
msgstr "La facture a été créée."
-#: models.py:730
+#: models.py:818
msgid "You can't use this payment method."
msgstr "Vous ne pouvez pas utiliser ce moyen de paiement."
-#: models.py:748
+#: models.py:836
msgid "No custom payment method."
msgstr "Pas de moyen de paiement personnalisé."
-#: models.py:783
+#: models.py:871
msgid "start date"
msgstr "date de début"
-#: models.py:786
+#: models.py:874
msgid "end date"
msgstr "date de fin"
-#: models.py:791
+#: models.py:879
msgid "Can view a subscription object"
msgstr "Peut voir un objet cotisation"
-#: models.py:792
+#: models.py:880
msgid "Can edit the previous subscriptions"
msgstr "Peut modifier les cotisations précédentes"
-#: models.py:794
+#: models.py:882
msgid "subscription"
msgstr "cotisation"
-#: models.py:795
+#: models.py:883
msgid "subscriptions"
msgstr "cotisations"
-#: models.py:799
+#: models.py:887
msgid "You don't have the right to edit a subscription."
msgstr "Vous n'avez pas le droit de modifier une cotisation."
-#: models.py:803
+#: models.py:891
msgid ""
"You don't have the right to edit a subscription already controlled or "
"invalidated."
@@ -436,11 +476,11 @@ msgstr ""
"Vous n'avez pas le droit de modifier une cotisation précédemment contrôlée "
"ou invalidée."
-#: models.py:810
+#: models.py:898
msgid "You don't have the right to delete a subscription."
msgstr "Vous n'avez pas le droit de supprimer une cotisation."
-#: models.py:813
+#: models.py:901
msgid ""
"You don't have the right to delete a subscription already controlled or "
"invalidated."
@@ -448,7 +488,7 @@ msgstr ""
"Vous n'avez pas le droit de supprimer une cotisation précédemment contrôlée "
"ou invalidée."
-#: models.py:821
+#: models.py:909
msgid "You don't have the right to view someone else's subscription history."
msgstr ""
"Vous n'avez pas le droit de voir l'historique des cotisations d'un autre "
@@ -482,11 +522,11 @@ msgstr "Le montant maximal d'argent autorisé pour le solde."
msgid "Allow user to credit their balance"
msgstr "Autorise l'utilisateur à créditer son solde"
-#: payment_methods/balance/models.py:81 payment_methods/balance/models.py:112
+#: payment_methods/balance/models.py:79 payment_methods/balance/models.py:110
msgid "Your balance is too low for this operation."
msgstr "Votre solde est trop bas pour cette opération."
-#: payment_methods/balance/models.py:99 validators.py:20
+#: payment_methods/balance/models.py:97 validators.py:20
msgid "There is already a payment method for user balance."
msgstr "Il y a déjà un moyen de paiement pour le solde utilisateur."
@@ -523,11 +563,11 @@ msgstr ""
msgid "Production mode enabled (production URL, instead of homologation)"
msgstr "Mode production activé (URL de production, au lieu d'homologation)"
-#: payment_methods/comnpay/models.py:104
+#: payment_methods/comnpay/models.py:102
msgid "Pay invoice number "
msgstr "Payer la facture numéro "
-#: payment_methods/comnpay/models.py:116
+#: payment_methods/comnpay/models.py:114
msgid ""
"In order to pay your invoice with ComNpay, the price must be greater than {} "
"€."
@@ -559,6 +599,30 @@ msgstr ""
msgid "no"
msgstr "non"
+#: payment_methods/note_kfet/forms.py:32
+msgid "pseudo note"
+msgstr "pseudo note"
+
+#: payment_methods/note_kfet/forms.py:35
+msgid "Password"
+msgstr "Mot de passe"
+
+#: payment_methods/note_kfet/models.py:40
+msgid "NoteKfet"
+msgstr "NoteKfet"
+
+#: payment_methods/note_kfet/models.py:50
+msgid "server"
+msgstr "serveur"
+
+#: payment_methods/note_kfet/views.py:60
+msgid "Unknown error."
+msgstr "Erreur inconnue."
+
+#: payment_methods/note_kfet/views.py:88
+msgid "The payment with note was done."
+msgstr "Le paiement par note a été effectué."
+
#: templates/cotisations/aff_article.html:34
msgid "Price"
msgstr "Prix"
@@ -579,34 +643,47 @@ msgstr "Utilisateurs concernés"
msgid "Available for everyone"
msgstr "Disponible pour tous"
-#: templates/cotisations/aff_article.html:52
-#: templates/cotisations/aff_paiement.html:48
-#: templates/cotisations/control.html:107 views.py:483 views.py:570
-#: views.py:650
-msgid "Edit"
-msgstr "Modifier"
-
#: templates/cotisations/aff_banque.html:32
msgid "Bank"
msgstr "Banque"
-#: templates/cotisations/aff_cotisations.html:38
-msgid "User"
-msgstr "Utilisateur"
-
+#: templates/cotisations/aff_cost_estimate.html:39
#: templates/cotisations/aff_cotisations.html:41
#: templates/cotisations/aff_custom_invoice.html:39
#: templates/cotisations/control.html:63
-#: templates/cotisations/edit_facture.html:45
+#: templates/cotisations/edit_facture.html:49
msgid "Designation"
msgstr "Désignation"
+#: templates/cotisations/aff_cost_estimate.html:40
#: templates/cotisations/aff_cotisations.html:42
#: templates/cotisations/aff_custom_invoice.html:40
#: templates/cotisations/control.html:64
msgid "Total price"
msgstr "Prix total"
+#: templates/cotisations/aff_cost_estimate.html:50
+msgid "Validity"
+msgstr "Validité"
+
+#: templates/cotisations/aff_cost_estimate.html:54
+msgid "Cost estimate ID"
+msgstr "ID devis"
+
+#: templates/cotisations/aff_cost_estimate.html:58
+msgid "Invoice created"
+msgstr "Facture créée"
+
+#: templates/cotisations/aff_cost_estimate.html:91
+#: templates/cotisations/aff_cotisations.html:81
+#: templates/cotisations/aff_custom_invoice.html:79
+msgid "PDF"
+msgstr "PDF"
+
+#: templates/cotisations/aff_cotisations.html:38
+msgid "User"
+msgstr "Utilisateur"
+
#: templates/cotisations/aff_cotisations.html:52
#: templates/cotisations/aff_custom_invoice.html:50
#: templates/cotisations/control.html:56
@@ -617,11 +694,6 @@ msgstr "ID facture"
msgid "Controlled invoice"
msgstr "Facture contrôlée"
-#: templates/cotisations/aff_cotisations.html:81
-#: templates/cotisations/aff_custom_invoice.html:79
-msgid "PDF"
-msgstr "PDF"
-
#: templates/cotisations/aff_cotisations.html:84
msgid "Invalidated invoice"
msgstr "Facture invalidée"
@@ -666,6 +738,11 @@ msgstr "Validé"
msgid "Controlled"
msgstr "Contrôlé"
+#: templates/cotisations/control.html:107 views.py:642 views.py:729
+#: views.py:809
+msgid "Edit"
+msgstr "Modifier"
+
#: templates/cotisations/delete.html:29
msgid "Deletion of subscriptions"
msgstr "Suppression de cotisations"
@@ -676,33 +753,33 @@ msgid ""
"Warning: are you sure you really want to delete this %(object_name)s object "
"( %(objet)s )?"
msgstr ""
-"\tAttention: voulez-vous vraiment supprimer cet objet %(object_name)s "
+"Attention: voulez-vous vraiment supprimer cet objet %(object_name)s "
"( %(objet)s ) ?"
#: templates/cotisations/delete.html:38
-#: templates/cotisations/edit_facture.html:60
-#: views.py:181 views.py:235
+#: templates/cotisations/edit_facture.html:64 views.py:178 views.py:228
+#: views.py:280
msgid "Confirm"
-msgstr "Valider"
+msgstr "Confirmer"
#: templates/cotisations/edit_facture.html:31
#: templates/cotisations/facture.html:30
msgid "Creation and editing of invoices"
msgstr "Création et modification de factures"
-#: templates/cotisations/edit_facture.html:38
-msgid "Edit the invoice"
+#: templates/cotisations/edit_facture.html:41
+msgid "Edit invoice"
msgstr "Modifier la facture"
-#: templates/cotisations/edit_facture.html:41
-#: templates/cotisations/facture.html:56
+#: templates/cotisations/edit_facture.html:45
+#: templates/cotisations/facture.html:62
#: templates/cotisations/index_article.html:30
msgid "Articles"
msgstr "Articles"
#: templates/cotisations/facture.html:37
msgid "Buy"
-msgstr "Acheter une cotisation"
+msgstr "Acheter"
#: templates/cotisations/facture.html:40
#, python-format
@@ -714,11 +791,11 @@ msgstr "Solde maximum autorisé : %(max_balance)s €"
msgid "Current balance: %(balance)s €"
msgstr "Solde actuel : %(balance)s €"
-#: templates/cotisations/facture.html:70
+#: templates/cotisations/facture.html:76
msgid "Add an extra article"
msgstr "Ajouter un article supplémentaire"
-#: templates/cotisations/facture.html:72
+#: templates/cotisations/facture.html:82
msgid "Total price: 0,00 €"
msgstr "Prix total : 0,00 €"
@@ -730,9 +807,8 @@ msgstr "Factures"
msgid "Subscriptions"
msgstr "Cotisations"
-
#: templates/cotisations/index_article.html:33
-msgid "Article types list"
+msgid "List of article types"
msgstr "Liste des types d'article"
#: templates/cotisations/index_article.html:36
@@ -744,12 +820,12 @@ msgid "Delete one or several article types"
msgstr "Supprimer un ou plusieurs types d'article"
#: templates/cotisations/index_banque.html:30
-#: templates/cotisations/sidebar.html:55
+#: templates/cotisations/sidebar.html:60
msgid "Banks"
msgstr "Banques"
#: templates/cotisations/index_banque.html:33
-msgid "Banks list"
+msgid "List of banks"
msgstr "Liste des banques"
#: templates/cotisations/index_banque.html:36
@@ -760,17 +836,26 @@ msgstr "Ajouter une banque"
msgid "Delete one or several banks"
msgstr "Supprimer une ou plusieurs banques"
+#: templates/cotisations/index_cost_estimate.html:28
+#: templates/cotisations/sidebar.html:50
+msgid "Cost estimates"
+msgstr "Devis"
+
+#: templates/cotisations/index_cost_estimate.html:31
+msgid "List of cost estimates"
+msgstr "Liste des devis"
+
#: templates/cotisations/index_custom_invoice.html:28
#: templates/cotisations/sidebar.html:45
msgid "Custom invoices"
msgstr "Factures personnalisées"
#: templates/cotisations/index_custom_invoice.html:31
-msgid "Custom invoices list"
-msgstr "Liste des factures personalisées"
+msgid "List of custom invoices"
+msgstr "Liste des factures personnalisées"
#: templates/cotisations/index_paiement.html:30
-#: templates/cotisations/sidebar.html:60
+#: templates/cotisations/sidebar.html:65
msgid "Payment methods"
msgstr "Moyens de paiement"
@@ -793,9 +878,9 @@ msgstr "Rechargement de solde"
#: templates/cotisations/payment.html:34
#, python-format
msgid "Pay %(amount)s €"
-msgstr "Recharger de %(amount)s €"
+msgstr "Payer %(amount)s €"
-#: templates/cotisations/payment.html:42 views.py:870
+#: templates/cotisations/payment.html:42 views.py:1049
msgid "Pay"
msgstr "Payer"
@@ -807,81 +892,104 @@ msgstr "Créer une facture"
msgid "Control the invoices"
msgstr "Contrôler les factures"
-#: views.py:167
+#: views.py:164
msgid "You need to choose at least one article."
msgstr "Vous devez choisir au moins un article."
+#: views.py:222
+msgid "The cost estimate was created."
+msgstr "Le devis a été créé."
-#: views.py:228
+#: views.py:232 views.py:534
+msgid "Cost estimate"
+msgstr "Devis"
+
+#: views.py:274
msgid "The custom invoice was created."
msgstr "La facture personnalisée a été créée."
-#: views.py:316 views.py:370
+#: views.py:363 views.py:466
msgid "The invoice was edited."
msgstr "La facture a été modifiée."
-#: views.py:336 views.py:430
+#: views.py:383 views.py:589
msgid "The invoice was deleted."
msgstr "La facture a été supprimée."
-#: views.py:341 views.py:435
+#: views.py:388 views.py:594
msgid "Invoice"
msgstr "Facture"
-#: views.py:456
+#: views.py:417
+msgid "The cost estimate was edited."
+msgstr "Le devis a été modifié."
+
+#: views.py:424
+msgid "Edit cost estimate"
+msgstr "Modifier le devis"
+
+#: views.py:436
+msgid "An invoice was successfully created from your cost estimate."
+msgstr "Une facture a bien été créée à partir de votre devis."
+
+#: views.py:529
+msgid "The cost estimate was deleted."
+msgstr "Le devis a été supprimé."
+
+#: views.py:615
msgid "The article was created."
msgstr "L'article a été créé."
-#: views.py:461 views.py:534 views.py:627
+#: views.py:620 views.py:693 views.py:786
msgid "Add"
msgstr "Ajouter"
-#: views.py:462
+#: views.py:621
msgid "New article"
msgstr "Nouvel article"
-#: views.py:478
+#: views.py:637
msgid "The article was edited."
msgstr "L'article a été modifié."
-#: views.py:484
+#: views.py:643
msgid "Edit article"
msgstr "Modifier l'article"
-#: views.py:500
+#: views.py:659
msgid "The articles were deleted."
msgstr "Les articles ont été supprimés."
-#: views.py:505 views.py:605 views.py:685
+#: views.py:664 views.py:764 views.py:844
msgid "Delete"
msgstr "Supprimer"
-#: views.py:506
+#: views.py:665
msgid "Delete article"
msgstr "Supprimer l'article"
-#: views.py:528
+#: views.py:687
msgid "The payment method was created."
msgstr "Le moyen de paiment a été créé."
-#: views.py:535
+#: views.py:694
msgid "New payment method"
msgstr "Nouveau moyen de paiement"
-#: views.py:564
+#: views.py:723
msgid "The payment method was edited."
msgstr "Le moyen de paiment a été modifié."
-#: views.py:571
+#: views.py:730
msgid "Edit payment method"
msgstr "Modifier le moyen de paiement"
-#: views.py:590
+#: views.py:749
#, python-format
msgid "The payment method %(method_name)s was deleted."
msgstr "Le moyen de paiement %(method_name)s a été supprimé."
-#: views.py:597
+#: views.py:756
#, python-format
msgid ""
"The payment method %(method_name)s can't be deleted "
@@ -890,52 +998,51 @@ msgstr ""
"Le moyen de paiement %(method_name)s ne peut pas être supprimé car il y a "
"des factures qui l'utilisent."
-#: views.py:606
+#: views.py:765
msgid "Delete payment method"
msgstr "Supprimer le moyen de paiement"
-#: views.py:622
+#: views.py:781
msgid "The bank was created."
msgstr "La banque a été créée."
-#: views.py:628
+#: views.py:787
msgid "New bank"
msgstr "Nouvelle banque"
-#: views.py:645
+#: views.py:804
msgid "The bank was edited."
msgstr "La banque a été modifiée."
-#: views.py:651
+#: views.py:810
msgid "Edit bank"
msgstr "Modifier la banque"
-#: views.py:670
+#: views.py:829
#, python-format
msgid "The bank %(bank_name)s was deleted."
msgstr "La banque %(bank_name)s a été supprimée."
-#: views.py:677
+#: views.py:836
#, python-format
msgid ""
-"The bank %(bank_name)s can't be deleted because there "
-"are invoices using it."
+"The bank %(bank_name)s can't be deleted because there are invoices using it."
msgstr ""
"La banque %(bank_name)s ne peut pas être supprimée car il y a des factures "
"qui l'utilisent."
-#: views.py:686
+#: views.py:845
msgid "Delete bank"
msgstr "Supprimer la banque"
-#: views.py:722
+#: views.py:881
msgid "Your changes have been properly taken into account."
msgstr "Vos modifications ont correctement été prises en compte."
-#: views.py:834
+#: views.py:1016
msgid "You are not allowed to credit your balance."
msgstr "Vous n'êtes pas autorisés à créditer votre solde."
-#: views.py:869
+#: views.py:1048
msgid "Refill your balance"
msgstr "Recharger votre solde"
diff --git a/cotisations/migrations/0036_custominvoice_remark.py b/cotisations/migrations/0036_custominvoice_remark.py
new file mode 100644
index 00000000..7719b31d
--- /dev/null
+++ b/cotisations/migrations/0036_custominvoice_remark.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-12-29 14:22
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cotisations', '0035_notepayment'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='custominvoice',
+ name='remark',
+ field=models.TextField(blank=True, null=True, verbose_name='Remark'),
+ ),
+ ]
diff --git a/cotisations/migrations/0037_costestimate.py b/cotisations/migrations/0037_costestimate.py
new file mode 100644
index 00000000..3d97f3f3
--- /dev/null
+++ b/cotisations/migrations/0037_costestimate.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-12-29 21:03
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cotisations', '0036_custominvoice_remark'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='CostEstimate',
+ fields=[
+ ('custominvoice_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cotisations.CustomInvoice')),
+ ('validity', models.DurationField(verbose_name='Period of validity')),
+ ('final_invoice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='origin_cost_estimate', to='cotisations.CustomInvoice')),
+ ],
+ options={
+ 'permissions': (('view_costestimate', 'Can view a cost estimate object'),),
+ },
+ bases=('cotisations.custominvoice',),
+ ),
+ ]
diff --git a/cotisations/migrations/0038_auto_20181231_1657.py b/cotisations/migrations/0038_auto_20181231_1657.py
new file mode 100644
index 00000000..a9415bf0
--- /dev/null
+++ b/cotisations/migrations/0038_auto_20181231_1657.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-12-31 22:57
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cotisations', '0037_costestimate'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='costestimate',
+ name='final_invoice',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='origin_cost_estimate', to='cotisations.CustomInvoice'),
+ ),
+ migrations.AlterField(
+ model_name='costestimate',
+ name='validity',
+ field=models.DurationField(help_text='DD HH:MM:SS', verbose_name='Period of validity'),
+ ),
+ migrations.AlterField(
+ model_name='custominvoice',
+ name='paid',
+ field=models.BooleanField(default=False, verbose_name='Paid'),
+ ),
+ ]
diff --git a/cotisations/models.py b/cotisations/models.py
index 6dd63b6f..c6b7cd1c 100644
--- a/cotisations/models.py
+++ b/cotisations/models.py
@@ -46,11 +46,14 @@ from django.urls import reverse
from django.shortcuts import redirect
from django.contrib import messages
+from preferences.models import CotisationsOption
from machines.models import regen
from re2o.field_permissions import FieldPermissionModelMixin
from re2o.mixins import AclMixin, RevMixin
-from cotisations.utils import find_payment_method, send_mail_invoice
+from cotisations.utils import (
+ find_payment_method, send_mail_invoice, send_mail_voucher
+)
from cotisations.validators import check_no_balance
@@ -236,15 +239,35 @@ class Facture(BaseInvoice):
'control': self.can_change_control,
}
self.__original_valid = self.valid
+ self.__original_control = self.control
+
+ def get_subscription(self):
+ """Returns every subscription associated with this invoice."""
+ return Cotisation.objects.filter(
+ vente__in=self.vente_set.filter(
+ Q(type_cotisation='All') |
+ Q(type_cotisation='Adhesion')
+ )
+ )
+
+ def is_subscription(self):
+ """Returns True if this invoice contains at least one subscribtion."""
+ return bool(self.get_subscription())
def save(self, *args, **kwargs):
super(Facture, self).save(*args, **kwargs)
if not self.__original_valid and self.valid:
send_mail_invoice(self)
+ if self.is_subscription() \
+ and not self.__original_control \
+ and self.control \
+ and CotisationsOption.get_cached_value('send_voucher_mail'):
+ send_mail_voucher(self)
def __str__(self):
return str(self.user) + ' ' + str(self.date)
+
@receiver(post_save, sender=Facture)
def facture_post_save(**kwargs):
"""
@@ -284,8 +307,65 @@ class CustomInvoice(BaseInvoice):
verbose_name=_("Address")
)
paid = models.BooleanField(
- verbose_name=_("Paid")
+ verbose_name=_("Paid"),
+ default=False
)
+ remark = models.TextField(
+ verbose_name=_("Remark"),
+ blank=True,
+ null=True
+ )
+
+
+class CostEstimate(CustomInvoice):
+ class Meta:
+ permissions = (
+ ('view_costestimate', _("Can view a cost estimate object")),
+ )
+ validity = models.DurationField(
+ verbose_name=_("Period of validity"),
+ help_text="DD HH:MM:SS"
+ )
+ final_invoice = models.ForeignKey(
+ CustomInvoice,
+ on_delete=models.SET_NULL,
+ null=True,
+ blank=True,
+ related_name="origin_cost_estimate",
+ primary_key=False
+ )
+
+ def create_invoice(self):
+ """Create a CustomInvoice from the CostEstimate."""
+ if self.final_invoice is not None:
+ return self.final_invoice
+ invoice = CustomInvoice()
+ invoice.recipient = self.recipient
+ invoice.payment = self.payment
+ invoice.address = self.address
+ invoice.paid = False
+ invoice.remark = self.remark
+ invoice.date = timezone.now()
+ invoice.save()
+ self.final_invoice = invoice
+ self.save()
+ for sale in self.vente_set.all():
+ Vente.objects.create(
+ facture=invoice,
+ name=sale.name,
+ prix=sale.prix,
+ number=sale.number,
+ )
+ return invoice
+
+ def can_delete(self, user_request, *args, **kwargs):
+ if not user_request.has_perm('cotisations.delete_costestimate'):
+ return False, _("You don't have the right "
+ "to delete a cost estimate.")
+ if self.final_invoice is not None:
+ return False, _("The cost estimate has an "
+ "invoice and can't be deleted.")
+ return True, None
# TODO : change Vente to Purchase
@@ -624,7 +704,7 @@ class Article(RevMixin, AclMixin, models.Model):
objects_pool = cls.objects.filter(
Q(type_user='All') | Q(type_user='Adherent')
)
- if not target_user.is_adherent():
+ if target_user is not None and not target_user.is_adherent():
objects_pool = objects_pool.filter(
Q(type_cotisation='All') | Q(type_cotisation='Adhesion')
)
@@ -718,7 +798,7 @@ class Paiement(RevMixin, AclMixin, models.Model):
if payment_method is not None and use_payment_method:
return payment_method.end_payment(invoice, request)
- ## So make this invoice valid, trigger send mail
+ # So make this invoice valid, trigger send mail
invoice.valid = True
invoice.save()
diff --git a/cotisations/payment_methods/note_kfet/views.py b/cotisations/payment_methods/note_kfet/views.py
index d4d0ac21..cfdda9b0 100644
--- a/cotisations/payment_methods/note_kfet/views.py
+++ b/cotisations/payment_methods/note_kfet/views.py
@@ -57,7 +57,7 @@ def note_payment(request, facture, factureid):
user = facture.user
payment_method = find_payment_method(facture.paiement)
if not payment_method or not isinstance(payment_method, NotePayment):
- messages.error(request, "Erreur inconnue")
+ messages.error(request, _("Unknown error."))
return redirect(reverse(
'users:profil',
kwargs={'userid': user.id}
@@ -85,7 +85,7 @@ def note_payment(request, facture, factureid):
)
facture.valid = True
facture.save()
- messages.success(request, "Le paiement par note a bien été effectué")
+ messages.success(request, _("The payment with note was done."))
return redirect(reverse(
'users:profil',
kwargs={'userid': user.id}
diff --git a/cotisations/templates/cotisations/aff_article.html b/cotisations/templates/cotisations/aff_article.html
index b07035da..682d6a05 100644
--- a/cotisations/templates/cotisations/aff_article.html
+++ b/cotisations/templates/cotisations/aff_article.html
@@ -49,9 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{{ article.available_for_everyone | tick }} |
{% can_edit article %}
-
-
-
+ {% include 'buttons/edit.html' with href='cotisations:edit-article' id=article.id %}
{% acl_end %}
{% history_button article %}
|
diff --git a/cotisations/templates/cotisations/aff_cost_estimate.html b/cotisations/templates/cotisations/aff_cost_estimate.html
new file mode 100644
index 00000000..eb040dce
--- /dev/null
+++ b/cotisations/templates/cotisations/aff_cost_estimate.html
@@ -0,0 +1,101 @@
+{% comment %}
+Re2o est un logiciel d'administration développé initiallement au rezometz. Il
+se veut agnostique au réseau considéré, de manière à être installable en
+quelques clics.
+
+Copyright © 2018 Hugo Levy-Falk
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+{% endcomment %}
+{% load i18n %}
+{% load acl %}
+{% load logs_extra %}
+{% load design %}
+
+
+ {% if cost_estimate_list.paginator %}
+ {% include 'pagination.html' with list=cost_estimate_list%}
+ {% endif %}
+
+
+
+
+
+ {% trans "Recipient" as tr_recip %}
+ {% include 'buttons/sort.html' with prefix='invoice' col='user' text=tr_recip %}
+ |
+ {% trans "Designation" %} |
+ {% trans "Total price" %} |
+
+ {% trans "Payment method" as tr_payment_method %}
+ {% include 'buttons/sort.html' with prefix='invoice' col='payement' text=tr_payment_method %}
+ |
+
+ {% trans "Date" as tr_date %}
+ {% include 'buttons/sort.html' with prefix='invoice' col='date' text=tr_date %}
+ |
+
+ {% trans "Validity" as tr_validity %}
+ {% include 'buttons/sort.html' with prefix='invoice' col='validity' text=tr_validity %}
+ |
+
+ {% trans "Cost estimate ID" as tr_estimate_id %}
+ {% include 'buttons/sort.html' with prefix='invoice' col='id' text=tr_estimate_id %}
+ |
+
+ {% trans "Invoice created" as tr_invoice_created%}
+ {% include 'buttons/sort.html' with prefix='invoice' col='paid' text=tr_invoice_created %}
+ |
+ |
+ |
+
+
+ {% for estimate in cost_estimate_list %}
+
+ {{ estimate.recipient }} |
+ {{ estimate.name }} |
+ {{ estimate.prix_total }} |
+ {{ estimate.payment }} |
+ {{ estimate.date }} |
+ {{ estimate.validity }} |
+ {{ estimate.id }} |
+
+ {% if estimate.final_invoice %}
+
+ {% else %}
+
+ {% endif %}
+ |
+
+ {% can_edit estimate %}
+ {% include 'buttons/edit.html' with href='cotisations:edit-cost-estimate' id=estimate.id %}
+ {% acl_end %}
+ {% history_button estimate %}
+ {% include 'buttons/suppr.html' with href='cotisations:del-cost-estimate' id=estimate.id %}
+
+
+
+
+ {% trans "PDF" %}
+
+ |
+
+ {% endfor %}
+
+
+ {% if custom_invoice_list.paginator %}
+ {% include 'pagination.html' with list=custom_invoice_list %}
+ {% endif %}
+
diff --git a/cotisations/templates/cotisations/aff_cotisations.html b/cotisations/templates/cotisations/aff_cotisations.html
index 93384106..e27ae8c7 100644
--- a/cotisations/templates/cotisations/aff_cotisations.html
+++ b/cotisations/templates/cotisations/aff_cotisations.html
@@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% if facture_list.paginator %}
-{% include 'pagination.html' with list=facture_list %}
+ {% include 'pagination.html' with list=facture_list %}
{% endif %}
@@ -48,7 +48,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% trans "Date" as tr_date %}
{% include 'buttons/sort.html' with prefix='cotis' col='date' text=tr_date %}
-
+ |
{% trans "Invoice ID" as tr_invoice_id %}
{% include 'buttons/sort.html' with prefix='cotis' col='id' text=tr_invoice_id %}
|
@@ -63,17 +63,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{{ facture.prix_total }} |
{{ facture.paiement }} |
{{ facture.date }} |
- {{ facture.id }} |
+ {{ facture.id }} |
{% can_edit facture %}
- {% include 'buttons/edit.html' with href='cotisations:edit-facture' id=facture.id %}
+ {% include 'buttons/edit.html' with href='cotisations:edit-facture' id=facture.id %}
{% acl_else %}
{% trans "Controlled invoice" %}
{% acl_end %}
{% can_delete facture %}
- {% include 'buttons/suppr.html' with href='cotisations:del-facture' id=facture.id %}
+ {% include 'buttons/suppr.html' with href='cotisations:del-facture' id=facture.id %}
{% acl_end %}
- {% history_button facture %}
+ {% history_button facture %}
|
{% if facture.valid %}
@@ -83,13 +83,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% else %}
{% trans "Invalidated invoice" %}
{% endif %}
+ {% if facture.control and facture.is_subscription %}
+
+ {% trans "Voucher" %}
+
+ {% endif %}
|
{% endfor %}
-{% if facture_list.paginator %}
-{% include 'pagination.html' with list=facture_list %}
-{% endif %}
+ {% if facture_list.paginator %}
+ {% include 'pagination.html' with list=facture_list %}
+ {% endif %}
diff --git a/cotisations/templates/cotisations/aff_custom_invoice.html b/cotisations/templates/cotisations/aff_custom_invoice.html
index 0f4605ad..c1c5a396 100644
--- a/cotisations/templates/cotisations/aff_custom_invoice.html
+++ b/cotisations/templates/cotisations/aff_custom_invoice.html
@@ -26,7 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% if custom_invoice_list.paginator %}
- {% include 'pagination.html' with list=custom_invoice_list %}
+ {% include 'pagination.html' with list=custom_invoice_list %}
{% endif %}
@@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% trans "Recipient" as tr_recip %}
- {% include 'buttons/sort.html' with prefix='invoice' col='user' text=tr_user %}
+ {% include 'buttons/sort.html' with prefix='invoice' col='user' text=tr_recip %}
|
{% trans "Designation" %} |
{% trans "Total price" %} |
@@ -51,7 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% include 'buttons/sort.html' with prefix='invoice' col='id' text=tr_invoice_id %}
- {% trans "Paid" as tr_invoice_paid%}
+ {% trans "Paid" as tr_invoice_paid %}
{% include 'buttons/sort.html' with prefix='invoice' col='paid' text=tr_invoice_paid %}
|
|
@@ -84,6 +84,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% if custom_invoice_list.paginator %}
- {% include 'pagination.html' with list=custom_invoice_list %}
+ {% include 'pagination.html' with list=custom_invoice_list %}
{% endif %}
diff --git a/cotisations/templates/cotisations/aff_paiement.html b/cotisations/templates/cotisations/aff_paiement.html
index 633eb456..afb78b48 100644
--- a/cotisations/templates/cotisations/aff_paiement.html
+++ b/cotisations/templates/cotisations/aff_paiement.html
@@ -45,9 +45,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% can_edit paiement %}
-
-
-
+ {% include 'buttons/edit.html' with href='cotisations:edit-paiement' id=paiement.id %}
{% acl_end %}
{% history_button paiement %}
|
diff --git a/cotisations/templates/cotisations/control.html b/cotisations/templates/cotisations/control.html
index 483c150c..5a18bd01 100644
--- a/cotisations/templates/cotisations/control.html
+++ b/cotisations/templates/cotisations/control.html
@@ -1,4 +1,4 @@
-{% extends "cotisations/sidebar.html" %}
+{% extends 'cotisations/sidebar.html' %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
@@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% trans "Invoice control and validation" %}
{% if facture_list.paginator %}
-{% include 'pagination.html' with list=facture_list %}
+ {% include 'pagination.html' with list=facture_list %}
{% endif %}
{% endif %}
+{% if factureform %}
+{% bootstrap_form_errors factureform %}
+{% endif %}
+{% if discount_form %}
+{% bootstrap_form_errors discount_form %}
+{% endif %}