From be3d14b75e512263d348860481fc1e2614967522 Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Tue, 4 Oct 2022 22:28:22 +0000 Subject: [PATCH] Add django command to bill for Trotec use --- apiserver/apiserver/api/emails/usage_bill.txt | 21 ++++ .../api/management/commands/bill_trotec.py | 101 ++++++++++++++++++ apiserver/apiserver/api/utils_email.py | 31 ++++++ 3 files changed, 153 insertions(+) create mode 100644 apiserver/apiserver/api/emails/usage_bill.txt create mode 100644 apiserver/apiserver/api/management/commands/bill_trotec.py diff --git a/apiserver/apiserver/api/emails/usage_bill.txt b/apiserver/apiserver/api/emails/usage_bill.txt new file mode 100644 index 0000000..e5b259c --- /dev/null +++ b/apiserver/apiserver/api/emails/usage_bill.txt @@ -0,0 +1,21 @@ +Hi [name], + +Please find attached a summary of your [device] usage in [month]. You used +[overage] minutes over your free allotment, which at $0.50/min comes to $[bill]. +You can pay this on the portal's Paymaster page under the consumables section, +or send an e-Transfer to info@protospace.ca, or hand a director cash. + +INVOICE +Device: [device] +Month: [month] +Usage: [minutes] min +Overage: [overage] min +---------------------- +Bill: $[bill] + +If you want to reply to this email, please click "reply-all" since the Spaceport +inbox does not exist. + +If you've already paid, ignore this email. + +Spaceport diff --git a/apiserver/apiserver/api/management/commands/bill_trotec.py b/apiserver/apiserver/api/management/commands/bill_trotec.py new file mode 100644 index 0000000..e379d25 --- /dev/null +++ b/apiserver/apiserver/api/management/commands/bill_trotec.py @@ -0,0 +1,101 @@ +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth.models import User +from django.db.models import Max, F, Count, Q, Sum +from django.utils.timezone import now +from django.core.cache import cache +from django.db import transaction +from dateutil import relativedelta +import math + +from apiserver import secrets, settings +from apiserver.api import models, utils, utils_email + +import time + +class Command(BaseCommand): + help = 'Bill Trotec laser usage for last month. Wise to run this on the 2nd of each month to prevent any timezone issues.' + + FREE_MINUTES = 60 * 6 + DEVICE = 'TROTECS300' + DEVICE_NAME = 'Trotec' + DOLLARS_PER_MINUTE = 0.50 + + def bill_trotec(self): + count = 0 + + now = utils.now_alberta_tz() + current_month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + previous_month_start = current_month_start - relativedelta.relativedelta(months=1) + + self.stdout.write('Billing from {} to {}...'.format( + previous_month_start, + current_month_start, + )) + + usages = models.Usage.objects.order_by('id').filter(should_bill=True) + month_trotec_usages = usages.filter( + started_at__gte=previous_month_start, + started_at__lt=current_month_start, + device=self.DEVICE, + ) + + month_trotec_user_ids = month_trotec_usages.values_list('user', flat=True).distinct() + month_trotec_users = User.objects.filter(id__in=month_trotec_user_ids) + + self.stdout.write('Found {} usages by {} users.'.format( + month_trotec_usages.count(), + month_trotec_users.count(), + )) + + for user in month_trotec_users: + if not user: + continue + + self.stdout.write('Billing {}:'.format(user.username)) + + users_usages = month_trotec_usages.filter( + user=user, + ) + + total_seconds = users_usages.aggregate(Sum('num_seconds'))['num_seconds__sum'] or 0 + total_minutes = math.ceil(total_seconds / 60.0) + billable_minutes = total_minutes - self.FREE_MINUTES + + self.stdout.write(' Total seconds: {}'.format(total_seconds)) + self.stdout.write(' Total minutes: {}'.format(total_minutes)) + self.stdout.write(' Billable minutes: {}'.format(billable_minutes)) + + if billable_minutes <= 0: + self.stdout.write(' Skipping, used free time.') + continue + + bill = billable_minutes * self.DOLLARS_PER_MINUTE + bill_str = format(bill, '.2f') + + self.stdout.write(' Total bill: ${}'.format(bill_str)) + + utils_email.send_usage_bill_email( + user, + self.DEVICE_NAME, + previous_month_start.strftime('%B'), + total_minutes, + billable_minutes, + bill_str, + ) + + self.stdout.write(' Sent usage bill email.') + + count += 1 + + return count + + def handle(self, *args, **options): + self.stdout.write('{} - Billing Trotec'.format(str(now()))) + start = time.time() + + count = self.bill_trotec() + self.stdout.write('Billed {} members'.format(count)) + + self.stdout.write('Completed billing in {} s'.format( + str(time.time() - start)[:4] + )) diff --git a/apiserver/apiserver/api/utils_email.py b/apiserver/apiserver/api/utils_email.py index 6110099..8254f1c 100644 --- a/apiserver/apiserver/api/utils_email.py +++ b/apiserver/apiserver/api/utils_email.py @@ -97,3 +97,34 @@ def send_interest_email(interest): time.sleep(0.5) # simulate slowly sending emails when logging to console logger.info('Sent interest email:\n' + email_text) + +def send_usage_bill_email(user, device, month, minutes, overage, bill): + def replace_fields(text): + return text.replace( + '[name]', user.member.preferred_name, + ).replace( + '[device]', device, + ).replace( + '[month]', month, + ).replace( + '[minutes]', str(minutes), + ).replace( + '[overage]', str(overage), + ).replace( + '[bill]', bill, + ) + + with open(EMAIL_DIR + 'usage_bill.txt', 'r') as f: + email_text = replace_fields(f.read()) + + send_mail( + subject='{} {} Usage Bill'.format(month, device), + message=email_text, + from_email=None, # defaults to DEFAULT_FROM_EMAIL + recipient_list=[user.email, 'directors@protospace.ca', 'protospace@tannercollin.com'], + ) + + if not settings.EMAIL_HOST: + time.sleep(0.5) # simulate slowly sending emails when logging to console + + logger.info('Sent usage bill email:\n' + email_text)