Skip to content

Commit

Permalink
Merge pull request #351 from fasrc/ajk_fix_rebalance
Browse files Browse the repository at this point in the history
Fix rebalance
  • Loading branch information
aaronk authored Dec 11, 2024
2 parents 22df8a9 + 15136fb commit 2669faf
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.11 on 2024-12-09 17:36

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('department', '0004_historicaldepartment_historicaldepartmentmember'),
]

operations = [
migrations.AddField(
model_name='historicaldepartmentmember',
name='detail',
field=models.CharField(blank=True, help_text='Additional details about the affiliation (e.g. Graduate Student)', max_length=255, null=True),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 4.2.11 on 2024-12-09 17:36

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('project', '0006_auto_20230515_1832'),
]

operations = [
migrations.AlterField(
model_name='historicalproject',
name='description',
field=models.TextField(default='We do not have information about your research. Please provide a detailed description of your work.', validators=[django.core.validators.MinLengthValidator(10, 'The project description must be > 10 characters.')]),
),
migrations.AlterField(
model_name='historicalproject',
name='title',
field=models.CharField(db_index=True, max_length=255),
),
migrations.AlterField(
model_name='project',
name='description',
field=models.TextField(default='We do not have information about your research. Please provide a detailed description of your work.', validators=[django.core.validators.MinLengthValidator(10, 'The project description must be > 10 characters.')]),
),
migrations.AlterField(
model_name='project',
name='title',
field=models.CharField(max_length=255, unique=True),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.11 on 2024-12-09 17:36

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('resource', '0003_auto_20231211_1732'),
]

operations = [
migrations.AlterField(
model_name='resource',
name='linked_resources',
field=models.ManyToManyField(blank=True, to='resource.resource'),
),
]
9 changes: 7 additions & 2 deletions coldfront/plugins/ifx/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
'''
from django.contrib import admin
from ifxuser.admin import UserAdmin
from coldfront.plugins.ifx.models import SuUser, ProjectOrganization
from coldfront.plugins.ifx.models import SuUser, ProjectOrganization, PreferredUsername

@admin.register(SuUser)
class SuUserAdmin(UserAdmin):
Expand All @@ -17,4 +17,9 @@ class SuUserAdmin(UserAdmin):
class ProjectOrganizationAdmin(admin.ModelAdmin):
list_display = ('project', 'organization')
search_fields = ('project__title', 'organization__name')
autocomplete_fields = ('project', 'organization')
autocomplete_fields = ('project', 'organization')

@admin.register(PreferredUsername)
class PreferredUsernameAdmin(admin.ModelAdmin):
list_display = ('ifxid', 'preferred_username')
search_fields = ('ifxid', 'preferred_username')
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 4.2.11 on 2024-12-09 17:36

from django.conf import settings
import django.contrib.auth.models
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('ifxuser', '0006_useraffiliation_detail'),
('ifx', '0005_auto_20211003_1153'),
]

operations = [
migrations.CreateModel(
name='ColdfrontIfxUser',
fields=[
('ifxuser_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'ifx_coldfrontifxuser',
},
bases=('ifxuser.ifxuser',),
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='PreferredUsername',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ifxid', models.CharField(max_length=255)),
('preferred_username', models.CharField(max_length=255)),
],
),
]
10 changes: 10 additions & 0 deletions coldfront/plugins/ifx/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,13 @@ def get_resource_allocation_authorization_map():
result.append(row)

return result

class PreferredUsername(models.Model):
'''
Preferred username for a user
'''
ifxid = models.CharField(max_length=255)
preferred_username = models.CharField(max_length=255)

def __str__(self):
return f'{self.ifxid} - {self.preferred_username}'
4 changes: 2 additions & 2 deletions coldfront/plugins/ifx/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ifxbilling import views as ifxbilling_views
from ifxuser.views import get_org_names
from coldfront.plugins.ifx.viewsets import ColdfrontBillingRecordViewSet, ColdfrontReportRunViewSet, ColdfrontProductUsageViewSet
from coldfront.plugins.ifx.views import update_user_accounts_view, get_billing_record_list, unauthorized, report_runs, run_report, calculate_billing_month, billing_month, get_product_usages, billing_records, send_billing_record_review_notification, lab_billing_summary
from coldfront.plugins.ifx.views import rebalance, update_user_accounts_view, get_billing_record_list, unauthorized, report_runs, run_report, calculate_billing_month, billing_month, get_product_usages, billing_records, send_billing_record_review_notification, lab_billing_summary

router = routers.DefaultRouter()
router.register(r'billing-records', ColdfrontBillingRecordViewSet, 'billing-record')
Expand All @@ -20,7 +20,7 @@
path('api/billing/get-summary-by-product-rate/', ifxbilling_views.get_summary_by_product_rate),
path('api/billing/get-summary-by-account/', ifxbilling_views.get_summary_by_account),
path('api/billing/get-pending-year-month/<str:invoice_prefix>/', ifxbilling_views.get_pending_year_month),
path('api/billing/rebalance/', ifxbilling_views.rebalance),
path('api/billing/rebalance/', rebalance),
path('api/billing/calculate-billing-month/<str:invoice_prefix>/<int:year>/<int:month>/', calculate_billing_month, name='calculate-billing-month'),
path('api/run-report/', run_report),
path('api/get-org-names/', get_org_names, name='get-org-names'),
Expand Down
83 changes: 83 additions & 0 deletions coldfront/plugins/ifx/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,34 @@
from ifxbilling.calculator import getClassFromName
from ifxbilling.views import get_billing_record_list as ifxbilling_get_billing_record_list
from ifxbilling.fiine import update_user_accounts
from ifxbilling.calculator import get_rebalancer_class
from ifxmail.client import send
from ifxuser import models as ifxuser_models
from coldfront.plugins.ifx.calculator import NewColdfrontBillingCalculator
from coldfront.plugins.ifx.permissions import AdminPermissions
from coldfront.plugins.ifx.models import PreferredUsername

logger = logging.getLogger(__name__)

def get_preferred_user(ifxid):
'''
Get the IfxUser with the preferred username
'''
users = ifxuser_models.IfxUser.objects.filter(ifxid=ifxid)
if not users:
raise Exception(f'User cannot be found using ifxid {ifxid}')
try:
pu = PreferredUsername.objects.get(ifxid=ifxid)
users = users.filter(username=pu.preferred_username)
except PreferredUsername.DoesNotExist:
pass
except PreferredUsername.MultipleObjectsReturned:
raise Exception(f'Multiple preferred usernames found for ifxid {ifxid}')
if len(users) > 1:
raise Exception(f'Multiple users found for ifxid {ifxid} and no preferred username is set')
return users[0]


@login_required
def unauthorized(request):
'''
Expand Down Expand Up @@ -351,3 +372,65 @@ def lab_billing_summary(request):
raise PermissionDenied
token = request.user.auth_token.key
return render(request, 'plugins/ifx/lab_billing_summary.html', { 'auth_token': token })


@api_view(('POST', ))
def rebalance(request):
'''
Rebalance the billing records for the given facility, user, year, and month.
*** This is a copy of the ifxbilling view modified to handle preferred usernames ***
'''
try:
data = json.loads(request.body.decode('utf-8'))
except json.JSONDecodeError as e:
logger.exception(e)
return Response(data={'error': 'Cannot parse request body'}, status=status.HTTP_400_BAD_REQUEST)

invoice_prefix = data.get('invoice_prefix', None)
ifxid = data.get('ifxid', None)
year = data.get('year', None)
month = data.get('month', None)
account_data = data.get('account_data', None)
requestor_ifxid = data.get('requestor_ifxid', None)

if not invoice_prefix:
return Response(data={ 'error': 'invoice_prefix is required' }, status=status.HTTP_400_BAD_REQUEST)
if not ifxid:
return Response(data={ 'error': 'ifxid is required' }, status=status.HTTP_400_BAD_REQUEST)
if not year:
return Response(data={ 'error': 'year is required' }, status=status.HTTP_400_BAD_REQUEST)
if not month:
return Response(data={ 'error': 'month is required' }, status=status.HTTP_400_BAD_REQUEST)
if not requestor_ifxid:
return Response(data={ 'error': 'requestor_ifxid is required' }, status=status.HTTP_400_BAD_REQUEST)


try:
facility = ifxbilling_models.Facility.objects.get(invoice_prefix=invoice_prefix)
except ifxbilling_models.Facility.DoesNotExist:
return Response(data={ 'error': f'Facility cannot be found using invoice_prefix {invoice_prefix}' }, status=status.HTTP_400_BAD_REQUEST)

try:
user = get_preferred_user(ifxid)
except Exception as e:
return Response(data={ 'error': f'Error getting user with ifxid {ifxid}: {e}' }, status=status.HTTP_400_BAD_REQUEST)

try:
requestor = get_preferred_user(requestor_ifxid)
except Exception as e:
return Response(data={ 'error': f'Error getting requestor with ifxid {requestor_ifxid}: {e}' }, status=status.HTTP_400_BAD_REQUEST)


auth_token_str = request.META.get('HTTP_AUTHORIZATION')
rebalancer = get_rebalancer_class()(year, month, facility, auth_token_str, requestor)
try:
rebalancer.rebalance_user_billing_month(user, account_data)
result = f'Rebalance of accounts for {user.full_name} for billing month {month}/{year} was successful.'
rebalancer.send_result_notification(result)
return Response(data={ 'success': result })
except Exception as e:
logger.exception(e)
result = f'Rebalance of accounts for {user.full_name} for billing month {month}/{year} failed: {e}'
rebalancer.send_result_notification(result)
return Response(data={ 'error': f'Rebalance failed {e}' }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
2 changes: 1 addition & 1 deletion ifxreport

0 comments on commit 2669faf

Please sign in to comment.