From 332ad49b64c7823d097ef6ff20577c9d9e313bb1 Mon Sep 17 00:00:00 2001 From: Marc Aymerich Date: Mon, 7 Sep 2015 13:07:04 +0000 Subject: [PATCH] Fixes on billing and mailman traffic backend --- TODO.md | 18 ++---------------- orchestra/contrib/lists/backends.py | 6 +++++- orchestra/contrib/resources/admin.py | 1 + orchestra/contrib/services/handlers.py | 21 +++++++++++++++++++-- orchestra/contrib/services/models.py | 16 ++++++++-------- 5 files changed, 35 insertions(+), 27 deletions(-) diff --git a/TODO.md b/TODO.md index fb8d84eb..fa24b064 100644 --- a/TODO.md +++ b/TODO.md @@ -156,7 +156,6 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl # * add ini, end dates on bill lines and breakup quanity into size(defaut:1) and metric # * threshold for significative metric accountancy on services.handler # * http://orchestra.pangea.org/admin/orders/order/6418/ -# * http://orchestra.pangea.org/admin/orders/order/6495/bill_selected_orders/ * move normurlpath to orchestra.utils from websites.utils @@ -183,22 +182,11 @@ ugettext("Description") * saas validate_creation generic approach, for all backends. standard output -* html code x: × for bill line verbose quantity - -* periodic task to cleanup backendlogs, monitor data and metricstorage -* create orchestrate databases.Database pk=1 -n --dry-run | --noinput --action save (default)|delete --backend name (limit to this backend) --help - -* uwsgi --max-requests=5000 \ # respawn processes after serving 5000 requests and -celery max-tasks-per-child - -* generate settings.py more like django (installed_apps, middlewares, etc,,,) +* periodic task to cleanup metricstorage +# create orchestrate databases.Database pk=1 -n --dry-run | --noinput --action save (default)|delete --backend name (limit to this backend) --help * postupgradeorchestra send signals in order to hook custom stuff -* autoscale celery workers http://docs.celeryproject.org/en/latest/userguide/workers.html#autoscaling - - -glic3rinu's django-fluent-dashboard * gevent is not ported to python3 :'( # FIXME account deletion generates an integrity error @@ -248,8 +236,6 @@ https://code.djangoproject.com/ticket/24576 # Determine the difference between data serializer used for validation and used for the rest API! # Make PluginApiView that fills metadata and other stuff like modeladmin plugin support -# TODO orchestra related services code reload: celery/uwsgi reloading find aonther way without root and implement reload - # reset setting button # admin edit relevant djanog settings diff --git a/orchestra/contrib/lists/backends.py b/orchestra/contrib/lists/backends.py index 0e759dad..6ca9f3a0 100644 --- a/orchestra/contrib/lists/backends.py +++ b/orchestra/contrib/lists/backends.py @@ -271,6 +271,7 @@ class MailmanTraffic(ServiceMonitor): 'Nov': '11', 'Dec': '12', }} + mailman_addr = re.compile(r'.*-(admin|bounces|confirm|join|leave|owner|request|subscribe|unsubscribe)@.*') def prepare(object_id, list_name, ini_date): global lists @@ -283,12 +284,15 @@ class MailmanTraffic(ServiceMonitor): try: with open(postlog, 'r') as postlog: for line in postlog.readlines(): - month, day, time, year, __, __, __, list_name, __, __, size = line.split()[:11] + month, day, time, year, __, __, __, list_name, __, addr, size = line.split()[:11] try: list = lists[list_name] except KeyError: continue else: + # discard mailman messages because of inconsistent POST logging + if mailman_addr.match(addr): + continue date = year + months[month] + day + time.replace(':', '') if list[0] < int(date) < end_date: size = size[5:-1] diff --git a/orchestra/contrib/resources/admin.py b/orchestra/contrib/resources/admin.py index 56f8108f..f41bee06 100644 --- a/orchestra/contrib/resources/admin.py +++ b/orchestra/contrib/resources/admin.py @@ -202,6 +202,7 @@ class MonitorDataAdmin(ExtendedModelAdmin): change_readonly_fields = fields list_select_related = ('content_type',) search_fields = ('content_object_repr',) + date_hierarchy = 'created_at' display_created = admin_date('created_at', short_description=_("Created")) diff --git a/orchestra/contrib/services/handlers.py b/orchestra/contrib/services/handlers.py index cddb7732..7d5af6f5 100644 --- a/orchestra/contrib/services/handlers.py +++ b/orchestra/contrib/services/handlers.py @@ -189,7 +189,11 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): def get_price_size(self, ini, end): rdelta = relativedelta.relativedelta(end, ini) - if self.billing_period == self.MONTHLY: + anual_prepay_of_monthly_pricing = bool( + self.billing_period == self.ANUAL and + self.payment_style == self.PREPAY and + self.get_pricing_period() == self.MONTHLY) + if self.billing_period == self.MONTHLY or anual_prepay_of_monthly_pricing: size = rdelta.years * 12 size += rdelta.months days = calendar.monthrange(end.year, end.month)[1] @@ -508,7 +512,9 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): recharges = [] rini = order.billed_on rend = min(bp, order.billed_until) - bmetric = order.billed_metric or 0 + bmetric = order.billed_metric + if bmetric is None: + bmetric = order.get_metric(order.billed_on) bsize = self.get_price_size(rini, order.billed_until) prepay_discount = self.get_price(account, bmetric) * bsize prepay_discount = round(prepay_discount, 2) @@ -580,6 +586,17 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): line = self.generate_line(order, price, cini, cend, metric=metric, discounts=discounts) lines.append(line) + elif self.get_pricing_period() in (self.MONTHLY, self.ANUAL): + if self.payment_style == self.PREPAY: + # Traffic Prepay + metric = order.get_metric(timezone.now().date()) + if metric > 0: + price = self.get_price(account, metric) + for cini, cend in self.get_pricing_slots(ini, bp): + line = self.generate_line(order, price, cini, cend, metric=metric) + lines.append(line) + else: + raise NotImplementedError else: raise NotImplementedError else: diff --git a/orchestra/contrib/services/models.py b/orchestra/contrib/services/models.py index d54aa868..a0ef7964 100644 --- a/orchestra/contrib/services/models.py +++ b/orchestra/contrib/services/models.py @@ -1,3 +1,4 @@ +import calendar import decimal from django.contrib.contenttypes.models import ContentType @@ -70,9 +71,8 @@ class Service(models.Model): " contractedplan.plan.name == 'association_fee''
" " instance.active")) handler_type = models.CharField(_("handler"), max_length=256, blank=True, - help_text=_("Handler used for processing this Service. A handler " - "enables customized behaviour far beyond what options " - "here allow to."), + help_text=_("Handler used for processing this Service. A handler enables customized " + "behaviour far beyond what options here allow to."), choices=ServiceHandler.get_choices()) is_active = models.BooleanField(_("active"), default=True) ignore_superusers = models.BooleanField(_("ignore %s") % _ignore_types, default=True, @@ -87,16 +87,16 @@ class Service(models.Model): ), default=ANUAL, blank=True) billing_point = models.CharField(_("billing point"), max_length=16, - help_text=_("Reference point for calculating the renewal date " - "on recurring invoices"), + help_text=_("Reference point for calculating the renewal date on recurring invoices"), choices=( (ON_REGISTER, _("Registration date")), - (FIXED_DATE, _("Fixed billing date")), + (FIXED_DATE, _("Every %(month)s") % { + 'month': calendar.month_name[settings.SERVICES_SERVICE_ANUAL_BILLING_MONTH] + }), ), default=FIXED_DATE) is_fee = models.BooleanField(_("fee"), default=False, - help_text=_("Designates whether this service should be billed as " - " membership fee or not")) + help_text=_("Designates whether this service should be billed as membership fee or not")) order_description = models.CharField(_("Order description"), max_length=128, blank=True, help_text=_( "Python expression "