Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
tetaneutral.net
djadhere
Commits
79682f73
Commit
79682f73
authored
Jun 09, 2020
by
Élie Bouttier
Browse files
improve billing
parent
f1e98e68
Changes
5
Show whitespace changes
Inline
Side-by-side
adhesions/admin.py
View file @
79682f73
...
@@ -241,7 +241,7 @@ class AdhesionAdmin(AdtSearchMixin, admin.ModelAdmin):
...
@@ -241,7 +241,7 @@ class AdhesionAdmin(AdtSearchMixin, admin.ModelAdmin):
list_filter
=
(
AdherentTypeFilter
,
ActiveFilter
,
AdhesionImportedFilter
)
list_filter
=
(
AdherentTypeFilter
,
ActiveFilter
,
AdhesionImportedFilter
)
list_select_related
=
(
'user'
,
'user__profile'
,
'corporation'
,)
list_select_related
=
(
'user'
,
'user__profile'
,
'corporation'
,)
fields
=
(
'id'
,
'type'
,
'get_adherent_link'
,
'get_membership_link'
,)
fields
=
(
'id'
,
'type'
,
'get_adherent_link'
,
'get_membership_link'
,)
readonly_fields
=
(
'id'
,
'type'
,
'get_adherent_link'
,
'get_membership_link'
,
'get_antennas_link'
,)
readonly_fields
=
(
'id'
,
'type'
,
'get_adherent_link'
,
'get_membership_link'
,
)
#
'get_antennas_link',)
search_fields
=
(
'=id'
,
'notes'
,)
\
search_fields
=
(
'=id'
,
'notes'
,)
\
+
tuple
([
'user__%s'
%
f
for
f
in
UserAdmin
.
search_fields
if
'adhesion'
not
in
f
])
\
+
tuple
([
'user__%s'
%
f
for
f
in
UserAdmin
.
search_fields
if
'adhesion'
not
in
f
])
\
+
tuple
([
'corporation__%s'
%
f
for
f
in
CorporationAdmin
.
search_fields
if
'adhesion'
not
in
f
])
+
tuple
([
'corporation__%s'
%
f
for
f
in
CorporationAdmin
.
search_fields
if
'adhesion'
not
in
f
])
...
...
banking/admin.py
View file @
79682f73
import
io
from
functools
import
update_wrapper
from
django.conf.urls
import
url
from
django.conf.urls
import
url
from
django.contrib
import
admin
from
django.contrib
import
admin
from
django.core.exceptions
import
PermissionDenied
from
django.core.exceptions
import
PermissionDenied
from
django.db
import
models
from
django.db
import
models
from
django.urls
import
path
,
reverse
from
django.forms
import
BaseInlineFormSet
from
django.forms
import
BaseInlineFormSet
from
django.http
import
FileResponse
,
Http404
from
django.http
import
FileResponse
,
Http404
from
django.shortcuts
import
get_object_or_404
,
redirect
from
django.shortcuts
import
get_object_or_404
,
redirect
from
django.template.response
import
TemplateResponse
from
django.template.response
import
TemplateResponse
from
django.urls
import
path
,
reverse
from
django.urls
import
path
,
reverse
from
django.utils.html
import
format_html
from
django.utils.html
import
format_html
from
django.db.models
import
F
,
Sum
,
ExpressionWrapper
,
Subquery
from
django.db.models.functions
import
Coalesce
from
django.utils.safestring
import
mark_safe
import
io
from
functools
import
update_wrapper
from
reportlab.pdfgen
import
canvas
from
reportlab.pdfgen
import
canvas
from
adhesions.admin
import
AdhesionAdmin
from
adhesions.admin
import
AdhesionAdmin
from
djadhere.utils
import
ActiveFilter
from
djadhere.utils
import
ActiveFilter
from
services.admin
import
ServiceAdmin
from
services.admin
import
ServiceAdmin
from
services.models
import
Service
Type
from
services.models
import
Service
from
.facture
import
facture
from
.facture
import
facture
from
.models
import
Expense
,
Invoice
,
InvoicedProduct
,
PaymentUpdate
,
RecurringPayment
from
.models
import
Expense
,
Invoice
,
InvoicedProduct
,
PaymentUpdate
,
RecurringPayment
from
.utils
import
notify_payment_update
from
.utils
import
notify_payment_update
### Inlines
### Inlines
class
PaymentMethodFilter
(
admin
.
SimpleListFilter
):
class
PaymentMethodFilter
(
admin
.
SimpleListFilter
):
...
@@ -313,16 +317,25 @@ class InvoicedProductAdmin(admin.TabularInline):
...
@@ -313,16 +317,25 @@ class InvoicedProductAdmin(admin.TabularInline):
class
InvoiceAdmin
(
admin
.
ModelAdmin
):
class
InvoiceAdmin
(
admin
.
ModelAdmin
):
inlines
=
(
InvoicedProductAdmin
,)
list_display
=
(
'reference'
,
'date'
,
'adhesion_link'
,
'total'
,
'invoiced'
,)
list_display
=
(
'reference'
,
'date'
,
'buyer'
,
'amount'
,
'invoiced'
,)
fields
=
(
'date'
,
'reference'
,
'buyer'
,
'notes'
,)
raw_id_fields
=
(
'buyer'
,)
raw_id_fields
=
(
'buyer'
,)
list_filter
=
(
'date'
,)
list_filter
=
(
'date'
,)
search_fields
=
(
'
reference
'
,
'=buyer__id'
,)
search_fields
=
(
'
=num
'
,
'=buyer__id'
,)
def
amount
(
self
,
invoice
):
def
get_queryset
(
self
,
request
):
return
sum
(
map
(
lambda
item
:
item
.
quantity
*
item
.
price
,
invoice
.
items
.
all
()))
qs
=
super
().
get_queryset
(
request
)
amount
.
short_description
=
'Montant'
qs
=
qs
.
annotate
(
total
=
Coalesce
(
Sum
(
ExpressionWrapper
(
F
(
'items__quantity'
)
*
F
(
'items__price'
),
output_field
=
models
.
DecimalField
(
decimal_places
=
2
)
)),
0
)
)
return
qs
def
total
(
self
,
invoice
):
return
'%.2f €'
%
invoice
.
total
total
.
short_description
=
'Montant'
total
.
admin_order_field
=
'total'
def
invoiced
(
self
,
invoice
):
def
invoiced
(
self
,
invoice
):
return
bool
(
invoice
.
pdf
)
return
bool
(
invoice
.
pdf
)
...
@@ -330,11 +343,59 @@ class InvoiceAdmin(admin.ModelAdmin):
...
@@ -330,11 +343,59 @@ class InvoiceAdmin(admin.ModelAdmin):
invoiced
.
boolean
=
True
invoiced
.
boolean
=
True
def
get_readonly_fields
(
self
,
request
,
obj
=
None
):
def
get_readonly_fields
(
self
,
request
,
obj
=
None
):
if
obj
and
obj
.
pdf
:
if
obj
:
return
(
'date'
,
'reference'
,
'
buyer
'
,)
return
(
'date'
,
'reference'
,
'
adhesion_link'
,
'total'
,
'membership'
,
'services
'
,)
else
:
else
:
return
[]
return
[]
def
get_fieldsets
(
self
,
request
,
obj
=
None
):
fieldsets
=
[]
if
obj
:
fieldsets
+=
[
(
None
,
{
'fields'
:
(
'date'
,
'reference'
,
'adhesion_link'
,
'total'
,
'notes'
),
})
]
if
not
obj
.
pdf
:
fieldsets
+=
[
(
'Voir les cotisation et services en cours'
,
{
'fields'
:
(
'membership'
,
'services'
),
'classes'
:
(
'collapse'
,),
})
]
else
:
fieldsets
+=
[
(
None
,
{
'fields'
:
(
'buyer'
,),
})
]
return
fieldsets
def
get_inlines
(
self
,
request
,
obj
=
None
):
if
obj
:
return
(
InvoicedProductAdmin
,)
else
:
return
()
def
membership
(
self
,
obj
):
return
format_html
(
u
'<a href="{}">{}</a>'
,
obj
.
buyer
.
membership
.
get_absolute_url
(),
obj
.
buyer
.
membership
.
get_current_payment_display
())
membership
.
short_description
=
'Cotisation'
def
services
(
self
,
obj
):
services
=
Service
.
objects
.
filter
(
adhesion
=
obj
.
buyer
,
active
=
True
)
services_display
=
[]
for
service
in
services
:
description
=
format_html
(
u
'<a href="{}">{}</a>'
,
service
.
get_absolute_url
(),
service
)
contribution
=
format_html
(
u
'<a href="{}">{}</a>'
,
service
.
contribution
.
get_absolute_url
(),
service
.
contribution
.
get_current_payment_display
())
services_display
.
append
(
'{}: {}'
.
format
(
description
,
contribution
))
return
mark_safe
(
'<br/>'
.
join
(
services_display
))
services
.
short_description
=
'Services'
def
adhesion_link
(
self
,
obj
):
url
=
reverse
(
'admin:adhesions_adhesion_change'
,
args
=
[
obj
.
buyer
.
pk
])
return
format_html
(
'<a href="{}">{}</a>'
,
url
,
str
(
obj
.
buyer
))
adhesion_link
.
short_description
=
'Adhérent'
def
has_delete_permission
(
self
,
request
,
obj
=
None
):
def
has_delete_permission
(
self
,
request
,
obj
=
None
):
if
not
obj
or
obj
.
pdf
:
if
not
obj
or
obj
.
pdf
:
return
False
return
False
...
@@ -353,7 +414,10 @@ class InvoiceAdmin(admin.ModelAdmin):
...
@@ -353,7 +414,10 @@ class InvoiceAdmin(admin.ModelAdmin):
p
=
canvas
.
Canvas
(
buffer
)
p
=
canvas
.
Canvas
(
buffer
)
produits
=
[]
produits
=
[]
for
item
in
invoice
.
items
.
all
():
for
item
in
invoice
.
items
.
all
():
produits
+=
[(
item
.
description
,
item
.
notes
,
item
.
quantity
,
item
.
price
)]
description
=
item
.
description
if
item
.
notes
:
description
+=
'
\n
'
+
item
.
notes
produits
+=
[(
description
,
item
.
quantity
,
item
.
price
)]
adt
=
invoice
.
buyer
.
adherent
adt
=
invoice
.
buyer
.
adherent
facture
(
p
,
dt
=
invoice
.
date
,
ref
=
invoice
.
reference
,
produits
=
produits
,
adt
=
str
(
adt
),
mail
=
adt
.
email
,
facture
(
p
,
dt
=
invoice
.
date
,
ref
=
invoice
.
reference
,
produits
=
produits
,
adt
=
str
(
adt
),
mail
=
adt
.
email
,
tel
=
adt
.
phone_number
,
addr
=
adt
.
address
,
draft
=
draft
)
tel
=
adt
.
phone_number
,
addr
=
adt
.
address
,
draft
=
draft
)
...
...
banking/migrations/0015_auto_20200609_0001.py
0 → 100644
View file @
79682f73
# Generated by Django 3.0.5 on 2020-06-08 22:01
import
re
from
django.db
import
migrations
,
models
def
set_num
(
apps
,
schema_editor
):
db_alias
=
schema_editor
.
connection
.
alias
Invoice
=
apps
.
get_model
(
'banking'
,
'Invoice'
)
regex
=
re
.
compile
(
'F([0-9]{8})-([0-9]{4})-ADT([0-9]{1,4})'
)
for
invoice
in
Invoice
.
objects
.
all
():
g
=
regex
.
match
(
invoice
.
reference
)
invoice
.
num
=
int
(
g
.
group
(
2
))
invoice
.
save
()
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'banking'
,
'0014_invoicedproduct_notes'
),
]
operations
=
[
migrations
.
AlterModelOptions
(
name
=
'invoice'
,
options
=
{
'ordering'
:
(
'-date'
,
'-num'
),
'verbose_name'
:
'facture'
,
'verbose_name_plural'
:
'factures'
},
),
migrations
.
AlterField
(
model_name
=
'invoice'
,
name
=
'reference'
,
field
=
models
.
CharField
(
max_length
=
32
,
verbose_name
=
'Référence'
,
null
=
True
),
),
migrations
.
AddField
(
model_name
=
'invoice'
,
name
=
'num'
,
field
=
models
.
IntegerField
(
null
=
True
),
),
migrations
.
RunPython
(
set_num
),
migrations
.
RemoveField
(
model_name
=
'invoice'
,
name
=
'reference'
,
),
migrations
.
AlterField
(
model_name
=
'invoice'
,
name
=
'num'
,
field
=
models
.
IntegerField
(),
),
]
banking/models.py
View file @
79682f73
...
@@ -170,18 +170,29 @@ class Expense(models.Model):
...
@@ -170,18 +170,29 @@ class Expense(models.Model):
class
Invoice
(
models
.
Model
):
class
Invoice
(
models
.
Model
):
created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
date
=
models
.
DateField
(
verbose_name
=
'Date'
,
default
=
datetime
.
date
.
today
)
date
=
models
.
DateField
(
verbose_name
=
'Date'
,
default
=
datetime
.
date
.
today
)
reference
=
models
.
Cha
rField
(
max_length
=
32
,
verbose_name
=
'Référence'
,
unique
=
True
)
num
=
models
.
Intege
rField
()
buyer
=
models
.
ForeignKey
(
'adhesions.Adhesion'
,
related_name
=
'invoices'
,
on_delete
=
models
.
PROTECT
,
verbose_name
=
'Adhérent'
)
buyer
=
models
.
ForeignKey
(
'adhesions.Adhesion'
,
related_name
=
'invoices'
,
on_delete
=
models
.
PROTECT
,
verbose_name
=
'Adhérent'
)
notes
=
models
.
TextField
(
blank
=
True
,
default
=
''
)
notes
=
models
.
TextField
(
blank
=
True
,
default
=
''
)
pdf
=
models
.
BinaryField
(
null
=
True
)
pdf
=
models
.
BinaryField
(
null
=
True
)
@
property
def
reference
(
self
):
return
'F{:04d}{:02d}{:02d}-{:04d}-ADT{}'
.
format
(
self
.
date
.
year
,
self
.
date
.
month
,
self
.
date
.
day
,
self
.
num
,
self
.
buyer
.
pk
)
def
get_absolute_url
(
self
):
def
get_absolute_url
(
self
):
return
reverse
(
'admin:%s_%s_change'
%
(
self
.
_meta
.
app_label
,
self
.
_meta
.
model_name
),
args
=
(
self
.
pk
,))
return
reverse
(
'admin:%s_%s_change'
%
(
self
.
_meta
.
app_label
,
self
.
_meta
.
model_name
),
args
=
(
self
.
pk
,))
def
save
(
self
,
*
args
,
**
kwargs
):
if
not
self
.
pk
:
self
.
date
=
datetime
.
date
.
today
()
last_invoice
=
Invoice
.
objects
.
filter
(
date__year
=
self
.
date
.
year
).
order_by
(
'num'
).
last
()
self
.
num
=
last_invoice
.
num
+
1
if
last_invoice
is
not
None
else
1
super
().
save
(
*
args
,
**
kwargs
)
class
Meta
:
class
Meta
:
verbose_name
=
'facture'
verbose_name
=
'facture'
verbose_name_plural
=
'factures'
verbose_name_plural
=
'factures'
ordering
=
(
'-
reference
'
,)
ordering
=
(
'-
date'
,
'-num
'
,)
def
__str__
(
self
):
def
__str__
(
self
):
return
self
.
reference
return
self
.
reference
...
...
services/admin.py
View file @
79682f73
...
@@ -17,6 +17,7 @@ from django.contrib.humanize.templatetags.humanize import naturaltime
...
@@ -17,6 +17,7 @@ from django.contrib.humanize.templatetags.humanize import naturaltime
from
django.contrib.contenttypes.models
import
ContentType
from
django.contrib.contenttypes.models
import
ContentType
from
django.http
import
HttpResponseRedirect
from
django.http
import
HttpResponseRedirect
from
django.core.exceptions
import
ValidationError
from
django.core.exceptions
import
ValidationError
from
django.utils.safestring
import
mark_safe
#from djgeojson.views import GeoJSONLayerView
#from djgeojson.views import GeoJSONLayerView
from
urllib.parse
import
urlencode
from
urllib.parse
import
urlencode
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment