diff --git a/ROADMAP.md b/ROADMAP.md
index 4f44b598..2d5656e2 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -12,12 +12,13 @@
2. [x] [Orchestra-orm](https://github.com/glic3rinu/orchestra-orm) a Python library for easily interacting with the REST API
3. [x] Service orchestration framework
4. [ ] Data model, input validation, admin and REST interfaces, permissions, unit and functional tests, service management, migration scripts and some documentation of:
- 1. [ ] Web applications and FTP accounts
+ 1. [x] Web applications
+ 2. [ ] FTP accounts
2. [ ] Databases
1. [ ] Mail accounts, aliases, forwards
- 1. [ ] DNS
+ 1. [x] DNS
1. [ ] Mailing lists
-1. [ ] Contact management and service contraction
+1. [x] Contact management and service contraction
1. [ ] Object level permissions system
1. [ ] Unittests of all the logic
2. [ ] Functional tests of all Admin and REST interations
@@ -26,12 +27,12 @@
### 1.0b1 Milestone (first beta release on Jul '14)
-1. [ ] Resource monitoring
+1. [x] Resource monitoring
1. [ ] Orders
2. [ ] Pricing
3. [ ] Billing
-1. [ ] Payment gateways
-2. [ ] Scheduling of service cancellations
+1. [ ] Payment methods
+2. [ ] Scheduling of service cancellations and deactivations
1. [ ] Full documentation
@@ -41,3 +42,4 @@
1. [ ] Integration with third-party service providers, e.g. Gandi
1. [ ] Support for additional services like VPS
2. [ ] Issue tracking system
+3. [ ] Translation to Spanish and Catalan
diff --git a/orchestra/apps/accounts/admin.py b/orchestra/apps/accounts/admin.py
index 856ccbd7..2f0c7eef 100644
--- a/orchestra/apps/accounts/admin.py
+++ b/orchestra/apps/accounts/admin.py
@@ -42,6 +42,7 @@ class AccountAdmin(ExtendedModelAdmin):
search_fields = ('users__username',)
add_form = AccountCreationForm
form = AccountChangeForm
+ change_form_template = 'admin/accounts/account/change_form.html'
user_link = admin_link('user', order='user__username')
diff --git a/orchestra/apps/accounts/templates/admin/accounts/account/change_list.html b/orchestra/apps/accounts/templates/admin/accounts/account/change_list.html
index 9901fd38..ea9d101b 100644
--- a/orchestra/apps/accounts/templates/admin/accounts/account/change_list.html
+++ b/orchestra/apps/accounts/templates/admin/accounts/account/change_list.html
@@ -19,6 +19,9 @@
{% block object-tools-items %}
{% if from_account %}
+
+ {{ account|truncatewords:"18" }}
+
{% trans 'Show all' %}
diff --git a/orchestra/apps/bills/admin.py b/orchestra/apps/bills/admin.py
index 5002131b..dcba6b74 100644
--- a/orchestra/apps/bills/admin.py
+++ b/orchestra/apps/bills/admin.py
@@ -1,3 +1,4 @@
+from django import forms
from django.contrib import admin
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
@@ -17,6 +18,21 @@ class BillLineInline(admin.TabularInline):
fields = (
'description', 'initial_date', 'final_date', 'price', 'amount', 'tax'
)
+
+ def get_readonly_fields(self, request, obj=None):
+ if obj and obj.status != Bill.OPEN:
+ return self.fields
+ return super(BillLineInline, self).get_readonly_fields(request, obj=obj)
+
+ def has_add_permission(self, request):
+ if request.__bill__ and request.__bill__.status != Bill.OPEN:
+ return False
+ return super(BillLineInline, self).has_add_permission(request)
+
+ def has_delete_permission(self, request, obj=None):
+ if obj and obj.status != Bill.OPEN:
+ return False
+ return super(BillLineInline, self).has_delete_permission(request, obj=obj)
class BudgetLineInline(admin.TabularInline):
@@ -28,15 +44,25 @@ class BudgetLineInline(admin.TabularInline):
class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
list_display = (
- 'ident', 'status', 'type_link', 'account_link', 'created_on_display'
+ 'number', 'status', 'type_link', 'account_link', 'created_on_display'
)
list_filter = (BillTypeListFilter, 'status',)
+ add_fields = ('account', 'type', 'status', 'due_on', 'comments')
+ fieldsets = (
+ (None, {
+ 'fields': ('number', 'account_link', 'type', 'status', 'due_on',
+ 'comments'),
+ }),
+ (_("Raw"), {
+ 'classes': ('collapse',),
+ 'fields': ('html',),
+ }),
+ )
change_view_actions = [generate_bill]
- change_readonly_fields = ('account', 'type', 'status')
- readonly_fields = ('ident',)
+ change_readonly_fields = ('account_link', 'type', 'status')
+ readonly_fields = ('number',)
inlines = [BillLineInline]
- account_link = admin_link('account')
created_on_display = admin_date('created_on')
def type_link(self, bill):
@@ -47,11 +73,27 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
type_link.short_description = _("type")
type_link.admin_order_field = 'type'
+ def get_readonly_fields(self, request, obj=None):
+ fields = super(BillAdmin, self).get_readonly_fields(request, obj=obj)
+ if obj and obj.status != Bill.OPEN:
+ fields += self.add_fields
+ return fields
+
def get_inline_instances(self, request, obj=None):
if self.model is Budget:
self.inlines = [BudgetLineInline]
+ # Make parent object available for inline.has_add_permission()
+ request.__bill__ = obj
return super(BillAdmin, self).get_inline_instances(request, obj=obj)
-
+
+ def formfield_for_dbfield(self, db_field, **kwargs):
+ """ Make value input widget bigger """
+ if db_field.name == 'comments':
+ kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 4})
+ if db_field.name == 'html':
+ kwargs['widget'] = forms.Textarea(attrs={'cols': 150, 'rows': 20})
+ return super(BillAdmin, self).formfield_for_dbfield(db_field, **kwargs)
+
admin.site.register(Bill, BillAdmin)
admin.site.register(Invoice, BillAdmin)
diff --git a/orchestra/apps/bills/models.py b/orchestra/apps/bills/models.py
index ad62defc..bce7b2d4 100644
--- a/orchestra/apps/bills/models.py
+++ b/orchestra/apps/bills/models.py
@@ -1,3 +1,5 @@
+import inspect
+
from django.db import models
from django.template import loader, Context
from django.utils import timezone
@@ -43,7 +45,7 @@ class Bill(models.Model):
('BUDGET', _("Budget")),
)
- ident = models.CharField(_("identifier"), max_length=16, unique=True,
+ number = models.CharField(_("number"), max_length=16, unique=True,
blank=True)
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
related_name='%(class)s')
@@ -62,7 +64,7 @@ class Bill(models.Model):
objects = BillManager()
def __unicode__(self):
- return self.ident
+ return self.number
@cached_property
def seller(self):
@@ -77,31 +79,34 @@ class Bill(models.Model):
return self.billlines
@classmethod
- def get_type(cls):
+ def get_class_type(cls):
return cls.__name__.upper()
- def set_ident(self):
+ def get_type(self):
+ return self.type or self.get_class_type()
+
+ def set_number(self):
cls = type(self)
- bill_type = self.type or cls.get_type()
+ bill_type = self.get_type()
if bill_type == 'BILL':
- raise TypeError("get_new_ident() can not be used on a Bill class")
+ raise TypeError("get_new_number() can not be used on a Bill class")
# Bill number resets every natural year
year = timezone.now().strftime("%Y")
bills = cls.objects.filter(created_on__year=year)
- number_length = settings.BILLS_IDENT_NUMBER_LENGTH
- prefix = getattr(settings, 'BILLS_%s_IDENT_PREFIX' % bill_type)
+ number_length = settings.BILLS_NUMBER_LENGTH
+ prefix = getattr(settings, 'BILLS_%s_NUMBER_PREFIX' % bill_type)
if self.status == self.OPEN:
prefix = 'O{}'.format(prefix)
bills = bills.filter(status=self.OPEN)
- num_bills = bills.order_by('-ident').first() or 0
+ num_bills = bills.order_by('-number').first() or 0
if num_bills is not 0:
- num_bills = int(num_bills.ident[-number_length:])
+ num_bills = int(num_bills.number[-number_length:])
else:
bills = bills.exclude(status=self.OPEN)
num_bills = bills.count()
zeros = (number_length - len(str(num_bills))) * '0'
number = zeros + str(num_bills + 1)
- self.ident = '{prefix}{year}{number}'.format(
+ self.number = '{prefix}{year}{number}'.format(
prefix=prefix, year=year, number=number)
def close(self):
@@ -130,9 +135,9 @@ class Bill(models.Model):
def save(self, *args, **kwargs):
if not self.type:
- self.type = type(self).get_type()
- if not self.ident or (self.ident.startswith('O') and self.status != self.OPEN):
- self.set_ident()
+ self.type = self.get_type()
+ if not self.number or (self.number.startswith('O') and self.status != self.OPEN):
+ self.set_number()
super(Bill, self).save(*args, **kwargs)
@@ -177,6 +182,14 @@ class BaseBillLine(models.Model):
class Meta:
abstract = True
+
+ def __unicode__(self):
+ return "#%i" % self.number
+
+ @property
+ def number(self):
+ lines = type(self).objects.filter(bill=self.bill_id)
+ return lines.filter(id__lte=self.id).order_by('id').count()
class BudgetLine(BaseBillLine):
diff --git a/orchestra/apps/bills/serializers.py b/orchestra/apps/bills/serializers.py
index d8180ebf..9d747722 100644
--- a/orchestra/apps/bills/serializers.py
+++ b/orchestra/apps/bills/serializers.py
@@ -17,6 +17,6 @@ class BillSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSeriali
class Meta:
model = Bill
fields = (
- 'url', 'ident', 'bill_type', 'status', 'created_on', 'due_on',
+ 'url', 'number', 'bill_type', 'status', 'created_on', 'due_on',
'comments', 'html', 'lines'
)
diff --git a/orchestra/apps/bills/settings.py b/orchestra/apps/bills/settings.py
index 8817347a..b00c5935 100644
--- a/orchestra/apps/bills/settings.py
+++ b/orchestra/apps/bills/settings.py
@@ -1,17 +1,17 @@
from django.conf import settings
-BILLS_IDENT_NUMBER_LENGTH = getattr(settings, 'BILLS_IDENT_NUMBER_LENGTH', 4)
+BILLS_NUMBER_LENGTH = getattr(settings, 'BILLS_NUMBER_LENGTH', 4)
-BILLS_INVOICE_IDENT_PREFIX = getattr(settings, 'BILLS_INVOICE_IDENT_PREFIX', 'I')
+BILLS_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_INVOICE_NUMBER_PREFIX', 'I')
-BILLS_AMENDMENT_INVOICE_IDENT_PREFIX = getattr(settings, 'BILLS_AMENDMENT_INVOICE_IDENT_PREFIX', 'A')
+BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX', 'A')
-BILLS_FEE_IDENT_PREFIX = getattr(settings, 'BILLS_FEE_IDENT_PREFIX', 'F')
+BILLS_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_FEE_NUMBER_PREFIX', 'F')
-BILLS_AMENDMENT_FEE_IDENT_PREFIX = getattr(settings, 'BILLS_AMENDMENT_FEE_IDENT_PREFIX', 'B')
+BILLS_AMENDMENT_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_FEE_NUMBER_PREFIX', 'B')
-BILLS_BUDGET_IDENT_PREFIX = getattr(settings, 'BILLS_BUDGET_IDENT_PREFIX', 'Q')
+BILLS_BUDGET_NUMBER_PREFIX = getattr(settings, 'BILLS_BUDGET_NUMBER_PREFIX', 'Q')
BILLS_INVOICE_TEMPLATE = getattr(settings, 'BILLS_INVOICE_TEMPLATE', 'bills/microspective.html')
diff --git a/orchestra/apps/bills/templates/bills/base.html b/orchestra/apps/bills/templates/bills/base.html
index 5990ab52..ce8a782e 100644
--- a/orchestra/apps/bills/templates/bills/base.html
+++ b/orchestra/apps/bills/templates/bills/base.html
@@ -1,6 +1,6 @@
- {% block title %}Invoice - I20110223{% endblock %}
+ {% block title %}{{ bill.get_type_display }} - {{ bill.number }}{% endblock %}
{% block head %}{% endblock %}
diff --git a/orchestra/apps/bills/templates/bills/microspective.html b/orchestra/apps/bills/templates/bills/microspective.html
index 304f1020..ecbd0b20 100644
--- a/orchestra/apps/bills/templates/bills/microspective.html
+++ b/orchestra/apps/bills/templates/bills/microspective.html
@@ -21,48 +21,47 @@
- Associacio Pangea -
- Coordinadora Comunicacio per a la Cooperacio
+ {{ seller.name }}
{% endblock %}
{% block summary %}
- Invoice
- F20110232
+ {{ bill.get_type_display }}
+ {{ bill.number }}
DUE DATE
-
Nov 21, 2011
+ {{ bill.due_on|date }}
TOTAL
-
122,03 €
+ {{ bill.total }} &{{ currency.lower }};
-
INVOICE DATE
-
Oct 20, 2012
+ {{ bill.get_type_display.upper }} DATE
+ {{ bill.created_on|date }}
- Aadults
- ES01939933
- Carrer nnoseque, 0
- 08034 - Barcelona
- Spain
+ {{ buyer.name }}
+ {{ buyer.vat }}
+ {{ buyer.address }}
+ {{ buyer.zipcode }} - {{ buyer.city }}
+ {{ buyer.country }}
{% endblock %}
@@ -74,50 +73,24 @@
rate/price
subtotal
-
- 1
-
-
- Hola que passa
- nosquevols
-
- 1
-
-
- 1,00 €
-
-
- 111,00 €
- -10,00 €
-
- 1
- Merda pura
- 1
- 1,00 €
- 111,00 €
-
- 1
- I tu que et passa
- 1
- 1,00 €
- 111,00 €
-
- 1
- Joder hostia puta
- 1
- 1,00 €
- 111,00 €
+ {% for line in bill.lines.all %}
+ {{ line.id }}
+ {{ line.description }}
+ {{ line.amount }}
+ {{ line.rate }}
+ {{ line.price }} &{{ currency.lower }};
+ {% endfor %}
subtotal
- 33,03 €
+ {{ bill.subtotal }} &{{ currency.lower }};
tax
- 33,03 €
+ {{ bill.taxes }} &{{ currency.lower }};
total
- 33,03 €
+ {{ bill.total }} &{{ currency.lower }};
{% endblock %}
@@ -126,13 +99,14 @@