diff --git a/orchestra/apps/accounts/admin.py b/orchestra/apps/accounts/admin.py
index d0d507ee..cc4d760d 100644
--- a/orchestra/apps/accounts/admin.py
+++ b/orchestra/apps/accounts/admin.py
@@ -137,7 +137,8 @@ class AccountAdminMixin(object):
def account_link(self, instance):
account = instance.account if instance.pk else self.account
url = reverse('admin:accounts_account_change', args=(account.pk,))
- return '%s' % (url, account.name)
+ pk = account.pk
+ return '%s' % (url, str(account))
account_link.short_description = _("account")
account_link.allow_tags = True
account_link.admin_order_field = 'account__user__username'
diff --git a/orchestra/apps/accounts/models.py b/orchestra/apps/accounts/models.py
index ecc4c52d..a35d9b02 100644
--- a/orchestra/apps/accounts/models.py
+++ b/orchestra/apps/accounts/models.py
@@ -10,6 +10,7 @@ from . import settings
class Account(models.Model):
+ # Users depends on Accounts (think about what should happen when you delete an account)
user = models.OneToOneField(djsettings.AUTH_USER_MODEL,
verbose_name=_("user"), related_name='accounts', null=True)
type = models.CharField(_("type"), choices=settings.ACCOUNTS_TYPES,
@@ -24,9 +25,9 @@ class Account(models.Model):
def __unicode__(self):
return self.name
- @cached_property
+ @property
def name(self):
- return self.user.username
+ return self.user.username if self.user_id else str(self.pk)
@classmethod
def get_main(cls):
diff --git a/orchestra/apps/bills/models.py b/orchestra/apps/bills/models.py
index b29c8403..47012c3d 100644
--- a/orchestra/apps/bills/models.py
+++ b/orchestra/apps/bills/models.py
@@ -220,6 +220,9 @@ class BillLine(models.Model):
amount = models.DecimalField(_("amount"), max_digits=12, decimal_places=2)
total = models.DecimalField(_("total"), max_digits=12, decimal_places=2)
tax = models.PositiveIntegerField(_("tax"))
+ # TODO
+# order_id = models.ForeignKey('orders.Order', null=True, blank=True,
+# help_text=_("Informative link back to the order"))
amended_line = models.ForeignKey('self', verbose_name=_("amended line"),
related_name='amendment_lines', null=True, blank=True)
diff --git a/orchestra/apps/databases/backends.py b/orchestra/apps/databases/backends.py
index 5606e657..5579adb5 100644
--- a/orchestra/apps/databases/backends.py
+++ b/orchestra/apps/databases/backends.py
@@ -73,7 +73,7 @@ class MySQLPermissionBackend(ServiceController):
class MysqlDisk(ServiceMonitor):
- model = 'database.Database'
+ model = 'databases.Database'
verbose_name = _("MySQL disk")
def exceeded(self, db):
diff --git a/orchestra/apps/orders/actions.py b/orchestra/apps/orders/actions.py
index b4a053f1..964a9e47 100644
--- a/orchestra/apps/orders/actions.py
+++ b/orchestra/apps/orders/actions.py
@@ -53,6 +53,8 @@ class BillSelectedOrders(object):
def select_related(self, request):
related = self.queryset.get_related().select_related('account__user', 'service')
+ if not related:
+ return self.confirmation(request)
self.options['related_queryset'] = related
form = BillSelectRelatedForm(initial=self.options)
if int(request.POST.get('step')) >= 2:
diff --git a/orchestra/apps/orders/forms.py b/orchestra/apps/orders/forms.py
index 69fe43bf..a26465cb 100644
--- a/orchestra/apps/orders/forms.py
+++ b/orchestra/apps/orders/forms.py
@@ -39,7 +39,12 @@ def selected_related_choices(queryset):
class BillSelectRelatedForm(AdminFormMixin, forms.Form):
- selected_related = forms.ModelMultipleChoiceField(label=_("Related"),
+ # This doesn't work well with reordering after billing
+# pricing_with_all = forms.BooleanField(label=_("Do pricing with all orders"),
+# initial=False, required=False, help_text=_("The price may vary "
+# "depending on the billed orders. This options designates whether "
+# "all existing orders will be used for price computation or not."))
+ selected_related = forms.ModelMultipleChoiceField(label=_("Related orders"),
queryset=Order.objects.none(), widget=forms.CheckboxSelectMultiple,
required=False)
billing_point = forms.DateField(widget=forms.HiddenInput())
diff --git a/orchestra/apps/orders/handlers.py b/orchestra/apps/orders/handlers.py
index 31c57f14..3c8506bb 100644
--- a/orchestra/apps/orders/handlers.py
+++ b/orchestra/apps/orders/handlers.py
@@ -10,8 +10,7 @@ from django.utils.translation import ugettext_lazy as _
from orchestra.utils import plugins
from orchestra.utils.python import AttributeDict
-from . import settings
-from .helpers import get_register_or_cancel_events, get_register_or_renew_events
+from . import settings, helpers
class ServiceHandler(plugins.Plugin):
@@ -138,9 +137,9 @@ class ServiceHandler(plugins.Plugin):
).filter(registered_on__lt=end).order_by('registered_on')
price = 0
if self.orders_effect == self.REGISTER_OR_RENEW:
- events = get_register_or_renew_events(porders, order, ini, end)
+ events = helpers.get_register_or_renew_events(porders, order, ini, end)
elif self.orders_effect == self.CONCURRENT:
- events = get_register_or_cancel_events(porders, order, ini, end)
+ events = helpers.get_register_or_cancel_events(porders, order, ini, end)
else:
raise NotImplementedError
for metric, position, ratio in events:
@@ -171,6 +170,68 @@ class ServiceHandler(plugins.Plugin):
'discounts': discounts,
})
+ def _generate_bill_lines(self, orders, **options):
+ # For the "boundary conditions" just think that:
+ # date(2011, 1, 1) is equivalent to datetime(2011, 1, 1, 0, 0, 0)
+ # In most cases:
+ # ini >= registered_date, end < registered_date
+
+ # TODO Perform compensations on cancelled services
+ if self.on_cancel in (self.COMPENSATE, self.REFOUND):
+ pass
+ # TODO compensations with commit=False, fuck commit or just fuck the transaction?
+ # compensate(orders, **options)
+ # TODO create discount per compensation
+ bp = None
+ lines = []
+ commit = options.get('commit', True)
+ ini = datetime.date.max
+ end = datetime.date.ini
+ # boundary lookup
+ for order in orders:
+ cini = order.registered_on
+ if order.billed_until:
+ cini = order.billed_until
+ bp = self.get_billing_point(order, bp=bp, **options)
+ order.new_billed_until = bp
+ ini = min(ini, cini)
+ end = max(end, bp) # TODO if all bp are the same ...
+
+ porders = orders.pricing_orders(ini=ini, end=end)
+ porders.sort(cmp=helpers.cmp_billed_until_or_registered_on)
+ # Compensation
+ compensations = []
+ receivers = []
+ for order in porders:
+ if order.billed_until and order.cancelled_on and order.cancelled_on < order.billed_until:
+ compensations.append[Interval(order.cancelled_on, order.billed_until, order)]
+ orders.sort(cmp=helpers.cmp_billed_until_or_registered_on)
+ for order in orders:
+ order_interval = Interval(order.billed_until or order.registered_on, order.new_billed_until)
+ helpers.compensate(order_interval, compensations)
+
+ def get_chunks(self, porders, ini, end, ix=0):
+ if ix >= len(porders):
+ return [[ini, end, []]]
+ order = porders[ix]
+ ix += 1
+ bu = getattr(order, 'new_billed_until', order.billed_until)
+ if not bu or bu <= ini or order.registered_on >= end:
+ return self.get_chunks(porders, ini, end, ix=ix)
+ result = []
+ if order.registered_on < end and order.registered_on > ini:
+ ro = order.registered_on
+ result = self.get_chunks(porders, ini, ro, ix=ix)
+ ini = ro
+ if bu < end:
+ result += self.get_chunks(porders, bu, end, ix=ix)
+ end = bu
+ chunks = self.get_chunks(porders, ini, end, ix=ix)
+ for chunk in chunks:
+ chunk[2].insert(0, order)
+ result.append(chunk)
+ return result
+
def generate_bill_lines(self, orders, **options):
# For the "boundary conditions" just think that:
# date(2011, 1, 1) is equivalent to datetime(2011, 1, 1, 0, 0, 0)
diff --git a/orchestra/apps/orders/helpers.py b/orchestra/apps/orders/helpers.py
index e2617b5e..bb4b1b2e 100644
--- a/orchestra/apps/orders/helpers.py
+++ b/orchestra/apps/orders/helpers.py
@@ -88,3 +88,89 @@ def get_register_or_renew_events(handler, porders, order, ini, end):
elif porder.billed_until > send or porder.cancelled_on > send:
counter += 1
yield counter, position, (send-sini)/total
+
+
+def cmp_billed_until_or_registered_on(a, b):
+ """
+ 1) billed_until greater first
+ 2) registered_on smaller first
+ """
+ if a.billed_until == b.billed_until:
+ return (a.registered_on-b.registered_on).days
+ elif a.billed_until and b.billed_until:
+ return (b.billed_until-a.billed_until).days
+ elif a.billed_until:
+ return (b.registered_on-a.billed_until).days
+ return (b.billed_until-a.registered_on).days
+
+
+class Interval(object):
+ def __init__(self, ini, end, order=None):
+ self.ini = ini
+ self.end = end
+ self.order = order
+
+ def __len__(self):
+ return max((self.end-self.ini).days, 0)
+
+ def __sub__(self, other):
+ remaining = []
+ if self.ini < other.ini:
+ remaining.append(Interval(self.ini, min(self.end, other.ini)))
+ if self.end > other.end:
+ remaining.append(Interval(max(self.ini,other.end), self.end))
+ return remaining
+
+ def __repr__(self):
+ return "Start: %s End: %s" % (self.ini, self.end)
+
+ def intersect(self, other, remaining_self=None, remaining_other=None):
+ if remaining_self is not None:
+ remaining_self += (self - other)
+ if remaining_other is not None:
+ remaining_other += (other - self)
+ result = Interval(max(self.ini, other.ini), min(self.end, other.end))
+ if len(result)>0:
+ return result
+ else:
+ return None
+
+
+def get_intersections(order, compensations):
+ intersections = []
+ for compensation in compensations:
+ intersection = compensation.intersect(order)
+ if intersection:
+ intersections.append((len(intersection), intersection))
+ return intersections
+
+# Intervals should not overlap
+def intersect(compensation, order_intervals):
+ compensated = []
+ not_compensated = []
+ unused_compensation = []
+ for interval in order_intervals:
+ compensated.append(compensation.intersect(interval, unused_compensation, not_compensated))
+ return (compensated, not_compensated, unused_compensation)
+
+
+def update_intersections(not_compensated, compensations):
+ intersections = []
+ for (_,compensation) in compensations:
+ intersections += get_intersections(compensation, not_compensated)
+ return intersections
+
+
+def compensate(order, compensations):
+ intersections = get_intersections(order, compensations)
+ not_compensated = [order]
+ result = []
+ while intersections:
+ # Apply the biggest intersection
+ intersections.sort(reverse=True)
+ (_,intersection) = intersections.pop()
+ (compensated, not_compensated, unused_compensation) = intersect(intersection, not_compensated)
+ # Reorder de intersections:
+ intersections = update_intersections(not_compensated, intersections)
+ result += compensated
+ return result
diff --git a/orchestra/apps/orders/models.py b/orchestra/apps/orders/models.py
index 1525bb1c..c0850195 100644
--- a/orchestra/apps/orders/models.py
+++ b/orchestra/apps/orders/models.py
@@ -152,6 +152,9 @@ class Service(models.Model):
(MATCH_PRICE, _("Match price")),
),
default=BEST_PRICE)
+ # TODO remove since it can be infered from pricing period?
+ # VARIABLE -> REGISTER_OR_RENEW
+ # FIXED -> CONCURRENT
orders_effect = models.CharField(_("orders effect"), max_length=16,
help_text=_("Defines the lookup behaviour when using orders for "
"the pricing rate computation of this service."),
@@ -320,8 +323,39 @@ class OrderQuerySet(models.QuerySet):
else:
bills += [(account, bill_lines)]
return bills
+
+ def pricing_effect(self, ini=None, end=None, **options):
+ # TODO register but not billed duscard
+ if not ini:
+ for cini, ro in self.values_list('billed_until', 'registered_on'):
+ if not cini:
+ cini = ro
+ if not ini:
+ ini = cini
+
+ ini = min(ini, cini)
+ if not end:
+ order = self.first()
+ if order:
+ service = order.service
+ service.billing_point == service.FIXED_DATE
+ end = service.handler.get_billing_point(order, **options)
+ else:
+ pass
+ return self.exclude(
+ cancelled_on__isnull=False, billed_until__isnull=False,
+ cancelled_on__lte=F('billed_until'), billed_until__lte=ini,
+ registered_on__gte=end)
- def get_related(self):
+ def get_related(self, ini=None, end=None):
+ if not ini:
+ ini = ''
+ if not end:
+ end = ''
+ return self.pricing_effect().filter(
+ Q(billed_until__isnull=False, billed_until__lt=end) |
+ Q(billed_until__isnull=True, registered_on__lt=end))
+ # TODO iterate over every order, calculate its billing point and find related
qs = self.exclude(cancelled_on__isnull=False,
billed_until__gte=F('cancelled_on')).distinct()
original_ids = self.values_list('id', flat=True)
@@ -439,7 +473,8 @@ class MetricStorage(models.Model):
except cls.DoesNotExist:
return 0
-
+# TODO If this happens to be very costly then, consider an additional
+# implementation when runnning within a request/Response cycle, more efficient :)
@receiver(pre_delete, dispatch_uid="orders.cancel_orders")
def cancel_orders(sender, **kwargs):
if sender in services:
diff --git a/orchestra/apps/orders/tests/functional_tests/tests.py b/orchestra/apps/orders/tests/functional_tests/tests.py
index f47349c2..b3924949 100644
--- a/orchestra/apps/orders/tests/functional_tests/tests.py
+++ b/orchestra/apps/orders/tests/functional_tests/tests.py
@@ -6,10 +6,11 @@ from django.utils import timezone
from orchestra.apps.accounts.models import Account
from orchestra.apps.users.models import User
-from orchestra.utils.tests import BaseTestCase
+from orchestra.utils.tests import BaseTestCase, random_ascii
from ... import settings
-from ...models import Service
+from ...helpers import cmp_billed_until_or_registered_on
+from ...models import Service, Order
class OrderTests(BaseTestCase):
@@ -51,10 +52,7 @@ class OrderTests(BaseTestCase):
quantity=1,
price=9,
)
- account = self.create_account()
- user = User.objects.create_user(username='rata_palida_ftp', account=account)
- POSIX = user._meta.get_field_by_name('posix')[0].model
- POSIX.objects.create(user=user)
+ self.account = self.create_account()
return service
# def test_ftp_account_1_year_fiexed(self):
@@ -62,24 +60,177 @@ class OrderTests(BaseTestCase):
# bp = timezone.now().date() + relativedelta.relativedelta(years=1)
# bills = service.orders.bill(billing_point=bp, fixed_point=True)
# self.assertEqual(20, bills[0].get_total())
-
- def test_ftp_account_1_year_fiexed(self):
+
+ def create_ftp(self):
+ username = '%s_ftp' % random_ascii(10)
+ user = User.objects.create_user(username=username, account=self.account)
+ POSIX = user._meta.get_field_by_name('posix')[0].model
+ POSIX.objects.create(user=user)
+ return user
+
+ def atest_get_chunks(self):
service = self.create_service()
+ handler = service.handler
+ porders = []
now = timezone.now().date()
- month = settings.ORDERS_SERVICE_ANUAL_BILLING_MONTH
- ini = datetime.datetime(year=now.year, month=month,
- day=1, tzinfo=timezone.get_current_timezone())
- order = service.orders.all()[0]
- order.registered_on = ini
- order.save()
- bp = ini
- bills = service.orders.bill(billing_point=bp, fixed_point=False, commit=False)
- print bills[0][1][0].subtotal
- print bills
- bp = ini + relativedelta.relativedelta(months=12)
- bills = service.orders.bill(billing_point=bp, fixed_point=False, commit=False)
- print bills[0][1][0].subtotal
- print bills
+ ct = ContentType.objects.get_for_model(User)
+
+ ftp = self.create_ftp()
+ order = Order.objects.get(content_type=ct, object_id=ftp.pk)
+ porders.append(order)
+ end = handler.get_billing_point(order).date()
+ chunks = handler.get_chunks(porders, now, end)
+ self.assertEqual(1, len(chunks))
+ self.assertIn([now, end, []], chunks)
+
+ ftp = self.create_ftp()
+ order1 = Order.objects.get(content_type=ct, object_id=ftp.pk)
+ order1.billed_until = now+datetime.timedelta(days=2)
+ porders.append(order1)
+ chunks = handler.get_chunks(porders, now, end)
+ self.assertEqual(2, len(chunks))
+ self.assertIn([order1.registered_on, order1.billed_until, [order1]], chunks)
+ self.assertIn([order1.billed_until, end, []], chunks)
+
+ ftp = self.create_ftp()
+ order2 = Order.objects.get(content_type=ct, object_id=ftp.pk)
+ order2.billed_until = now+datetime.timedelta(days=700)
+ porders.append(order2)
+ chunks = handler.get_chunks(porders, now, end)
+ self.assertEqual(2, len(chunks))
+ self.assertIn([order.registered_on, order1.billed_until, [order1, order2]], chunks)
+ self.assertIn([order1.billed_until, end, [order2]], chunks)
+
+ ftp = self.create_ftp()
+ order3 = Order.objects.get(content_type=ct, object_id=ftp.pk)
+ order3.billed_until = now+datetime.timedelta(days=700)
+ porders.append(order3)
+ chunks = handler.get_chunks(porders, now, end)
+ self.assertEqual(2, len(chunks))
+ self.assertIn([order.registered_on, order1.billed_until, [order1, order2, order3]], chunks)
+ self.assertIn([order1.billed_until, end, [order2, order3]], chunks)
+
+ ftp = self.create_ftp()
+ order4 = Order.objects.get(content_type=ct, object_id=ftp.pk)
+ order4.registered_on = now+datetime.timedelta(days=5)
+ order4.billed_until = now+datetime.timedelta(days=10)
+ porders.append(order4)
+ chunks = handler.get_chunks(porders, now, end)
+ self.assertEqual(4, len(chunks))
+ self.assertIn([order.registered_on, order1.billed_until, [order1, order2, order3]], chunks)
+ self.assertIn([order1.billed_until, order4.registered_on, [order2, order3]], chunks)
+ self.assertIn([order4.registered_on, order4.billed_until, [order2, order3, order4]], chunks)
+ self.assertIn([order4.billed_until, end, [order2, order3]], chunks)
+
+ ftp = self.create_ftp()
+ order5 = Order.objects.get(content_type=ct, object_id=ftp.pk)
+ order5.registered_on = now+datetime.timedelta(days=700)
+ order5.billed_until = now+datetime.timedelta(days=780)
+ porders.append(order5)
+ chunks = handler.get_chunks(porders, now, end)
+ self.assertEqual(4, len(chunks))
+ self.assertIn([order.registered_on, order1.billed_until, [order1, order2, order3]], chunks)
+ self.assertIn([order1.billed_until, order4.registered_on, [order2, order3]], chunks)
+ self.assertIn([order4.registered_on, order4.billed_until, [order2, order3, order4]], chunks)
+ self.assertIn([order4.billed_until, end, [order2, order3]], chunks)
+
+ ftp = self.create_ftp()
+ order6 = Order.objects.get(content_type=ct, object_id=ftp.pk)
+ order6.registered_on = now-datetime.timedelta(days=780)
+ order6.billed_until = now-datetime.timedelta(days=700)
+ porders.append(order6)
+ chunks = handler.get_chunks(porders, now, end)
+ self.assertEqual(4, len(chunks))
+ self.assertIn([order.registered_on, order1.billed_until, [order1, order2, order3]], chunks)
+ self.assertIn([order1.billed_until, order4.registered_on, [order2, order3]], chunks)
+ self.assertIn([order4.registered_on, order4.billed_until, [order2, order3, order4]], chunks)
+ self.assertIn([order4.billed_until, end, [order2, order3]], chunks)
+
+ def atest_sort_billed_until_or_registered_on(self):
+ service = self.create_service()
+ now = timezone.now()
+ order = Order(
+ service=service,
+ registered_on=now,
+ billed_until=now+datetime.timedelta(days=200))
+ order1 = Order(
+ service=service,
+ registered_on=now+datetime.timedelta(days=5),
+ billed_until=now+datetime.timedelta(days=200))
+ order2 = Order(
+ service=service,
+ registered_on=now+datetime.timedelta(days=6),
+ billed_until=now+datetime.timedelta(days=200))
+ order3 = Order(
+ service=service,
+ registered_on=now+datetime.timedelta(days=6),
+ billed_until=now+datetime.timedelta(days=201))
+ order4 = Order(
+ service=service,
+ registered_on=now+datetime.timedelta(days=6))
+ order5 = Order(
+ service=service,
+ registered_on=now+datetime.timedelta(days=7))
+ order6 = Order(
+ service=service,
+ registered_on=now+datetime.timedelta(days=8))
+ orders = [order3, order, order1, order2, order4, order5, order6]
+ self.assertEqual(orders, sorted(orders, cmp=cmp_billed_until_or_registered_on))
+
+ def test_compensation(self):
+ now = timezone.now()
+ order = Order(
+ registered_on=now,
+ billed_until=now+datetime.timedelta(days=200),
+ cancelled_on=now+datetime.timedelta(days=100))
+ order1 = Order(
+ registered_on=now+datetime.timedelta(days=5),
+ cancelled_on=now+datetime.timedelta(days=190),
+ billed_until=now+datetime.timedelta(days=200))
+ order2 = Order(
+ registered_on=now+datetime.timedelta(days=6),
+ cancelled_on=now+datetime.timedelta(days=200),
+ billed_until=now+datetime.timedelta(days=200))
+ order3 = Order(
+ registered_on=now+datetime.timedelta(days=6),
+ billed_until=now+datetime.timedelta(days=200))
+ order4 = Order(
+ registered_on=now+datetime.timedelta(days=6))
+ order5 = Order(
+ registered_on=now+datetime.timedelta(days=7))
+ order6 = Order(
+ registered_on=now+datetime.timedelta(days=8))
+ porders = [order3, order, order1, order2, order4, order5, order6]
+ porders = sorted(porders, cmp=cmp_billed_until_or_registered_on)
+ service = self.create_service()
+ compensations = []
+ from ... import helpers
+ for order in porders:
+ if order.billed_until and order.cancelled_on and order.cancelled_on < order.billed_until:
+ compensations.append(helpers.Interval(order.cancelled_on, order.billed_until, order=order))
+ for order in porders:
+ bp = service.handler.get_billing_point(order)
+ order_interval = helpers.Interval(order.billed_until or order.registered_on, bp)
+ print helpers.compensate(order_interval, compensations)
+
+
+# def test_ftp_account_1_year_fiexed(self):
+# service = self.create_service()
+# now = timezone.now().date()etb
+# month = settings.ORDERS_SERVICE_ANUAL_BILLING_MONTH
+# ini = datetime.datetime(year=now.year, month=month,
+# day=1, tzinfo=timezone.get_current_timezone())
+# order = service.orders.all()[0]
+# order.registered_on = ini
+# order.save()
+# bp = ini
+# bills = service.orders.bill(billing_point=bp, fixed_point=False, commit=False)
+# print bills[0][1][0].subtotal
+# print bills
+# bp = ini + relativedelta.relativedelta(months=12)
+# bills = service.orders.bill(billing_point=bp, fixed_point=False, commit=False)
+# print bills[0][1][0].subtotal
+# print bills
# def test_ftp_account_2_year_fiexed(self):
# service = self.create_service()
# bp = timezone.now().date() + relativedelta.relativedelta(years=2)
diff --git a/orchestra/apps/payments/methods/sepadirectdebit.py b/orchestra/apps/payments/methods/sepadirectdebit.py
index e243118b..88cf1aff 100644
--- a/orchestra/apps/payments/methods/sepadirectdebit.py
+++ b/orchestra/apps/payments/methods/sepadirectdebit.py
@@ -65,6 +65,7 @@ class SEPADirectDebit(PaymentMethod):
from ..models import TransactionProcess
process = TransactionProcess.objects.create()
context = cls.get_context(transactions)
+ # http://businessbanking.bankofireland.com/fs/doc/wysiwyg/b22440-mss130725-pain001-xml-file-structure-dec13.pdf
sepa = lxml.builder.ElementMaker(
nsmap = {
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
@@ -75,12 +76,12 @@ class SEPADirectDebit(PaymentMethod):
E.CstmrCdtTrfInitn(
cls.get_header(context),
E.PmtInf( # Payment Info
- E.PmtInfId(str(process.id)), # Payment Id
+ E.PmtInfId(str(process.id)), # Payment Id
E.PmtMtd("TRF"), # Payment Method
E.NbOfTxs(context['num_transactions']), # Number of Transactions
E.CtrlSum(context['total']), # Control Sum
- E.ReqdExctnDt ( # Requested Execution Date
- context['now'].strftime("%Y-%m-%d")
+ E.ReqdExctnDt( # Requested Execution Date
+ (context['now']+datetime.timedelta(days=10)).strftime("%Y-%m-%d")
),
E.Dbtr( # Debtor
E.Nm(context['name'])
@@ -108,6 +109,7 @@ class SEPADirectDebit(PaymentMethod):
from ..models import TransactionProcess
process = TransactionProcess.objects.create()
context = cls.get_context(transactions)
+ # http://businessbanking.bankofireland.com/fs/doc/wysiwyg/sepa-direct-debit-pain-008-001-02-xml-file-structure-july-2013.pdf
sepa = lxml.builder.ElementMaker(
nsmap = {
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
@@ -118,7 +120,7 @@ class SEPADirectDebit(PaymentMethod):
E.CstmrDrctDbtInitn(
cls.get_header(context, process),
E.PmtInf( # Payment Info
- E.PmtInfId(str(process.id)), # Payment Id
+ E.PmtInfId(str(process.id)), # Payment Id
E.PmtMtd("DD"), # Payment Method
E.NbOfTxs(context['num_transactions']), # Number of Transactions
E.CtrlSum(context['total']), # Control Sum