Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
4
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Switch to GitLab Next
Sign in / Register
Toggle navigation
I
invoice
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Iterations
Merge Requests
2
Merge Requests
2
Requirements
Requirements
List
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Test Cases
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Operations
Operations
Incidents
Environments
Packages & Registries
Packages & Registries
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issue
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
kb
invoice
Commits
39e3f90a
Commit
39e3f90a
authored
Oct 29, 2019
by
Patrick Kimber
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch '4044-exception-list' into 'master'
Invoice issues See merge request
!16
parents
dea6ff33
285079ff
Pipeline
#92299099
passed with stage
in 5 minutes and 53 seconds
Changes
33
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
33 changed files
with
1622 additions
and
297 deletions
+1622
-297
example_invoice/templates/example/batch_invoice_list.html
example_invoice/templates/example/batch_invoice_list.html
+50
-0
example_invoice/tests/test_view.py
example_invoice/tests/test_view.py
+43
-1
example_invoice/tests/test_view_perm.py
example_invoice/tests/test_view_perm.py
+12
-1
example_invoice/urls.py
example_invoice/urls.py
+6
-0
example_invoice/views.py
example_invoice/views.py
+15
-1
invoice/forms.py
invoice/forms.py
+23
-4
invoice/migrations/0022_auto_20191029_1235.py
invoice/migrations/0022_auto_20191029_1235.py
+159
-0
invoice/models.py
invoice/models.py
+276
-30
invoice/templates/invoice/_batch_header.html
invoice/templates/invoice/_batch_header.html
+33
-1
invoice/templates/invoice/_batch_invoice_list.html
invoice/templates/invoice/_batch_invoice_list.html
+28
-0
invoice/templates/invoice/_invoice_detail.html
invoice/templates/invoice/_invoice_detail.html
+21
-1
invoice/templates/invoice/_invoice_detail_lines.html
invoice/templates/invoice/_invoice_detail_lines.html
+4
-1
invoice/templates/invoice/_invoice_issue_description.html
invoice/templates/invoice/_invoice_issue_description.html
+17
-0
invoice/templates/invoice/_invoice_issue_list.html
invoice/templates/invoice/_invoice_issue_list.html
+38
-0
invoice/templates/invoice/batch_list.html
invoice/templates/invoice/batch_list.html
+6
-1
invoice/templates/invoice/invoice_list.html
invoice/templates/invoice/invoice_list.html
+40
-10
invoice/templates/invoice/invoiceissue_form.html
invoice/templates/invoice/invoiceissue_form.html
+89
-0
invoice/templates/invoice/reconcile_day_view.html
invoice/templates/invoice/reconcile_day_view.html
+150
-141
invoice/tests/factories.py
invoice/tests/factories.py
+16
-2
invoice/tests/test_batch.py
invoice/tests/test_batch.py
+109
-8
invoice/tests/test_batch_invoice.py
invoice/tests/test_batch_invoice.py
+14
-0
invoice/tests/test_invoice.py
invoice/tests/test_invoice.py
+4
-2
invoice/tests/test_invoice_contact.py
invoice/tests/test_invoice_contact.py
+131
-8
invoice/tests/test_invoice_issue.py
invoice/tests/test_invoice_issue.py
+198
-0
invoice/tests/test_payment_processor.py
invoice/tests/test_payment_processor.py
+12
-20
invoice/tests/test_view.py
invoice/tests/test_view.py
+16
-3
invoice/tests/test_view_perm.py
invoice/tests/test_view_perm.py
+8
-8
invoice/urls.py
invoice/urls.py
+7
-7
invoice/views.py
invoice/views.py
+84
-34
requirements/base.txt
requirements/base.txt
+10
-10
requirements/branch.txt
requirements/branch.txt
+1
-1
setup.py
setup.py
+1
-1
setup.yaml
setup.yaml
+1
-1
No files found.
invoice/templates/invoice/batch
invoice_list.html
→
example_invoice/templates/example/batch_
invoice_list.html
View file @
39e3f90a
{% extends 'dash/base.html' %}
{% load static %}
{% load humanize %}
{% block title %}
Invoices for Batch {{ batch.pk }}, {{ batch.batch_date|date:'d/m/Y'}}
...
...
@@ -30,65 +29,13 @@
<table
class=
"pure-table pure-table-bordered"
width=
"100%"
>
<tbody>
{% include 'invoice/_batch_header.html' %}
<tr>
<td>
Net
</td>
<td>
{{ batch_net|intcomma }}
</td>
</tr>
<tr>
<td>
VAT
</td>
<td>
{{ batch_vat|intcomma }}
</td>
</tr>
<tr>
<td>
Gross
</td>
<td>
{{ batch_gross|intcomma }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div
class=
"pure-g"
>
<div
class=
"pure-u-1"
>
<table
class=
"pure-table pure-table-bordered small-margin-top"
width=
"100%"
>
<thead>
<tr
valign=
"top"
>
<th>
Date
</th>
<th>
Invoice Number
</th>
<th>
Contact
</th>
</tr>
</thead>
<tbody>
{% for o in object_list %}
<tr
valign=
"top"
>
<td>
{{ o.invoice.invoice_date|date:'d/m/Y'}}
</td>
<td>
<a
href=
"{% url 'invoice.detail' o.invoice.pk %}"
>
{{ o.invoice.invoice_number }}
</a>
</td>
<td>
<a
href=
"{% url 'contact.detail' o.invoice.contact.pk %}"
>
{{ o.invoice.contact }}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include 'invoice/_batch_invoice_list.html' %}
</div>
</div>
<div
class=
"pure-g"
>
...
...
example_invoice/tests/test_view.py
View file @
39e3f90a
...
...
@@ -4,10 +4,52 @@ import pytest
from
django.urls
import
reverse
from
http
import
HTTPStatus
from
invoice.tests.factories
import
InvoiceCreditFactory
,
InvoiceFactory
from
invoice.tests.factories
import
(
BatchFactory
,
BatchInvoiceFactory
,
InvoiceCreditFactory
,
InvoiceFactory
,
)
from
login.tests.factories
import
TEST_PASSWORD
,
UserFactory
@
pytest
.
mark
.
django_db
def
test_batch_invoice_list
(
client
):
batch
=
BatchFactory
()
BatchInvoiceFactory
(
batch
=
batch
,
invoice
=
InvoiceFactory
(
invoice_date
=
batch
.
batch_date
,
number
=
1
),
)
BatchInvoiceFactory
(
batch
=
BatchFactory
(),
invoice
=
InvoiceFactory
(
invoice_date
=
batch
.
batch_date
,
number
=
2
),
)
BatchInvoiceFactory
(
batch
=
batch
,
invoice
=
InvoiceFactory
(
invoice_date
=
batch
.
batch_date
,
number
=
3
),
)
BatchInvoiceFactory
(
batch
=
batch
,
invoice
=
InvoiceFactory
(
invoice_date
=
batch
.
batch_date
,
number
=
4
),
)
user
=
UserFactory
(
is_staff
=
True
)
assert
client
.
login
(
username
=
user
.
username
,
password
=
TEST_PASSWORD
)
is
True
response
=
client
.
get
(
reverse
(
"invoice.batch.invoice.list"
,
args
=
[
batch
.
pk
])
)
assert
HTTPStatus
.
OK
==
response
.
status_code
assert
"batch"
in
response
.
context
assert
"batch_gross"
in
response
.
context
assert
"batch_net"
in
response
.
context
assert
"batch_vat"
in
response
.
context
assert
"object_list"
in
response
.
context
assert
"batchinvoice_list"
in
response
.
context
assert
batch
==
response
.
context
[
"batch"
]
assert
[
4
,
3
,
1
]
==
[
x
.
invoice
.
number
for
x
in
response
.
context
[
"batchinvoice_list"
]
]
@
pytest
.
mark
.
django_db
def
test_invoice_detail
(
client
):
invoice
=
InvoiceFactory
()
...
...
example_invoice/tests/test_view_perm.py
View file @
39e3f90a
...
...
@@ -5,12 +5,23 @@ from django.urls import reverse
from
contact.tests.factories
import
ContactFactory
,
UserContactFactory
from
crm.tests.factories
import
CrmContactFactory
,
TicketFactory
from
invoice.tests.factories
import
InvoiceContactFactory
,
InvoiceFactory
from
invoice.tests.factories
import
(
BatchFactory
,
InvoiceContactFactory
,
InvoiceFactory
,
)
from
login.tests.factories
import
TEST_PASSWORD
,
UserFactory
from
login.tests.fixture
import
perm_check
from
login.tests.scenario
import
get_user_web
@
pytest
.
mark
.
django_db
def
test_batch_invoice_list
(
perm_check
):
batch
=
BatchFactory
()
url
=
reverse
(
"invoice.batch.invoice.list"
,
args
=
[
batch
.
pk
])
perm_check
.
staff
(
url
)
@
pytest
.
mark
.
django_db
def
test_contact_detail
(
perm_check
):
contact
=
ContactFactory
()
...
...
example_invoice/urls.py
View file @
39e3f90a
...
...
@@ -7,6 +7,7 @@ from django.views.generic import RedirectView
from
example_invoice.views
import
InvoiceListView
from
.views
import
(
BatchInvoiceListView
,
ContactDetailView
,
ContactListView
,
HomeView
,
...
...
@@ -22,6 +23,11 @@ urlpatterns = [
path
(
"admin/"
,
admin
.
site
.
urls
),
url
(
regex
=
r
"^$"
,
view
=
HomeView
.
as_view
(),
name
=
"project.home"
),
url
(
regex
=
r
"^"
,
view
=
include
(
"login.urls"
)),
url
(
regex
=
r
"^batch/(?P<pk>\d+)/invoice/$"
,
view
=
BatchInvoiceListView
.
as_view
(),
name
=
"invoice.batch.invoice.list"
,
),
url
(
regex
=
r
"^contact/"
,
view
=
include
(
"contact.urls"
)),
url
(
regex
=
r
"^contact/(?P<pk>\d+)/$"
,
...
...
example_invoice/views.py
View file @
39e3f90a
...
...
@@ -4,7 +4,21 @@ from django.views.generic import DetailView, TemplateView, ListView
from
base.view_utils
import
BaseMixin
from
contact.views
import
ContactDetailMixin
,
ContactListMixin
from
invoice.views
import
InvoiceDetailMixin
,
InvoiceListMixin
from
invoice.views
import
(
BatchInvoiceListMixin
,
InvoiceDetailMixin
,
InvoiceListMixin
,
)
class
BatchInvoiceListView
(
LoginRequiredMixin
,
StaffuserRequiredMixin
,
BatchInvoiceListMixin
,
BaseMixin
,
ListView
,
):
template_name
=
"example/batch_invoice_list.html"
class
ContactDetailView
(
...
...
invoice/forms.py
View file @
39e3f90a
...
...
@@ -9,6 +9,7 @@ from .models import (
get_contact_model
,
Invoice
,
InvoiceContact
,
InvoiceIssue
,
InvoiceLine
,
InvoiceUser
,
PaymentProcessor
,
...
...
@@ -52,6 +53,19 @@ class InvoiceBlankTodayForm(RequiredFieldForm):
fields
=
(
"currency"
,
"iteration_end"
)
class
InvoiceIssueForm
(
forms
.
ModelForm
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
().
__init__
(
*
args
,
**
kwargs
)
self
.
fields
[
"comment"
].
widget
.
attrs
.
update
(
{
"class"
:
"pure-input-1"
,
"rows"
:
5
}
)
self
.
fields
[
"confirmed"
].
label
=
"Issue resolved"
class
Meta
:
model
=
InvoiceIssue
fields
=
(
"confirmed"
,
"comment"
)
class
InvoiceEmptyForm
(
forms
.
ModelForm
):
class
Meta
:
model
=
Invoice
...
...
@@ -144,8 +158,8 @@ class TimeRecordForm(RequiredFieldForm):
class
SlugModelChoiceField
(
forms
.
ModelChoiceField
):
def
label_from_instance
(
self
,
obj
):
return
obj
.
slug
def
label_from_instance
(
self
,
x
):
return
x
.
slug
class
SearchForm
(
forms
.
Form
):
...
...
@@ -155,7 +169,7 @@ class SearchForm(forms.Form):
currency
=
SlugModelChoiceField
(
label
=
"Currency"
,
queryset
=
Currency
.
objects
.
none
(),
required
=
False
)
payment_processor
=
Slug
ModelChoiceField
(
payment_processor
=
forms
.
ModelChoiceField
(
label
=
"Payment"
,
queryset
=
PaymentProcessor
.
objects
.
none
(),
required
=
False
,
...
...
@@ -168,20 +182,25 @@ class SearchForm(forms.Form):
super
().
__init__
(
*
args
,
**
kwargs
)
# invoice date
f
=
self
.
fields
[
"invoice_date"
]
f
.
label
=
""
f
.
widget
.
attrs
.
update
({
"class"
:
"datepicker pure-u-23-24"
})
# invoice number
f
=
self
.
fields
[
"number"
]
f
.
label
=
""
f
.
widget
.
attrs
.
update
(
{
"class"
:
"pure-u-23-24"
,
"placeholder"
:
"Number"
}
)
# currency
f
=
self
.
fields
[
"currency"
]
f
.
empty_label
=
"All currencies..."
f
.
label
=
""
f
.
queryset
=
currency_qs
f
.
widget
.
attrs
.
update
({
"class"
:
"chosen-select pure-u-23-24"
})
# payment processor
f
=
self
.
fields
[
"payment_processor"
]
if
display_payment_processor
:
f
.
empty_label
=
"All processors..."
f
.
label
=
""
f
.
queryset
=
payment_processor_qs
f
.
widget
.
attrs
.
update
({
"class"
:
"chosen-select pure-u-23-24"
})
else
:
...
...
invoice/migrations/0022_auto_20191029_1235.py
0 → 100644
View file @
39e3f90a
# Generated by Django 2.2.6 on 2019-10-29 12:35
from
decimal
import
Decimal
from
django.conf
import
settings
from
django.db
import
migrations
,
models
import
django.db.models.deletion
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
migrations
.
swappable_dependency
(
settings
.
AUTH_USER_MODEL
),
(
"finance"
,
"0009_auto_20190731_1525"
),
(
"invoice"
,
"0021_auto_20190322_1235"
),
]
operations
=
[
migrations
.
CreateModel
(
name
=
"InvoiceIssue"
,
fields
=
[
(
"id"
,
models
.
AutoField
(
auto_created
=
True
,
primary_key
=
True
,
serialize
=
False
,
verbose_name
=
"ID"
,
),
),
(
"confirmed"
,
models
.
BooleanField
(
default
=
False
)),
(
"comment"
,
models
.
TextField
(
blank
=
True
,
null
=
True
)),
(
"confirmed_by_user"
,
models
.
ForeignKey
(
blank
=
True
,
null
=
True
,
on_delete
=
django
.
db
.
models
.
deletion
.
CASCADE
,
to
=
settings
.
AUTH_USER_MODEL
,
),
),
],
options
=
{
"verbose_name"
:
"Invoice Issue"
,
"verbose_name_plural"
:
"Invoice Issues"
,
"ordering"
:
(
"-invoice__invoice_date"
,
"-invoice__pk"
),
},
),
migrations
.
AlterModelOptions
(
name
=
"batch"
,
options
=
{
"ordering"
:
(
"batch_date"
,
"currency"
,
"exchange_rate"
,
"payment_processor"
,
),
"verbose_name"
:
"Invoice Batch"
,
"verbose_name_plural"
:
"Invoice Batches"
,
},
),
migrations
.
AlterModelOptions
(
name
=
"paymentprocessor"
,
options
=
{
"ordering"
:
(
"description"
,),
"verbose_name"
:
"Payment Processor"
,
},
),
migrations
.
RemoveField
(
model_name
=
"paymentprocessor"
,
name
=
"slug"
),
migrations
.
AddField
(
model_name
=
"batch"
,
name
=
"exchange_rate"
,
field
=
models
.
DecimalField
(
decimal_places
=
3
,
default
=
Decimal
(
"0"
),
max_digits
=
5
),
),
migrations
.
AddField
(
model_name
=
"invoice"
,
name
=
"exchange_rate"
,
field
=
models
.
DecimalField
(
decimal_places
=
3
,
default
=
Decimal
(
"0"
),
max_digits
=
5
),
),
migrations
.
AddField
(
model_name
=
"invoicecontact"
,
name
=
"country"
,
field
=
models
.
ForeignKey
(
blank
=
True
,
help_text
=
"Invoicing country (for VAT)"
,
null
=
True
,
on_delete
=
django
.
db
.
models
.
deletion
.
CASCADE
,
related_name
=
"+"
,
to
=
"finance.Country"
,
),
),
migrations
.
AddField
(
model_name
=
"invoicecontact"
,
name
=
"vat_number"
,
field
=
models
.
CharField
(
blank
=
True
,
max_length
=
50
),
),
migrations
.
AddField
(
model_name
=
"invoiceline"
,
name
=
"discount"
,
field
=
models
.
DecimalField
(
blank
=
True
,
decimal_places
=
2
,
max_digits
=
8
,
null
=
True
),
),
migrations
.
AlterField
(
model_name
=
"paymentprocessor"
,
name
=
"description"
,
field
=
models
.
CharField
(
max_length
=
100
,
unique
=
True
),
),
migrations
.
AlterUniqueTogether
(
name
=
"batch"
,
unique_together
=
{
(
"batch_date"
,
"currency"
,
"exchange_rate"
,
"payment_processor"
)
},
),
migrations
.
CreateModel
(
name
=
"InvoiceIssueLine"
,
fields
=
[
(
"id"
,
models
.
AutoField
(
auto_created
=
True
,
primary_key
=
True
,
serialize
=
False
,
verbose_name
=
"ID"
,
),
),
(
"created"
,
models
.
DateTimeField
(
auto_now_add
=
True
)),
(
"modified"
,
models
.
DateTimeField
(
auto_now
=
True
)),
(
"description"
,
models
.
TextField
()),
(
"invoice_issue"
,
models
.
ForeignKey
(
on_delete
=
django
.
db
.
models
.
deletion
.
CASCADE
,
to
=
"invoice.InvoiceIssue"
,
),
),
],
options
=
{
"verbose_name"
:
"Invoice Issue Line"
,
"verbose_name_plural"
:
"Invoice Issue Lines"
,
"ordering"
:
(
"-invoice_issue__invoice__invoice_date"
,
"-invoice_issue__invoice__pk"
,
"-pk"
,
),
},
),
migrations
.
AddField
(
model_name
=
"invoiceissue"
,
name
=
"invoice"
,
field
=
models
.
OneToOneField
(
on_delete
=
django
.
db
.
models
.
deletion
.
CASCADE
,
to
=
"invoice.Invoice"
,
),
),
]
invoice/models.py
View file @
39e3f90a
...
...
@@ -4,7 +4,7 @@ import collections
from
datetime
import
date
,
datetime
from
dateutil.relativedelta
import
relativedelta
from
dateutil.rrule
import
WEEKLY
,
rrule
,
SU
from
dateutil.rrule
import
SU
,
WEEKLY
,
rrule
from
decimal
import
Decimal
from
django.apps
import
apps
from
django.conf
import
settings
...
...
@@ -16,15 +16,15 @@ from django.utils.timesince import timeuntil
from
reversion
import
revisions
as
reversion
from
base.model_utils
import
(
private_file_store
,
TimedCreateModifyDeleteModel
,
TimedCreateModifyDeleteVersionModel
,
TimedCreateModifyDeleteVersionModelManager
,
TimeStampedModel
,
private_file_store
,
)
from
base.singleton
import
SingletonModel
from
crm.models
import
Note
,
Ticket
from
finance.models
import
VatCode
,
Currency
from
finance.models
import
Country
,
Currency
,
VatCode
,
VatSettings
from
stock.models
import
Product
...
...
@@ -178,6 +178,43 @@ class TimeAnalysis:
return
format_minutes
(
self
.
charge
+
self
.
fixed
+
self
.
non_charge
)
class
InvoiceContactManager
(
models
.
Manager
):
def
_create_invoice_contact
(
self
,
contact
,
country
,
hourly_rate
,
vat_number
):
x
=
self
.
model
(
contact
=
contact
,
country
=
country
,
hourly_rate
=
hourly_rate
,
vat_number
=
vat_number
or
""
,
)
x
.
save
()
return
x
def
init_invoice_contact
(
self
,
contact
,
country
=
None
,
hourly_rate
=
None
,
vat_number
=
None
):
try
:
x
=
self
.
model
.
objects
.
get
(
contact
=
contact
)
update
=
False
if
country
:
x
.
country
=
country
update
=
True
if
hourly_rate
:
x
.
hourly_rate
=
hourly_rate
update
=
True
if
vat_number
:
x
.
vat_number
=
vat_number
update
=
True
if
update
:
x
.
save
()
except
self
.
model
.
DoesNotExist
:
x
=
self
.
_create_invoice_contact
(
contact
,
country
,
hourly_rate
,
vat_number
)
return
x
class
InvoiceContact
(
TimeStampedModel
):
contact
=
models
.
OneToOneField
(
...
...
@@ -186,6 +223,19 @@ class InvoiceContact(TimeStampedModel):
hourly_rate
=
models
.
DecimalField
(
blank
=
True
,
null
=
True
,
max_digits
=
8
,
decimal_places
=
2
)
country
=
models
.
ForeignKey
(
Country
,
help_text
=
"Invoicing country (for VAT)"
,
blank
=
True
,
null
=
True
,
on_delete
=
models
.
CASCADE
,
related_name
=
"+"
,
)
# Maximum of 12 characters?
# https://www.gov.uk/guidance/vat-eu-country-codes-vat-numbers-and-vat-in-other-languages
# 10/09/2019 The Xero 'TaxNumber' has a maximum length of 50 characters.
vat_number
=
models
.
CharField
(
max_length
=
50
,
blank
=
True
)
objects
=
InvoiceContactManager
()
class
Meta
:
verbose_name
=
"Invoice Contact"
...
...
@@ -195,11 +245,21 @@ class InvoiceContact(TimeStampedModel):
result
=
"{}"
.
format
(
self
.
contact
.
get_full_name
)
if
self
.
hourly_rate
:
result
=
"{} @ {}"
.
format
(
result
,
self
.
hourly_rate
)
if
self
.
vat_number
:
result
=
"{}, VAT Number {}"
.
format
(
result
,
self
.
vat_number
)
return
result
def
get_absolute_url
(
self
):
return
self
.
contact
.
get_absolute_url
()
def
is_european_union_vat_transaction
(
self
):
result
=
False
if
self
.
country
and
self
.
vat_number
:
if
VatSettings
.
objects
.
european_union_countries_initialised
():
if
VatSettings
.
objects
.
is_european_union_country
(
self
.
country
):
result
=
True
return
result
reversion
.
register
(
InvoiceContact
)
...
...
@@ -214,35 +274,25 @@ class InvoiceError(Exception):
class
PaymentProcessorManager
(
models
.
Manager
):
def
_create_payment_processor
(
self
,
slug
,
description
):
x
=
self
.
model
(
slug
=
slug
,
description
=
description
)
def
create_payment_processor
(
self
,
description
):
x
=
self
.
model
(
description
=
description
)
x
.
save
()
return
x
def
init_payment_processor
(
self
,
slug
,
description
):
try
:
x
=
self
.
model
.
objects
.
get
(
slug
=
slug
)
x
.
description
=
description
x
.
save
()
except
self
.
model
.
DoesNotExist
:
x
=
self
.
_create_payment_processor
(
slug
,
description
)
return
x
class
PaymentProcessor
(
TimeStampedModel
):
slug
=
models
.
SlugField
(
max_length
=
30
,
unique
=
True
)
description
=
models
.
CharField
(
max_length
=
100
)
description
=
models
.
CharField
(
max_length
=
100
,
unique
=
True
)
deleted
=
models
.
BooleanField
(
default
=
False
)
objects
=
PaymentProcessorManager
()
class
Meta
:
ordering
=
(
"description"
,)
verbose_name
=
"Payment Processor"
def
__str__
(
self
):
if
self
.
description
:
return
"{} - {}"
.
format
(
self
.
slug
,
self
.
description
)
else
:
return
self
.
slug
return
"{}{}"
.
format
(
self
.
description
,
" (deleted)"
if
self
.
deleted
else
""
)
class
InvoiceManager
(
TimedCreateModifyDeleteVersionModelManager
):
...
...
@@ -260,13 +310,15 @@ class InvoiceManager(TimedCreateModifyDeleteVersionModelManager):
def
not_upfront_payment
(
self
,
invoice_date
):
"""List of invoices which cannot be added to a batch.
We use a ``Batch`` to group invoices which were for paid *on order*
(``upfront_payment=True``).
- Exclude credit notes.
- We use a ``Batch`` to group invoices which were paid for *on order*
(``upfront_payment=True``).