Added date_hierarchy indexes and improvements on billing
This commit is contained in:
parent
a364f88452
commit
09025102bc
2
TODO.md
2
TODO.md
|
@ -450,4 +450,4 @@ def comma(value):
|
|||
return value
|
||||
|
||||
|
||||
# Close, send + download admin action for bills (with confirmation)
|
||||
# db_index on date_hierarchy
|
||||
|
|
|
@ -107,7 +107,7 @@ class ChangeViewActionsMixin(object):
|
|||
verbose_name = verbose_name(obj)
|
||||
view.verbose_name = verbose_name
|
||||
view.css_class = getattr(action, 'css_class', 'historylink')
|
||||
view.description = getattr(action, 'description', '')
|
||||
view.help_text = getattr(action, 'help_text', '')
|
||||
views.append(view)
|
||||
return views
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ from django.utils.safestring import mark_safe
|
|||
from django.utils.translation import ungettext, ugettext_lazy as _
|
||||
|
||||
from orchestra.admin.forms import adminmodelformset_factory
|
||||
from orchestra.admin.decorators import action_with_confirmation
|
||||
from orchestra.admin.utils import get_object_from_url, change_url
|
||||
from orchestra.utils.html import html_to_pdf
|
||||
|
||||
|
@ -23,24 +24,6 @@ from .helpers import validate_contact
|
|||
from .models import Bill, BillLine
|
||||
|
||||
|
||||
def download_bills(modeladmin, request, queryset):
|
||||
if queryset.count() > 1:
|
||||
bytesio = io.BytesIO()
|
||||
archive = zipfile.ZipFile(bytesio, 'w')
|
||||
for bill in queryset:
|
||||
pdf = bill.as_pdf()
|
||||
archive.writestr('%s.pdf' % bill.number, pdf)
|
||||
archive.close()
|
||||
response = HttpResponse(bytesio.getvalue(), content_type='application/pdf')
|
||||
response['Content-Disposition'] = 'attachment; filename="orchestra-bills.zip"'
|
||||
return response
|
||||
bill = queryset.get()
|
||||
pdf = bill.as_pdf()
|
||||
return HttpResponse(pdf, content_type='application/pdf')
|
||||
download_bills.verbose_name = _("Download")
|
||||
download_bills.url_name = 'download'
|
||||
|
||||
|
||||
def view_bill(modeladmin, request, queryset):
|
||||
bill = queryset.get()
|
||||
if not validate_contact(request, bill):
|
||||
|
@ -111,6 +94,7 @@ close_bills.verbose_name = _("Close")
|
|||
close_bills.url_name = 'close'
|
||||
|
||||
|
||||
@action_with_confirmation()
|
||||
def send_bills(modeladmin, request, queryset):
|
||||
for bill in queryset:
|
||||
if not validate_contact(request, bill):
|
||||
|
@ -128,12 +112,41 @@ send_bills.verbose_name = lambda bill: _("Resend" if getattr(bill, 'is_sent', Fa
|
|||
send_bills.url_name = 'send'
|
||||
|
||||
|
||||
def download_bills(modeladmin, request, queryset):
|
||||
if queryset.count() > 1:
|
||||
bytesio = io.BytesIO()
|
||||
archive = zipfile.ZipFile(bytesio, 'w')
|
||||
for bill in queryset:
|
||||
pdf = bill.as_pdf()
|
||||
archive.writestr('%s.pdf' % bill.number, pdf)
|
||||
archive.close()
|
||||
response = HttpResponse(bytesio.getvalue(), content_type='application/pdf')
|
||||
response['Content-Disposition'] = 'attachment; filename="orchestra-bills.zip"'
|
||||
return response
|
||||
bill = queryset.get()
|
||||
pdf = bill.as_pdf()
|
||||
return HttpResponse(pdf, content_type='application/pdf')
|
||||
download_bills.verbose_name = _("Download")
|
||||
download_bills.url_name = 'download'
|
||||
|
||||
|
||||
def close_send_download_bills(modeladmin, request, queryset):
|
||||
close_bills(modeladmin, request, queryset)
|
||||
if request.POST.get('post') == 'generic_confirmation':
|
||||
send_bills(modeladmin, request, queryset)
|
||||
return download_bills(modeladmin, request, queryset)
|
||||
close_send_download_bills.verbose_name = _("C.S.D.")
|
||||
close_send_download_bills.url_name = 'close-send-download'
|
||||
close_send_download_bills.help_text = _("Close, send and download bills in one shot.")
|
||||
|
||||
|
||||
def manage_lines(modeladmin, request, queryset):
|
||||
url = reverse('admin:bills_bill_manage_lines')
|
||||
url += '?ids=%s' % ','.join(map(str, queryset.values_list('id', flat=True)))
|
||||
return redirect(url)
|
||||
|
||||
|
||||
@action_with_confirmation()
|
||||
def undo_billing(modeladmin, request, queryset):
|
||||
group = {}
|
||||
for line in queryset.select_related('order'):
|
||||
|
@ -214,6 +227,7 @@ def copy_lines(modeladmin, request, queryset):
|
|||
return move_lines(modeladmin, request, queryset)
|
||||
|
||||
|
||||
@action_with_confirmation()
|
||||
def amend_bills(modeladmin, request, queryset):
|
||||
if queryset.filter(is_open=True).exists():
|
||||
messages.warning(request, _("Selected bills should be in closed state"))
|
||||
|
|
|
@ -207,11 +207,11 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
search_fields = ('number', 'account__username', 'comments')
|
||||
change_view_actions = [
|
||||
actions.manage_lines, actions.view_bill, actions.download_bills, actions.send_bills,
|
||||
actions.close_bills, actions.amend_bills,
|
||||
actions.close_bills, actions.amend_bills, actions.close_send_download_bills,
|
||||
]
|
||||
actions = [
|
||||
actions.manage_lines, actions.download_bills, actions.close_bills, actions.send_bills,
|
||||
actions.amend_bills, actions.report
|
||||
actions.amend_bills, actions.report, actions.close_send_download_bills,
|
||||
]
|
||||
change_readonly_fields = ('account_link', 'type', 'is_open', 'amend_of_link', 'amend_links')
|
||||
readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state')
|
||||
|
@ -303,7 +303,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
exclude = []
|
||||
if obj:
|
||||
if not obj.is_open:
|
||||
exclude.append('close_bills')
|
||||
exclude += ['close_bills', 'close_send_download_bills']
|
||||
return [action for action in actions if action.__name__ not in exclude]
|
||||
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -102,7 +102,7 @@ class Bill(models.Model):
|
|||
related_name='amends')
|
||||
type = models.CharField(_("type"), max_length=16, choices=TYPES)
|
||||
created_on = models.DateField(_("created on"), auto_now_add=True)
|
||||
closed_on = models.DateField(_("closed on"), blank=True, null=True)
|
||||
closed_on = models.DateField(_("closed on"), blank=True, null=True, db_index=True)
|
||||
is_open = models.BooleanField(_("open"), default=True)
|
||||
is_sent = models.BooleanField(_("sent"), default=False)
|
||||
due_on = models.DateField(_("due on"), null=True, blank=True)
|
||||
|
|
|
@ -49,7 +49,7 @@ def change_ticket_state_factory(action, final_state):
|
|||
change_ticket_state.url_name = action
|
||||
change_ticket_state.verbose_name = action
|
||||
change_ticket_state.short_description = _('%s selected tickets') % action.capitalize()
|
||||
change_ticket_state.description = _('Mark ticket as %s.') % final_state.lower()
|
||||
change_ticket_state.help_text = _('Mark ticket as %s.') % final_state.lower()
|
||||
change_ticket_state.__name__ = action
|
||||
return change_ticket_state
|
||||
|
||||
|
@ -90,7 +90,7 @@ def take_tickets(modeladmin, request, queryset):
|
|||
modeladmin.message_user(request, msg)
|
||||
take_tickets.url_name = 'take'
|
||||
take_tickets.short_description = _("Take selected tickets")
|
||||
take_tickets.description = _("Make yourself owner of the ticket.")
|
||||
take_tickets.help_text = _("Make yourself owner of the ticket.")
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('issues', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ticket',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(db_index=True, auto_now_add=True, verbose_name='created'),
|
||||
),
|
||||
]
|
|
@ -68,7 +68,7 @@ class Ticket(models.Model):
|
|||
priority = models.CharField(_("priority"), max_length=32, choices=PRIORITIES,
|
||||
default=MEDIUM)
|
||||
state = models.CharField(_("state"), max_length=32, choices=STATES, default=NEW)
|
||||
created_at = models.DateTimeField(_("created"), auto_now_add=True)
|
||||
created_at = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
|
||||
updated_at = models.DateTimeField(_("modified"), auto_now=True)
|
||||
cc = models.TextField("CC", help_text=_("emails to send a carbon copy to"),
|
||||
blank=True)
|
||||
|
|
|
@ -118,7 +118,7 @@ class BackendLogAdmin(admin.ModelAdmin):
|
|||
'display_created', 'execution_time',
|
||||
)
|
||||
list_display_links = ('id', 'backend')
|
||||
list_filter = ('state', 'backend')
|
||||
list_filter = ('state', 'backend', 'server')
|
||||
date_hierarchy = 'created_at'
|
||||
inlines = (BackendOperationInline,)
|
||||
fields = (
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import orchestra.models.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orchestration', '0004_route_async_actions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='backendlog',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(db_index=True, verbose_name='created', auto_now_add=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='route',
|
||||
name='async_actions',
|
||||
field=orchestra.models.fields.MultiSelectField(blank=True, help_text='Specify individual actions to be executed asynchronoulsy.', max_length=256),
|
||||
),
|
||||
]
|
|
@ -79,7 +79,7 @@ class BackendLog(models.Model):
|
|||
exit_code = models.IntegerField(_("exit code"), null=True)
|
||||
task_id = models.CharField(_("task ID"), max_length=36, unique=True, null=True,
|
||||
help_text="Celery task ID when used as execution backend")
|
||||
created_at = models.DateTimeField(_("created"), auto_now_add=True)
|
||||
created_at = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
|
||||
updated_at = models.DateTimeField(_("updated"), auto_now=True)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orders', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='order',
|
||||
name='registered_on',
|
||||
field=models.DateField(db_index=True, default=django.utils.timezone.now, verbose_name='registered'),
|
||||
),
|
||||
]
|
|
@ -112,7 +112,7 @@ class Order(models.Model):
|
|||
object_id = models.PositiveIntegerField(null=True)
|
||||
service = models.ForeignKey(settings.ORDERS_SERVICE_MODEL, verbose_name=_("service"),
|
||||
related_name='orders')
|
||||
registered_on = models.DateField(_("registered"), default=timezone.now)
|
||||
registered_on = models.DateField(_("registered"), default=timezone.now, db_index=True)
|
||||
cancelled_on = models.DateField(_("cancelled"), null=True, blank=True)
|
||||
billed_on = models.DateField(_("billed"), null=True, blank=True)
|
||||
billed_until = models.DateField(_("billed until"), null=True, blank=True)
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.db.models.deletion
|
||||
import orchestra.models.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('payments', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='paymentsource',
|
||||
name='method',
|
||||
field=models.CharField(max_length=32, verbose_name='method', choices=[('SEPADirectDebit', 'SEPA Direct Debit')]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transaction',
|
||||
name='process',
|
||||
field=models.ForeignKey(verbose_name='process', on_delete=django.db.models.deletion.SET_NULL, blank=True, related_name='transactions', to='payments.TransactionProcess', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transactionprocess',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(db_index=True, verbose_name='created', auto_now_add=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transactionprocess',
|
||||
name='file',
|
||||
field=orchestra.models.fields.PrivateFileField(blank=True, verbose_name='file', upload_to=''),
|
||||
),
|
||||
]
|
|
@ -166,7 +166,7 @@ class TransactionProcess(models.Model):
|
|||
data = JSONField(_("data"), blank=True)
|
||||
file = PrivateFileField(_("file"), blank=True)
|
||||
state = models.CharField(_("state"), max_length=16, choices=STATES, default=CREATED)
|
||||
created_at = models.DateTimeField(_("created"), auto_now_add=True)
|
||||
created_at = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
|
||||
updated_at = models.DateTimeField(_("updated"), auto_now=True)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
{% block object-tools-items %}
|
||||
{% for item in object_tools_items %}
|
||||
<li><a href="{{ item.url_name }}/" class="{{ item.css_class }}" title="{{ item.description }}">{{ item.verbose_name }}</a></li>
|
||||
<li><a href="{{ item.url_name }}/" class="{{ item.css_class }}" title="{{ item.help_text }}">{{ item.verbose_name }}</a></li>
|
||||
{% endfor %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in a new issue