Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix rebalance #351

Merged
merged 3 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading