8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-05 09:26:27 +00:00

Merge branch 'fix_93_store_custom_invoices' into 'dev'

Fix 93 store custom invoices

See merge request federez/re2o!203
This commit is contained in:
klafyvel 2018-08-16 00:39:09 +02:00
commit 3cae0564ef
16 changed files with 891 additions and 447 deletions

View file

@ -136,3 +136,17 @@ Fix several issues with email accounts, you need to collect the static files.
```bash
./manage.py collectstatic
```
## MR 203 Add custom invoices
The custom invoices are now stored in database. You need to migrate your database :
```bash
python3 manage.py migrate
```
On some database engines (postgreSQL) you also need to update the id sequences:
```bash
python3 manage.py sqlsequencereset cotisations | python3 manage.py dbshell
```

View file

@ -30,6 +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
class FactureAdmin(VersionAdmin):
@ -37,6 +38,11 @@ class FactureAdmin(VersionAdmin):
pass
class CustomInvoiceAdmin(VersionAdmin):
"""Admin class for custom invoices."""
pass
class VenteAdmin(VersionAdmin):
"""Class admin d'une vente, tous les champs (facture related)"""
pass
@ -69,3 +75,4 @@ admin.site.register(Banque, BanqueAdmin)
admin.site.register(Paiement, PaiementAdmin)
admin.site.register(Vente, VenteAdmin)
admin.site.register(Cotisation, CotisationAdmin)
admin.site.register(CustomInvoice, CustomInvoiceAdmin)

View file

@ -46,7 +46,7 @@ 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
from .models import Article, Paiement, Facture, Banque, CustomInvoice
from .payment_methods import balance
@ -131,24 +131,13 @@ class SelectClubArticleForm(Form):
self.fields['article'].queryset = Article.find_allowed_articles(user)
# TODO : change Facture to Invoice
class NewFactureFormPdf(Form):
class CustomInvoiceForm(FormRevMixin, ModelForm):
"""
Form used to create a custom PDF invoice.
Form used to create a custom invoice.
"""
paid = forms.BooleanField(label=_l("Paid"), required=False)
# TODO : change dest field to recipient
dest = forms.CharField(
required=True,
max_length=255,
label=_l("Recipient")
)
# TODO : change chambre field to address
chambre = forms.CharField(
required=False,
max_length=10,
label=_l("Address")
)
class Meta:
model = CustomInvoice
fields = '__all__'
class ArticleForm(FormRevMixin, ModelForm):

View file

@ -21,9 +21,9 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-05-10 15:21-0500\n"
"POT-Creation-Date: 2018-07-25 23:22+0200\n"
"PO-Revision-Date: 2018-03-31 16:09+0002\n"
"Last-Translator: Maël Kervella <dev@maelkervella.eu>\n"
"Last-Translator: Hugo Levy-Falk <me@klafyvel.me>\n"
"Language-Team: \n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
@ -34,74 +34,39 @@ msgstr ""
msgid "You don't have the rights to see this application."
msgstr "Vous n'avez pas les droits de voir cette application."
#: forms.py:63 forms.py:321
msgid "Cheque number"
msgstr "Numéro de chèque"
#: forms.py:64 forms.py:322
msgid "Not specified"
msgstr "Non renseigné"
#: forms.py:66 forms.py:324
#: forms.py:63 forms.py:274
msgid "Select a payment method"
msgstr "Sélectionnez un moyen de paiement"
#: forms.py:83 forms.py:347
msgid "A payment method must be specified."
msgstr "Un moyen de paiement doit être renseigné."
#: forms.py:87 forms.py:352
msgid "A cheque number and a bank must be specified."
msgstr "Un numéro de chèqe et une banque doivent être renseignés."
#: forms.py:184
#: forms.py:66
msgid "Member"
msgstr "Adhérent"
#: forms.py:186
#: forms.py:68
msgid "Select the proprietary member"
msgstr "Sélectionnez l'adhérent propriétaire"
#: forms.py:187
#: forms.py:69
msgid "Validated invoice"
msgstr "Facture validée"
#: forms.py:201
#: forms.py:82
msgid "A payment method must be specified."
msgstr "Un moyen de paiement doit être renseigné."
#: forms.py:154
msgid "Article name"
msgstr "Nom de l'article"
#: forms.py:239
#: forms.py:192
msgid "Payment method name"
msgstr "Nom du moyen de paiement"
#: forms.py:240
msgid "Payment type"
msgstr "Type de paiement"
#: forms.py:242
msgid ""
"The payement type is used for specific behaviour. The \"cheque\" "
"type means a cheque number and a bank name may be added when "
"using this payment method."
msgstr ""
"Le type de paiement est utilisé pour des comportements spécifiques. Le type "
"\"chèque\" permet de spécifier un numéro de chèque et une banque lors de "
"l'utilisation de cette méthode."
#: forms.py:282
#: forms.py:230
msgid "Bank name"
msgstr "Nom de la banque"
#: forms.py:380
#, python-format
msgid ""
"Requested amount is too small. Minimum amount possible : "
"%(min_online_amount)s €."
msgstr ""
"Montant demandé est trop faible. Montant minimal possible : "
"%(min_online_amount)s €"
#: forms.py:390
#: forms.py:287
#, python-format
msgid ""
"Requested amount is too high. Your balance can't exceed "
@ -110,15 +75,15 @@ msgstr ""
"Montant demandé trop grand. Votre solde ne peut excéder "
"%(max_online_balance)s €"
#: models.py:165 models.py:213
#: models.py:175 models.py:223
msgid "You don't have the right to edit an invoice."
msgstr "Vous n'avez pas le droit de modifier une facture."
#: models.py:168
#: models.py:178
msgid "You don't have the right to edit this user's invoices."
msgstr "Vous n'avez pas le droit de modifier les facture de cette utilisateur."
#: models.py:172
#: models.py:182
msgid ""
"You don't have the right to edit an invoice already controlled or "
"invalidated."
@ -126,15 +91,15 @@ msgstr ""
"Vous n'avez pas le droit de modifier une facture précedement controllée ou "
"invalidée."
#: models.py:179
#: models.py:189
msgid "You don't have the right to delete an invoice."
msgstr "Vous n'avez pas le droit de supprimer une facture."
#: models.py:181
#: models.py:191
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:184
#: models.py:194
msgid ""
"You don't have the right to delete an invoice already controlled or "
"invalidated."
@ -142,35 +107,41 @@ msgstr ""
"Vous n'avez pas le droit de supprimer une facture précedement controllée ou "
"invalidée."
#: models.py:192
#: models.py:202
msgid "You don't have the right to see someone else's invoices history."
msgstr ""
"Vous n'avez pas le droit de voir l'historique de la facture de quelqu'un "
"d'autre."
#: models.py:195
#: models.py:205
msgid "The invoice has been invalidated."
msgstr "La facture a été invalidée."
#: models.py:205
#, fuzzy
#| msgid "You don't have the right to edit the controlled state."
#: models.py:215
msgid "You don't have the right to edit the \"controlled\" state."
msgstr "Vous n'avez pas le droit de modifier l'état \"controllé\"."
#: models.py:372
#: models.py:237
msgid "There are no payment types which you can use."
msgstr "Il n'y a pas de type de paiement que vous puissiez utiliser."
#: models.py:239
msgid "There are no article that you can buy."
msgstr "Il n'y a pas d'article qui vous soit autorisé."
#: models.py:424
msgid "A cotisation should always have a duration."
msgstr "Une cotisation devrait toujours avoir une durée."
#: models.py:379
#: models.py:431
msgid "You don't have the right to edit the purchases."
msgstr "Vous n'avez pas le droit de modifier les achats."
#: models.py:384
#: models.py:436
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:388
#: models.py:440
msgid ""
"You don't have the right to edit a purchase already controlled or "
"invalidated."
@ -178,15 +149,15 @@ msgstr ""
"Vous n'avez pas le droit de modifier un achat précédement controllé ou "
"invalidé."
#: models.py:395
#: models.py:447
msgid "You don't have the right to delete a purchase."
msgstr "Vous n'avez pas le droit de supprimer un achat."
#: models.py:397
#: models.py:449
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:400
#: models.py:452
msgid ""
"You don't have the right to delete a purchase already controlled or "
"invalidated."
@ -194,29 +165,46 @@ msgstr ""
"Vous n'avez pas le droit de supprimer un achat précédement controllé ou "
"invalidé."
#: models.py:408
#: models.py:460
msgid "You don't have the right to see someone else's purchase history."
msgstr ""
"Vous n'avez pas le droit de voir l'historique d'un achat de quelqu'un "
"d'autre."
#: models.py:517
#: models.py:582
msgid "Solde is a reserved article name"
msgstr "Solde est un nom d'article réservé"
#: models.py:521
#: models.py:586
msgid "Duration must be specified for a cotisation"
msgstr "La durée doit être spécifiée pour une cotisation"
#: models.py:603
msgid "You cannot have multiple payment method of type cheque"
msgstr "Vous ne pouvez avoir plusieurs moyens de paiement de type chèque"
#: models.py:607
msgid "You cannot buy this Article."
msgstr "Vous ne pouvez pas acheter cet article."
#: models.py:654
#: models.py:713 payment_methods/comnpay/views.py:63
msgid ""
"The cotisation of %(member_name)s has been extended to %(end_date)s."
msgstr "La cotisation de %(member_name)s a été étendu jusqu'à %(end_date)s."
#: models.py:723
msgid "The invoice has been created."
msgstr "La facture a été créée."
#: models.py:744
msgid "You cannot use this Payment."
msgstr "Vous ne pouvez pas utiliser ce Paiement."
#: models.py:762
msgid "No custom payment method"
msgstr "Pas de méthode de paiement personnalisée"
#: models.py:811
msgid "You don't have the right to edit a cotisation."
msgstr "Vous n'avez pas le droit de modifier une cotisation."
#: models.py:658
#: models.py:815
msgid ""
"You don't have the right to edit a cotisation already controlled or "
"invalidated."
@ -224,11 +212,11 @@ msgstr ""
"Vous n'avez pas le droit de modifier une cotisaiton précédement controllée "
"ou invalidée."
#: models.py:665
#: models.py:822
msgid "You don't have the right to delete a cotisation."
msgstr "Vous n'avez pas le droit de supprimer une cotisation."
#: models.py:668
#: models.py:825
msgid ""
"You don't have the right to delete a cotisation already controlled or "
"invalidated."
@ -236,113 +224,159 @@ msgstr ""
"Vous n'avez pas le droit de supprimer une cotisation précédement controllée "
"ou invalidée."
#: models.py:676
#: models.py:833
msgid "You don't have the right to see someone else's cotisation history."
msgstr ""
"Vous n'avez pas le droit de voir l'historique d'une cotisation de quelqu'un "
"d'autre."
#: payment.py:31
#: payment_methods/balance/models.py:82 payment_methods/balance/models.py:113
msgid "Your balance is too low for this operation."
msgstr "Votre solde est trop faible pour cette opération."
#: payment_methods/balance/models.py:100
msgid "There is already a payment type for user balance"
msgstr "Il y a déjà un type de paiement pour le solde utilisateur"
#: payment_methods/cheque/views.py:47
msgid "You cannot pay this invoice with a cheque."
msgstr "Vous ne pouvez pas payer cette facture avec un chèque."
#: payment_methods/comnpay/models.py:94
msgid "Pay invoice no : "
msgstr "Payer la facture numéro : "
#: payment_methods/comnpay/models.py:106
msgid ""
"In order to pay your invoice with ComNpay, the price must be grater than {} €"
msgstr ""
"Pour pouvoir payer votre facture avec ComNpay, le prix doit être plus grand "
"que {} €"
#: payment_methods/comnpay/views.py:53
#, python-format
msgid "The payment of %(amount)s € has been accepted."
msgstr "Le paiement de %(amount)s € a été accepté."
#: payment.py:49
#: payment_methods/comnpay/views.py:84
msgid "The payment has been refused."
msgstr "Le paiment a été refusé."
#: templates/cotisations/aff_article.html:31
#: templates/cotisations/facture.html:43
#: templates/cotisations/new_facture.html:50
#: templates/cotisations/new_facture_solde.html:44
#: templates/cotisations/aff_article.html:33
#: templates/cotisations/facture.html:60
msgid "Article"
msgstr "Article"
#: templates/cotisations/aff_article.html:32
#: templates/cotisations/aff_article.html:34
msgid "Price"
msgstr "Prix"
#: templates/cotisations/aff_article.html:33
#: templates/cotisations/aff_article.html:35
msgid "Cotisation type"
msgstr "Type de cotisation"
#: templates/cotisations/aff_article.html:34
#: templates/cotisations/aff_article.html:36
msgid "Duration (month)"
msgstr "Durée (mois)"
#: templates/cotisations/aff_article.html:35
#: templates/cotisations/aff_article.html:37
msgid "Concerned users"
msgstr "Utilisateurs concernés"
#: templates/cotisations/aff_article.html:48
#: templates/cotisations/aff_banque.html:40
#: templates/cotisations/aff_cotisations.html:69
#: templates/cotisations/aff_cotisations.html:75
#: templates/cotisations/aff_paiement.html:40
#: templates/cotisations/control.html:104 views.py:396 views.py:443
#: views.py:507 views.py:585
#: templates/cotisations/aff_article.html:38
msgid "Available for everyone"
msgstr "Articles disponibles"
#: templates/cotisations/aff_article.html:52
#: templates/cotisations/aff_banque.html:41
#: templates/cotisations/aff_cotisations.html:70
#: templates/cotisations/aff_cotisations.html:76
#: templates/cotisations/aff_custom_invoice.html:73
#: templates/cotisations/aff_custom_invoice.html:79
#: templates/cotisations/aff_paiement.html:48
#: templates/cotisations/control.html:104 views.py:480 views.py:568
#: views.py:649
msgid "Edit"
msgstr "Modifier"
#: templates/cotisations/aff_article.html:52
#: templates/cotisations/aff_banque.html:44
#: templates/cotisations/aff_cotisations.html:90
#: templates/cotisations/aff_paiement.html:44
msgid "Historique"
msgstr "Historique"
#: templates/cotisations/aff_banque.html:31
#: templates/cotisations/aff_banque.html:32
msgid "Bank"
msgstr "Banque"
#: templates/cotisations/aff_cotisations.html:37
#: templates/cotisations/aff_cotisations.html:38
msgid "User"
msgstr "Utilisateur"
#: templates/cotisations/aff_cotisations.html:40
#: templates/cotisations/aff_cotisations.html:41
#: templates/cotisations/aff_custom_invoice.html:42
#: templates/cotisations/control.html:60
#: templates/cotisations/edit_facture.html:45
msgid "Designation"
msgstr "Désignation"
#: templates/cotisations/aff_cotisations.html:41
#: templates/cotisations/aff_cotisations.html:42
#: templates/cotisations/aff_custom_invoice.html:43
#: templates/cotisations/control.html:61
msgid "Total price"
msgstr "Prix total"
#: templates/cotisations/aff_cotisations.html:43
#: templates/cotisations/aff_paiement.html:31
#: templates/cotisations/aff_cotisations.html:44
#: templates/cotisations/aff_custom_invoice.html:45
#: templates/cotisations/control.html:63
msgid "Payment method"
msgstr "Moyen de paiement"
#: templates/cotisations/aff_cotisations.html:47
#: templates/cotisations/aff_cotisations.html:48
#: templates/cotisations/aff_custom_invoice.html:49
#: templates/cotisations/control.html:67
msgid "Date"
msgstr "Date"
#: templates/cotisations/aff_cotisations.html:51
#: templates/cotisations/aff_cotisations.html:52
#: templates/cotisations/aff_custom_invoice.html:53
#: templates/cotisations/control.html:53
msgid "Invoice id"
msgstr "Id facture"
#: templates/cotisations/aff_cotisations.html:79
#: templates/cotisations/aff_cotisations.html:80
msgid "Controlled invoice"
msgstr "Facture controllé"
#: templates/cotisations/aff_cotisations.html:84 views.py:464 views.py:542
#: views.py:620
#: templates/cotisations/aff_cotisations.html:85
#: templates/cotisations/aff_custom_invoice.html:86 views.py:502 views.py:604
#: views.py:685
msgid "Delete"
msgstr "Supprimer"
#: templates/cotisations/aff_cotisations.html:99
#: templates/cotisations/aff_cotisations.html:98
#: templates/cotisations/aff_custom_invoice.html:98
msgid "PDF"
msgstr "PDF"
#: templates/cotisations/aff_cotisations.html:102
#: templates/cotisations/aff_cotisations.html:101
msgid "Invalidated invoice"
msgstr "Facture invalidée"
#: templates/cotisations/aff_custom_invoice.html:39
msgid "Recipient"
msgstr "Destinataire"
#: templates/cotisations/aff_custom_invoice.html:56
msgid "Paid"
msgstr "Payé"
#: templates/cotisations/aff_paiement.html:33
msgid "Payment type"
msgstr "Type de paiement"
#: templates/cotisations/aff_paiement.html:34
msgid "Is available for everyone"
msgstr "Est disponible pour tout le monde"
#: templates/cotisations/aff_paiement.html:35
msgid "Custom payment method"
msgstr "Méthode de paiement personnalisée"
#: templates/cotisations/control.html:30
msgid "Invoice control"
msgstr "Contrôle des factures"
@ -394,15 +428,11 @@ msgstr ""
#: templates/cotisations/delete.html:40
#: templates/cotisations/edit_facture.html:60
#: templates/cotisations/new_facture_solde.html:59
#: templates/cotisations/recharge.html:42
msgid "Confirm"
msgstr "Confirmer"
#: templates/cotisations/edit_facture.html:31
#: templates/cotisations/facture.html:30
#: templates/cotisations/new_facture.html:30
#: templates/cotisations/new_facture_solde.html:30
msgid "Invoices creation and edition"
msgstr "Création et modification de factures"
@ -411,9 +441,7 @@ msgid "Edit the invoice"
msgstr "Edition de factures"
#: templates/cotisations/edit_facture.html:41
#: templates/cotisations/facture.html:38
#: templates/cotisations/new_facture.html:46
#: templates/cotisations/new_facture_solde.html:40
#: templates/cotisations/facture.html:55
msgid "Invoice's articles"
msgstr "Articles de la facture"
@ -421,15 +449,23 @@ msgstr "Articles de la facture"
msgid "Quantity"
msgstr "Quantité"
#: templates/cotisations/facture.html:52
#: templates/cotisations/new_facture.html:59
#: templates/cotisations/new_facture_solde.html:53
#: templates/cotisations/facture.html:36
msgid "New invoice"
msgstr "Nouvelle facture"
#: templates/cotisations/facture.html:39
msgid "Maximum allowed balance : "
msgstr "Solde maximum autorisé : "
#: templates/cotisations/facture.html:43
msgid "Current balance :"
msgstr "Solde actuel :"
#: templates/cotisations/facture.html:69
msgid "Add an article"
msgstr "Ajouter un article"
#: templates/cotisations/facture.html:54
#: templates/cotisations/new_facture.html:61
#: templates/cotisations/new_facture_solde.html:55
#: templates/cotisations/facture.html:71
msgid ""
"\n"
" Total price : <span id=\"total_price\">0,00</span> €\n"
@ -464,7 +500,7 @@ msgid "Delete article types"
msgstr "Supprimer des types d'articles"
#: templates/cotisations/index_banque.html:30
#: templates/cotisations/sidebar.html:50
#: templates/cotisations/sidebar.html:55
msgid "Banks"
msgstr "Banques"
@ -480,6 +516,15 @@ msgstr "Ajouter une banque"
msgid "Delete banks"
msgstr "Supprimer des banques"
#: 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"
#: templates/cotisations/index_paiement.html:30
msgid "Payments"
msgstr "Paiement"
@ -496,59 +541,24 @@ msgstr "Ajouter un type de paiement"
msgid "Delete payment types"
msgstr "Supprimer un type de paiement"
#: templates/cotisations/new_facture.html:37
#: templates/cotisations/new_facture_solde.html:37
msgid "New invoice"
msgstr "Nouvelle facture"
#: templates/cotisations/new_facture.html:39
#, python-format
msgid ""
"\n"
" User's balance : %(user.solde)s €\n"
" "
msgstr ""
"\n"
" Solde de l'utilisateur : %(user.solde)s €\n"
" "
#: templates/cotisations/new_facture.html:65 views.py:257
msgid "Create"
msgstr "Créer"
#: templates/cotisations/payment.html:30 templates/cotisations/recharge.html:30
#: templates/cotisations/recharge.html:33
#: templates/cotisations/payment.html:30
msgid "Balance refill"
msgstr "Rechargement de solde"
#: templates/cotisations/payment.html:34
#, python-format
msgid ""
"\n"
" Refill of %(amount)s €\n"
" Pay %(amount)s €\n"
" "
msgstr ""
"\n"
" Recharger de %(amount)s €\n"
" "
#: templates/cotisations/payment.html:40
#: templates/cotisations/payment.html:44 views.py:867
msgid "Pay"
msgstr "Payer"
#: templates/cotisations/recharge.html:35
#, python-format
msgid ""
"\n"
" Balance : <span class=\"label label-default\">%(request.user.solde)s "
"€</span>\n"
" "
msgstr ""
"\n"
" Solde : <span class=\"label label-default\">%(request.user.solde)s "
"€</span>\n"
" "
#: templates/cotisations/sidebar.html:32
msgid "Create an invoice"
msgstr "Créer une facture"
@ -557,83 +567,94 @@ msgstr "Créer une facture"
msgid "Control the invoices"
msgstr "Contrôler les factures"
#: templates/cotisations/sidebar.html:45
#: templates/cotisations/sidebar.html:50
msgid "Available articles"
msgstr "Articles disponibles"
#: templates/cotisations/sidebar.html:55
#: templates/cotisations/sidebar.html:60
msgid "Payment methods"
msgstr "Moyens de paiement"
#: views.py:138
msgid "Your balance is too low for this operation."
msgstr "Votre solde est trop faible pour cette opération."
#: validators.py:20
msgid "There are already payment method(s) for user balance"
msgstr "Il y a déjà une méthode de paiement pour le solde utilisateur"
#: views.py:168
#, python-format
msgid ""
"The cotisation of %(member_name)s has been extended to "
"%(end_date)s."
msgstr "La cotisation de %(member_name)s a été étendu jusqu'à %(end_date)s."
#: views.py:178
msgid "The invoice has been created."
msgstr "La facture a été créée."
#: views.py:186 views.py:824
#: views.py:165
msgid "You need to choose at least one article."
msgstr "Vous devez choisir au moins un article."
#: views.py:338
#: views.py:178 views.py:232
msgid "Create"
msgstr "Créer"
#: views.py:225
msgid "The custom invoice was successfully created."
msgstr "La facture a été créée avec succès."
#: views.py:313 views.py:367
msgid "The invoice has been successfully edited."
msgstr "La facture a été crée avec succès."
#: views.py:358
#: views.py:333 views.py:427
msgid "The invoice has been successfully deleted."
msgstr "La facture a été supprimée avec succès."
#: views.py:363
#: views.py:338 views.py:432
msgid "Invoice"
msgstr "Facture"
#: views.py:391
msgid "Balance successfully updated."
msgstr "Solde mis à jour avec succès."
#: views.py:417
#: views.py:453
msgid "The article has been successfully created."
msgstr "L'article a été créé avec succès."
#: views.py:422 views.py:485 views.py:563
#, fuzzy
#| msgid "Address"
msgid "Add"
#: views.py:458 views.py:531 views.py:626
msgid "Address"
msgstr "Adresse"
#: views.py:438
#: views.py:459
msgid "New article"
msgstr "Nouvel article"
#: views.py:475
msgid "The article has been successfully edited."
msgstr "L'article a été modifié avec succès."
#: views.py:459
#: views.py:481
msgid "Edit article"
msgstr "Éditer l'article"
#: views.py:497
msgid "The article(s) have been successfully deleted."
msgstr "L'(es) article(s) a(ont) été supprimé(s) avec succès. "
#: views.py:480
#: views.py:503
msgid "Delete article"
msgstr "Supprimer l'article"
#: views.py:525
msgid "The payment method has been successfully created."
msgstr "Le moyen de paiement a été créé avec succès."
#: views.py:502
#: views.py:532
msgid "New payment method"
msgstr "Nouveau moyen de paiement"
#: views.py:562
msgid "The payement method has been successfully edited."
msgstr "Le moyen de paiement a été modifié avec succès."
#: views.py:526
#: views.py:569
msgid "Edit payment method"
msgstr "Éditer le moyen de paiement"
#: views.py:588
#, python-format
msgid ""
"The payment method %(method_name)s has been successfully "
"deleted."
msgstr "Le moyen de paiement %(method_name)s a été supprimé avec succès."
#: views.py:534
#: views.py:596
#, python-format
msgid ""
"The payment method %(method_name)s can't be deleted "
@ -642,21 +663,33 @@ msgstr ""
"Le moyen de paiement %(method_name)s ne peut pas être mis à jour car il y a "
"des factures l'utilisant."
#: views.py:558
#: views.py:605
msgid "Delete payment method"
msgstr "Supprimer le moyen de paiement"
#: views.py:621
msgid "The bank has been successfully created."
msgstr "La banque a été crée avec succès."
#: views.py:580
#: views.py:627
msgid "New bank"
msgstr "Créer la banque"
#: views.py:644
msgid "The bank has been successfully edited"
msgstr "La banque a été modifée avec succès."
#: views.py:604
#: views.py:650
msgid "Edit bank"
msgstr "Éditer la banque"
#: views.py:669
#, python-format
msgid ""
"The bank %(bank_name)s has been successfully deleted."
msgstr "La banque %(bank_name)s a été supprimée avec succès."
#: views.py:612
#: views.py:677
#, python-format
msgid ""
"The bank %(bank_name)s can't be deleted because there "
@ -665,127 +698,131 @@ msgstr ""
"La banque %(bank_name)s ne peut pas être supprimée car il y a des factures "
"qui l'utilisent."
#: views.py:656
#: views.py:686
msgid "Delete bank"
msgstr "Supprimer la banque"
#: views.py:722
msgid "Your changes have been properly taken into account."
msgstr "Vos modifications ont correctement été prises en compte."
#: views.py:776
msgid "The balance is too low for this operation."
msgstr "Le solde est trop faible pour cette opération."
#: views.py:834
msgid "You are not allowed to credit your balance."
msgstr "Vous n'êtes pas autorisé à créditer votre solde."
#: views.py:806
#, python-format
msgid ""
"The cotisation of %(member_name)s has been successfully "
"extended to %(end_date)s."
msgstr "La cotisation de %(member_name)s a été prolongée jusqu'à %(end_date)s."
#: views.py:866
msgid "Refill your balance"
msgstr "Créditer votre solde"
#: views.py:816
msgid "The invoice has been successuflly created."
msgstr "La facture a été créée avec succès."
#: models.py:137
msgid "Cheque number"
msgstr "Numéro de chèque"
#: views.py:846
msgid "Online payment is disabled."
msgstr "Le paiement en ligne est désactivé."
msgid "Not specified"
msgstr "Non renseigné"
#~ msgid "Paid"
#~ msgstr "Payé"
msgid "A cheque number and a bank must be specified."
msgstr "Un numéro de chèqe et une banque doivent être renseignés."
#~ msgid "Recipient"
#~ msgstr "Destinataire"
#: models.py:155
msgid "Can change the \"controlled\" state"
msgstr "Peut modifier l'état \"controllé\""
#~ msgid "Invoice number"
#~ msgstr "Numéro de facture"
#: models.py:157
msgid "Can see an invoice's details"
msgstr "Peut voir les détails d'une facture"
#~ msgid "Existing articles"
#~ msgstr "Articles disponibles"
#: models.py:159
msgid "Can edit all the previous invoices"
msgstr "Peut modifier toutes les factures existantes"
#~ msgid "Existing payment method"
#~ msgstr "Moyen de paiements disponibles"
#: models.py:297
msgid "Connexion"
msgstr "Connexion"
#~ msgid "Existing banks"
#~ msgstr "Banques disponibles"
#: models.py:336
msgid "Membership"
msgstr "Adhésion"
#~ msgid "Amount"
#~ msgstr "Montant"
#: models.py:299
msgid "Both of them"
msgstr "Les deux"
#~ msgid "Can change the \"controlled\" state"
#~ msgstr "Peut modifier l'état \"controllé\""
#: models.py:328
msgid "Duration (in whole month)"
msgstr "Durée (en mois entiers)"
#~ msgid "Can create a custom PDF invoice"
#~ msgstr "Peut crée une facture PDF personnalisée"
#: models.py:336
msgid "Type of cotisation"
msgstr "Type de cotisation"
#~ msgid "Can see an invoice's details"
#~ msgstr "Peut voir les détails d'une facture"
#: models.py:341
msgid "Can see a purchase's details"
msgstr "Peut voir les détails d'un achat"
#~ msgid "Can edit all the previous invoices"
#~ msgstr "Peut modifier toutes les factures existantes"
#: models.py:342
msgid "Can edit all the previous purchases"
msgstr "Peut voir les achats existants"
#~ msgid "Connexion"
#~ msgstr "Connexion"
#: models.py:344
msgid "Purchase"
msgstr "Achat"
#~ msgid "Membership"
#~ msgstr "Adhésion"
#: models.py:345
msgid "Purchases"
msgstr "Achat"
#~ msgid "Both of them"
#~ msgstr "Les deux"
#: models.py:512
msgid "Club"
msgstr "Club"
#~ msgid "Duration (in whole month)"
#~ msgstr "Durée (en mois entiers)"
#: models.py:530
msgid "Unitary price"
msgstr "Prix unitaire"
#~ msgid "Type of cotisation"
#~ msgstr "Type de cotisation"
#: models.py:538
msgid "Type of users concerned"
msgstr "Type d'utilisateurs concernés"
#~ msgid "Can see a purchase's details"
#~ msgstr "Peut voir les détails d'un achat"
#: models.py:561
msgid "Can see an article's details"
msgstr "Peut voir les détails d'un article"
#~ msgid "Can edit all the previous purchases"
#~ msgstr "Peut voir les achats existants"
#: models.py:621
msgid "Name"
msgstr "Nom"
#~ msgid "Purchase"
#~ msgstr "Achat"
#: models.py:626
msgid "Can see a bank's details"
msgstr "Peut voir les détails d'une banque"
#~ msgid "Purchases"
#~ msgstr "Achat"
#: models.py:344
msgid "Standard"
msgstr "Standard"
#~ msgid "Club"
#~ msgstr "Club"
msgid "Cheque"
msgstr "Chèque"
#~ msgid "Unitary price"
#~ msgstr "Prix unitaire"
#: models.py:647
msgid "Method"
msgstr "Moyen"
#~ msgid "Type of users concerned"
#~ msgstr "Type d'utilisateurs concernés"
#: models.py:663
msgid "Can see a payement's details"
msgstr "Peut voir les détails d'un paiement"
#~ msgid "Can see an article's details"
#~ msgstr "Peut voir les détails d'un article"
#: models.py:785
msgid "Starting date"
msgstr "Date de début"
#~ msgid "Name"
#~ msgstr "Nom"
#: models.py:788
msgid "Ending date"
msgstr "Date de fin"
#~ msgid "Can see a bank's details"
#~ msgstr "Peut voir les détails d'une banque"
#: models.py:793
msgid "Can see a cotisation's details"
msgstr "Peut voir les détails d'une cotisation"
#~ msgid "Standard"
#~ msgstr "Standard"
#~ msgid "Cheque"
#~ msgstr "Chèque"
#~ msgid "Method"
#~ msgstr "Moyen"
#~ msgid "Can see a payement's details"
#~ msgstr "Peut voir les détails d'un paiement"
#~ msgid "Starting date"
#~ msgstr "Date de début"
#~ msgid "Ending date"
#~ msgstr "Date de fin"
#~ msgid "Can see a cotisation's details"
#~ msgstr "Peut voir les détails d'une cotisation"
#~ msgid "Can edit the previous cotisations"
#~ msgstr "Peut voir les cotisations existantes"
#: models.py:794
msgid "Can edit the previous cotisations"
msgstr "Peut voir les cotisations existantes"

View file

@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-07-21 20:01
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
from django.contrib.auth.management import create_permissions
import re2o.field_permissions
import re2o.mixins
def reattribute_ids(apps, schema_editor):
Facture = apps.get_model('cotisations', 'Facture')
BaseInvoice = apps.get_model('cotisations', 'BaseInvoice')
for f in Facture.objects.all():
base = BaseInvoice.objects.create(id=f.pk, date=f.date)
f.baseinvoice_ptr = base
f.save()
def update_rights(apps, schema_editor):
Permission = apps.get_model('auth', 'Permission')
# creates needed permissions
app = apps.get_app_config('cotisations')
app.models_module = True
create_permissions(app)
app.models_module = False
former = Permission.objects.get(codename='change_facture_pdf')
new_1 = Permission.objects.get(codename='add_custominvoice')
new_2 = Permission.objects.get(codename='change_custominvoice')
new_3 = Permission.objects.get(codename='view_custominvoice')
new_4 = Permission.objects.get(codename='delete_custominvoice')
for group in former.group_set.all():
group.permissions.remove(former)
group.permissions.add(new_1)
group.permissions.add(new_2)
group.permissions.add(new_3)
group.permissions.add(new_4)
group.save()
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0031_comnpaypayment_production'),
]
operations = [
migrations.CreateModel(
name='BaseInvoice',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateTimeField(auto_now_add=True, verbose_name='Date')),
],
bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, re2o.field_permissions.FieldPermissionModelMixin, models.Model),
),
migrations.CreateModel(
name='CustomInvoice',
fields=[
('baseinvoice_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cotisations.BaseInvoice')),
('recipient', models.CharField(max_length=255, verbose_name='Recipient')),
('payment', models.CharField(max_length=255, verbose_name='Payment type')),
('address', models.CharField(max_length=255, verbose_name='Address')),
('paid', models.BooleanField(verbose_name='Paid')),
],
bases=('cotisations.baseinvoice',),
options={'permissions': (('view_custominvoice', 'Can view a custom invoice'),)},
),
migrations.AddField(
model_name='facture',
name='baseinvoice_ptr',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='cotisations.BaseInvoice', null=True),
preserve_default=False,
),
migrations.RunPython(reattribute_ids),
migrations.AlterField(
model_name='vente',
name='facture',
field=models.ForeignKey(on_delete=models.CASCADE, verbose_name='Invoice', to='cotisations.BaseInvoice')
),
migrations.RemoveField(
model_name='facture',
name='id',
),
migrations.RemoveField(
model_name='facture',
name='date',
),
migrations.AlterField(
model_name='facture',
name='baseinvoice_ptr',
field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cotisations.BaseInvoice'),
),
migrations.RunPython(update_rights),
migrations.AlterModelOptions(
name='facture',
options={'permissions': (('change_facture_control', 'Can change the "controlled" state'), ('view_facture', "Can see an invoice's details"), ('change_all_facture', 'Can edit all the previous invoices')), 'verbose_name': 'Invoice', 'verbose_name_plural': 'Invoices'},
),
]

View file

@ -55,80 +55,11 @@ from cotisations.utils import find_payment_method
from cotisations.validators import check_no_balance
# TODO : change facture to invoice
class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
"""
The model for an invoice. It reprensents the fact that a user paid for
something (it can be multiple article paid at once).
An invoice is linked to :
* one or more purchases (one for each article sold that time)
* a user (the one who bought those articles)
* a payment method (the one used by the user)
* (if applicable) a bank
* (if applicable) a cheque number.
Every invoice is dated throught the 'date' value.
An invoice has a 'controlled' value (default : False) which means that
someone with high enough rights has controlled that invoice and taken it
into account. It also has a 'valid' value (default : True) which means
that someone with high enough rights has decided that this invoice was not
valid (thus it's like the user never paid for his articles). It may be
necessary in case of non-payment.
"""
user = models.ForeignKey('users.User', on_delete=models.PROTECT)
# TODO : change paiement to payment
paiement = models.ForeignKey('Paiement', on_delete=models.PROTECT)
# TODO : change banque to bank
banque = models.ForeignKey(
'Banque',
on_delete=models.PROTECT,
blank=True,
null=True
)
# TODO : maybe change to cheque nummber because not evident
cheque = models.CharField(
max_length=255,
blank=True,
verbose_name=_l("Cheque number")
)
class BaseInvoice(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
date = models.DateTimeField(
auto_now_add=True,
verbose_name=_l("Date")
)
# TODO : change name to validity for clarity
valid = models.BooleanField(
default=True,
verbose_name=_l("Validated")
)
# TODO : changed name to controlled for clarity
control = models.BooleanField(
default=False,
verbose_name=_l("Controlled")
)
class Meta:
abstract = False
permissions = (
# TODO : change facture to invoice
('change_facture_control',
_l("Can change the \"controlled\" state")),
# TODO : seems more likely to be call create_facture_pdf
# or create_invoice_pdf
('change_facture_pdf',
_l("Can create a custom PDF invoice")),
('view_facture',
_l("Can see an invoice's details")),
('change_all_facture',
_l("Can edit all the previous invoices")),
)
verbose_name = _l("Invoice")
verbose_name_plural = _l("Invoices")
def linked_objects(self):
"""Return linked objects : machine and domain.
Usefull in history display"""
return self.vente_set.all()
# TODO : change prix to price
def prix(self):
@ -167,6 +98,74 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
).values_list('name', flat=True))
return name
# TODO : change facture to invoice
class Facture(BaseInvoice):
"""
The model for an invoice. It reprensents the fact that a user paid for
something (it can be multiple article paid at once).
An invoice is linked to :
* one or more purchases (one for each article sold that time)
* a user (the one who bought those articles)
* a payment method (the one used by the user)
* (if applicable) a bank
* (if applicable) a cheque number.
Every invoice is dated throught the 'date' value.
An invoice has a 'controlled' value (default : False) which means that
someone with high enough rights has controlled that invoice and taken it
into account. It also has a 'valid' value (default : True) which means
that someone with high enough rights has decided that this invoice was not
valid (thus it's like the user never paid for his articles). It may be
necessary in case of non-payment.
"""
user = models.ForeignKey('users.User', on_delete=models.PROTECT)
# TODO : change paiement to payment
paiement = models.ForeignKey('Paiement', on_delete=models.PROTECT)
# TODO : change banque to bank
banque = models.ForeignKey(
'Banque',
on_delete=models.PROTECT,
blank=True,
null=True
)
# TODO : maybe change to cheque nummber because not evident
cheque = models.CharField(
max_length=255,
blank=True,
verbose_name=_l("Cheque number")
)
# TODO : change name to validity for clarity
valid = models.BooleanField(
default=True,
verbose_name=_l("Validated")
)
# TODO : changed name to controlled for clarity
control = models.BooleanField(
default=False,
verbose_name=_l("Controlled")
)
class Meta:
abstract = False
permissions = (
# TODO : change facture to invoice
('change_facture_control',
_l("Can change the \"controlled\" state")),
('view_facture',
_l("Can see an invoice's details")),
('change_all_facture',
_l("Can edit all the previous invoices")),
)
verbose_name = _l("Invoice")
verbose_name_plural = _l("Invoices")
def linked_objects(self):
"""Return linked objects : machine and domain.
Usefull in history display"""
return self.vente_set.all()
def can_edit(self, user_request, *args, **kwargs):
if not user_request.has_perm('cotisations.change_facture'):
return False, _("You don't have the right to edit an invoice.")
@ -212,14 +211,6 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
_("You don't have the right to edit the \"controlled\" state.")
)
@staticmethod
def can_change_pdf(user_request, *_args, **_kwargs):
""" Returns True if the user can change this invoice """
return (
user_request.has_perm('cotisations.change_facture_pdf'),
_("You don't have the right to edit an invoice.")
)
@staticmethod
def can_create(user_request, *_args, **_kwargs):
"""Check if a user can create an invoice.
@ -265,6 +256,28 @@ def facture_post_delete(**kwargs):
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
class CustomInvoice(BaseInvoice):
class Meta:
permissions = (
('view_custominvoice', _l("Can view a custom invoice")),
)
recipient = models.CharField(
max_length=255,
verbose_name=_l("Recipient")
)
payment = models.CharField(
max_length=255,
verbose_name=_l("Payment type")
)
address = models.CharField(
max_length=255,
verbose_name=_l("Address")
)
paid = models.BooleanField(
verbose_name="Paid"
)
# TODO : change Vente to Purchase
class Vente(RevMixin, AclMixin, models.Model):
"""
@ -288,7 +301,7 @@ class Vente(RevMixin, AclMixin, models.Model):
# TODO : change facture to invoice
facture = models.ForeignKey(
'Facture',
'BaseInvoice',
on_delete=models.CASCADE,
verbose_name=_l("Invoice")
)
@ -355,6 +368,10 @@ class Vente(RevMixin, AclMixin, models.Model):
cotisation_type defined (which means the article sold represents
a cotisation)
"""
try:
invoice = self.facture.facture
except Facture.DoesNotExist:
return
if not hasattr(self, 'cotisation') and self.type_cotisation:
cotisation = Cotisation(vente=self)
cotisation.type_cotisation = self.type_cotisation
@ -362,7 +379,7 @@ class Vente(RevMixin, AclMixin, models.Model):
end_cotisation = Cotisation.objects.filter(
vente__in=Vente.objects.filter(
facture__in=Facture.objects.filter(
user=self.facture.user
user=invoice.user
).exclude(valid=False))
).filter(
Q(type_cotisation='All') |
@ -371,9 +388,9 @@ class Vente(RevMixin, AclMixin, models.Model):
date_start__lt=date_start
).aggregate(Max('date_end'))['date_end__max']
elif self.type_cotisation == "Adhesion":
end_cotisation = self.facture.user.end_adhesion()
end_cotisation = invoice.user.end_adhesion()
else:
end_cotisation = self.facture.user.end_connexion()
end_cotisation = invoice.user.end_connexion()
date_start = date_start or timezone.now()
end_cotisation = end_cotisation or date_start
date_max = max(end_cotisation, date_start)
@ -445,6 +462,10 @@ def vente_post_save(**kwargs):
LDAP user when a purchase has been saved.
"""
purchase = kwargs['instance']
try:
purchase.facture.facture
except Facture.DoesNotExist:
return
if hasattr(purchase, 'cotisation'):
purchase.cotisation.vente = purchase
purchase.cotisation.save()
@ -462,8 +483,12 @@ def vente_post_delete(**kwargs):
Synchronise the LDAP user after a purchase has been deleted.
"""
purchase = kwargs['instance']
try:
invoice = purchase.facture.facture
except Facture.DoesNotExist:
return
if purchase.type_cotisation:
user = purchase.facture.user
user = invoice.user
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)

View file

@ -46,7 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ article.type_cotisation }}</td>
<td>{{ article.duration }}</td>
<td>{{ article.type_user }}</td>
<td>{{ article.available_for_everyone|tick }}</td>
<td>{{ article.available_for_everyone | tick }}</td>
<td class="text-right">
{% can_edit article %}
<a class="btn btn-primary btn-sm" role="button" title="{% trans "Edit" %}" href="{% url 'cotisations:edit-article' article.id %}">

View file

@ -0,0 +1,89 @@
{% 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 %}
<div class="table-responsive">
{% if custom_invoice_list.paginator %}
{% include 'pagination.html' with list=custom_invoice_list %}
{% endif %}
<table class="table table-striped">
<thead>
<tr>
<th>
{% trans "Recipient" as tr_recip %}
{% include 'buttons/sort.html' with prefix='invoice' col='user' text=tr_user %}
</th>
<th>{% trans "Designation" %}</th>
<th>{% trans "Total price" %}</th>
<th>
{% trans "Payment method" as tr_payment_method %}
{% include 'buttons/sort.html' with prefix='invoice' col='payement' text=tr_payment_method %}
</th>
<th>
{% trans "Date" as tr_date %}
{% include 'buttons/sort.html' with prefix='invoice' col='date' text=tr_date %}
</th>
<th>
{% trans "Invoice id" as tr_invoice_id %}
{% include 'buttons/sort.html' with prefix='invoice' col='id' text=tr_invoice_id %}
</th>
<th>
{% trans "Paid" as tr_invoice_paid%}
{% include 'buttons/sort.html' with prefix='invoice' col='paid' text=tr_invoice_paid %}
</th>
<th></th>
<th></th>
</tr>
</thead>
{% for invoice in custom_invoice_list %}
<tr>
<td>{{ invoice.recipient }}</td>
<td>{{ invoice.name }}</td>
<td>{{ invoice.prix_total }}</td>
<td>{{ invoice.payment }}</td>
<td>{{ invoice.date }}</td>
<td>{{ invoice.id }}</td>
<td>{{ invoice.paid|tick }}</td>
<td>
{% can_edit invoice %}
{% include 'buttons/edit.html' with href='cotisations:edit-custom-invoice' id=invoice.id %}
{% acl_end %}
{% can_delete invoice %}
{% include 'buttons/suppr.html' with href='cotisations:del-custom-invoice' id=invoice.id %}
{% acl_end %}
{% history_button invoice %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:custom-invoice-pdf' invoice.id %}">
<i class="fa fa-file-pdf"></i> {% trans "PDF" %}
</a>
</td>
</tr>
{% endfor %}
</table>
{% if custom_invoice_list.paginator %}
{% include 'pagination.html' with list=custom_invoice_list %}
{% endif %}
</div>

View file

@ -0,0 +1,36 @@
{% 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
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
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 acl %}
{% load i18n %}
{% block title %}{% trans "Custom invoices" %}{% endblock %}
{% block content %}
<h2>{% trans "Custom invoices list" %}</h2>
{% can_create CustomInvoice %}
{% include "buttons/add.html" with href='cotisations:new-custom-invoice'%}
{% acl_end %}
{% include 'cotisations/aff_custom_invoice.html' with custom_invoice_list=custom_invoice_list %}
{% endblock %}

View file

@ -27,8 +27,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load i18n %}
{% block sidebar %}
{% can_change Facture pdf %}
<a class="list-group-item list-group-item-success" href="{% url "cotisations:new-facture-pdf" %}">
{% can_create CustomInvoice %}
<a class="list-group-item list-group-item-success" href="{% url "cotisations:new-custom-invoice" %}">
<i class="fa fa-plus"></i> {% trans "Create an invoice" %}
</a>
<a class="list-group-item list-group-item-warning" href="{% url "cotisations:control" %}">
@ -40,6 +40,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<i class="fa fa-list-ul"></i> {% trans "Invoices" %}
</a>
{% acl_end %}
{% can_view_all CustomInvoice %}
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-custom-invoice" %}">
<i class="fa fa-list-ul"></i> {% trans "Custom invoices" %}
</a>
{% acl_end %}
{% can_view_all Article %}
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-article" %}">
<i class="fa fa-list-ul"></i> {% trans "Available articles" %}

View file

@ -105,7 +105,7 @@ def render_tex(_request, template, ctx={}):
Returns:
An HttpResponse with type `application/pdf` containing the PDF file.
"""
pdf = create_pdf(template, ctx={})
pdf = create_pdf(template, ctx)
r = HttpResponse(content_type='application/pdf')
r.write(pdf)
return r

View file

@ -52,9 +52,29 @@ urlpatterns = [
name='facture-pdf'
),
url(
r'^new_facture_pdf/$',
views.new_facture_pdf,
name='new-facture-pdf'
r'^index_custom_invoice/$',
views.index_custom_invoice,
name='index-custom-invoice'
),
url(
r'^new_custom_invoice/$',
views.new_custom_invoice,
name='new-custom-invoice'
),
url(
r'^edit_custom_invoice/(?P<custominvoiceid>[0-9]+)$',
views.edit_custom_invoice,
name='edit-custom-invoice'
),
url(
r'^custom_invoice_pdf/(?P<custominvoiceid>[0-9]+)$',
views.custom_invoice_pdf,
name='custom-invoice-pdf',
),
url(
r'^del_custom_invoice/(?P<custominvoiceid>[0-9]+)$',
views.del_custom_invoice,
name='del-custom-invoice'
),
url(
r'^credit_solde/(?P<userid>[0-9]+)$',

View file

@ -58,7 +58,15 @@ from re2o.acl import (
can_change,
)
from preferences.models import AssoOption, GeneralOption
from .models import Facture, Article, Vente, Paiement, Banque
from .models import (
Facture,
Article,
Vente,
Paiement,
Banque,
CustomInvoice,
BaseInvoice
)
from .forms import (
FactureForm,
ArticleForm,
@ -67,10 +75,10 @@ from .forms import (
DelPaiementForm,
BanqueForm,
DelBanqueForm,
NewFactureFormPdf,
SelectUserArticleForm,
SelectClubArticleForm,
RechargeForm
RechargeForm,
CustomInvoiceForm
)
from .tex import render_invoice
from .payment_methods.forms import payment_method_factory
@ -178,10 +186,10 @@ def new_facture(request, user, userid):
# TODO : change facture to invoice
@login_required
@can_change(Facture, 'pdf')
def new_facture_pdf(request):
@can_create(CustomInvoice)
def new_custom_invoice(request):
"""
View used to generate a custom PDF invoice. It's mainly used to
View used to generate a custom invoice. It's mainly used to
get invoices that are not taken into account, for the administrative
point of view.
"""
@ -190,7 +198,7 @@ def new_facture_pdf(request):
Q(type_user='All') | Q(type_user=request.user.class_name)
)
# Building the invocie form and the article formset
invoice_form = NewFactureFormPdf(request.POST or None)
invoice_form = CustomInvoiceForm(request.POST or None)
if request.user.is_class_club:
articles_formset = formset_factory(SelectClubArticleForm)(
request.POST or None,
@ -202,44 +210,31 @@ def new_facture_pdf(request):
form_kwargs={'user': request.user}
)
if invoice_form.is_valid() and articles_formset.is_valid():
# Get the article list and build an list out of it
# contiaining (article_name, article_price, quantity, total_price)
articles_info = []
for articles_form in articles_formset:
if articles_form.cleaned_data:
article = articles_form.cleaned_data['article']
quantity = articles_form.cleaned_data['quantity']
articles_info.append({
'name': article.name,
'price': article.prix,
'quantity': quantity,
'total_price': article.prix * quantity
})
paid = invoice_form.cleaned_data['paid']
recipient = invoice_form.cleaned_data['dest']
address = invoice_form.cleaned_data['chambre']
total_price = sum(a['total_price'] for a in articles_info)
new_invoice_instance = invoice_form.save()
for art_item in articles_formset:
if art_item.cleaned_data:
article = art_item.cleaned_data['article']
quantity = art_item.cleaned_data['quantity']
Vente.objects.create(
facture=new_invoice_instance,
name=article.name,
prix=article.prix,
type_cotisation=article.type_cotisation,
duration=article.duration,
number=quantity
)
messages.success(
request,
_('The custom invoice was successfully created.')
)
return redirect(reverse('cotisations:index-custom-invoice'))
return render_invoice(request, {
'DATE': timezone.now(),
'recipient_name': recipient,
'address': address,
'article': articles_info,
'total': total_price,
'paid': paid,
'asso_name': AssoOption.get_cached_value('name'),
'line1': AssoOption.get_cached_value('adresse1'),
'line2': AssoOption.get_cached_value('adresse2'),
'siret': AssoOption.get_cached_value('siret'),
'email': AssoOption.get_cached_value('contact'),
'phone': AssoOption.get_cached_value('telephone'),
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
})
return form({
'factureform': invoice_form,
'action_name': _("Create"),
'articlesformset': articles_formset,
'articles': articles
'articlelist': articles
}, 'cotisations/facture.html', request)
@ -292,7 +287,7 @@ def facture_pdf(request, facture, **_kwargs):
def edit_facture(request, facture, **_kwargs):
"""
View used to edit an existing invoice.
Articles can be added or remove to the invoice and quantity
Articles can be added or removed to the invoice and quantity
can be set as desired. This is also the view used to invalidate
an invoice.
"""
@ -347,6 +342,100 @@ def del_facture(request, facture, **_kwargs):
}, 'cotisations/delete.html', request)
@login_required
@can_edit(CustomInvoice)
def edit_custom_invoice(request, invoice, **kwargs):
# Building the invocie form and the article formset
invoice_form = CustomInvoiceForm(
request.POST or None,
instance=invoice
)
purchases_objects = Vente.objects.filter(facture=invoice)
purchase_form_set = modelformset_factory(
Vente,
fields=('name', 'number'),
extra=0,
max_num=len(purchases_objects)
)
purchase_form = purchase_form_set(
request.POST or None,
queryset=purchases_objects
)
if invoice_form.is_valid() and purchase_form.is_valid():
if invoice_form.changed_data:
invoice_form.save()
purchase_form.save()
messages.success(
request,
_("The invoice has been successfully edited.")
)
return redirect(reverse('cotisations:index-custom-invoice'))
return form({
'factureform': invoice_form,
'venteform': purchase_form
}, 'cotisations/edit_facture.html', request)
@login_required
@can_view(CustomInvoice)
def custom_invoice_pdf(request, invoice, **_kwargs):
"""
View used to generate a PDF file from an existing invoice in database
Creates a line for each Purchase (thus article sold) and generate the
invoice with the total price, the payment method, the address and the
legal information for the user.
"""
# TODO : change vente to purchase
purchases_objects = Vente.objects.all().filter(facture=invoice)
# Get the article list and build an list out of it
# contiaining (article_name, article_price, quantity, total_price)
purchases_info = []
for purchase in purchases_objects:
purchases_info.append({
'name': purchase.name,
'price': purchase.prix,
'quantity': purchase.number,
'total_price': purchase.prix_total
})
return render_invoice(request, {
'paid': invoice.paid,
'fid': invoice.id,
'DATE': invoice.date,
'recipient_name': invoice.recipient,
'address': invoice.address,
'article': purchases_info,
'total': invoice.prix_total(),
'asso_name': AssoOption.get_cached_value('name'),
'line1': AssoOption.get_cached_value('adresse1'),
'line2': AssoOption.get_cached_value('adresse2'),
'siret': AssoOption.get_cached_value('siret'),
'email': AssoOption.get_cached_value('contact'),
'phone': AssoOption.get_cached_value('telephone'),
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
})
# TODO : change facture to invoice
@login_required
@can_delete(CustomInvoice)
def del_custom_invoice(request, invoice, **_kwargs):
"""
View used to delete an existing invocie.
"""
if request.method == "POST":
invoice.delete()
messages.success(
request,
_("The invoice has been successfully deleted.")
)
return redirect(reverse('cotisations:index-custom-invoice'))
return form({
'objet': invoice,
'objet_name': _("Invoice")
}, 'cotisations/delete.html', request)
@login_required
@can_create(Article)
def add_article(request):
@ -681,8 +770,31 @@ def index_banque(request):
})
@login_required
@can_view_all(CustomInvoice)
def index_custom_invoice(request):
"""View used to display every custom invoice."""
pagination_number = GeneralOption.get_cached_value('pagination_number')
custom_invoice_list = CustomInvoice.objects.prefetch_related('vente_set')
custom_invoice_list = SortTable.sort(
custom_invoice_list,
request.GET.get('col'),
request.GET.get('order'),
SortTable.COTISATIONS_CUSTOM
)
custom_invoice_list = re2o_paginator(
request,
custom_invoice_list,
pagination_number,
)
return render(request, 'cotisations/index_custom_invoice.html', {
'custom_invoice_list': custom_invoice_list
})
@login_required
@can_view_all(Facture)
@can_view_all(CustomInvoice)
def index(request):
"""
View used to display the list of all exisitng invoices.
@ -698,7 +810,7 @@ def index(request):
)
invoice_list = re2o_paginator(request, invoice_list, pagination_number)
return render(request, 'cotisations/index.html', {
'facture_list': invoice_list
'facture_list': invoice_list,
})

View file

@ -250,6 +250,14 @@ class SortTable:
'cotis_id': ['id'],
'default': ['-date']
}
COTISATIONS_CUSTOM = {
'invoice_date': ['date'],
'invoice_id': ['id'],
'invoice_recipient': ['recipient'],
'invoice_address': ['address'],
'invoice_payment': ['payment'],
'default': ['-date']
}
COTISATIONS_CONTROL = {
'control_name': ['user__adherent__name'],
'control_surname': ['user__surname'],

View file

@ -21,6 +21,6 @@ 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 %}
<a class="btn btn-primary btn-sm" role="button" href="{% url href id %}" title="{{ desc|default:"Ajouter" }}">
<a class="btn btn-primary btn-sm" role="button" href="{% if id %}{% url href id %}{% else %}{% url href %}{% endif %}" title="{{ desc|default:"Ajouter" }}">
<i class="fa fa-plus"></i>
</a>