Skip to content

Commit

Permalink
integrate celery into repack request processing
Browse files Browse the repository at this point in the history
  • Loading branch information
erikvw committed Nov 16, 2024
1 parent ed61f4e commit 200b146
Show file tree
Hide file tree
Showing 21 changed files with 537 additions and 120 deletions.
76 changes: 60 additions & 16 deletions edc_pharmacy/admin/actions/process_repack_request.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,74 @@
from __future__ import annotations

from celery import current_app
from django.contrib import admin, messages
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils.translation import gettext
from django.utils.html import format_html
from edc_utils.celery import run_task_sync_or_async

from ...utils import process_repack_request
from ...tasks.process_repack_request import process_repack_request_queryset


@admin.action(description="Process repack request")
def process_repack_request_action(modeladmin, request, queryset):
if queryset.count() > 1 or queryset.count() == 0:
"""Action to process repack request.
Redirects to process_repack_request.
If celery is running, will run through the entire queryset otherwise
just the first instance in the queryset.
"""
repack_request_pks = [obj.pk for obj in queryset]

# if celery is not running, just keep the first pk
i = current_app.control.inspect()
if not i.active():
repack_request_pks = repack_request_pks[:1]

# run task / func and update or clear the task_id
task = run_task_sync_or_async(
process_repack_request_queryset, repack_request_pks=repack_request_pks
)
task_id = getattr(task, "id", None)
queryset.update(task_id=task_id)

# add messages for user
messages.add_message(
request,
messages.SUCCESS,
format_html(
"Repack request submitted. <BR>Next, go to the ACTION menu below and "
"(1)`Print labels`. Then (2) Label your stock containers with the printed labels. "
"Once all stock is labelled, go to the ACTION menu below and "
"(3) Select `Confirm repacked and labelled stock`. "
f"Scan in the labels to CONFIRM the stock. ({task_id})"
),
)
if task_id:
messages.add_message(
request,
messages.ERROR,
gettext("Select one and only one item"),
messages.INFO,
f"Task {task_id} is processing your repack requests.",
)
else:
repack_obj = queryset.first()
if repack_obj.processed:
messages.add_message(
request, messages.ERROR, "Nothing to do. Repack request already processed"
)
else:
process_repack_request(repack_obj)
url = reverse("edc_pharmacy_admin:edc_pharmacy_repackrequest_changelist")
url = f"{url}?q={repack_obj.from_stock.code}"
return HttpResponseRedirect(url)
return None
repack_request = queryset.first()
messages.add_message(
request,
messages.INFO,
(
f"Processed only 1 of {queryset.count()} repack requests selected. "
f"See {repack_request}."
),
)
messages.add_message(
request, messages.ERROR, "Task workers not running. Contact data management."
)

# redirect to changelist
url = reverse("edc_pharmacy_admin:edc_pharmacy_repackrequest_changelist")
if queryset.count() == 1:
repack_request = queryset.first()
url = f"{url}?q={repack_request.from_stock.code}"
return HttpResponseRedirect(url)
43 changes: 40 additions & 3 deletions edc_pharmacy/admin/list_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,50 @@ def queryset(self, request, queryset):
from_stock = True
if self.value() == YES:
opts = dict(from_stock__isnull=False) if from_stock else {}
qs = queryset.filter(allocation__isnull=False, **opts)
qs = queryset.filter(
allocation__isnull=False,
container__may_request_as=True,
**opts,
)
elif self.value() == NO:
opts = dict(from_stock__isnull=False) if from_stock else {}
qs = queryset.filter(allocation__isnull=True, **opts)
qs = queryset.filter(
allocation__isnull=True,
container__may_request_as=True,
**opts,
)
elif self.value() == NOT_APPLICABLE:
opts = dict(from_stock__isnull=True) if from_stock else {}
qs = queryset.filter(allocation__isnull=True, **opts)
qs = queryset.filter(
allocation__isnull=True,
container__may_request_as=False,
**opts,
)
return qs


class TransferredListFilter(SimpleListFilter):
title = "Transferred"
parameter_name = "transferred"

def lookups(self, request, model_admin):
return YES_NO_NA

def queryset(self, request, queryset):
qs = None
if self.value():
if self.value() == YES:
qs = queryset.filter(
container__may_request_as=True,
allocation__stock_request_item__stock_request__location=F("location"),
)
elif self.value() == NO:
qs = queryset.filter(
~Q(allocation__stock_request_item__stock_request__location=F("location")),
container__may_request_as=True,
)
elif self.value() == NOT_APPLICABLE:
qs = queryset.filter(allocation__isnull=True, container__may_request_as=False)
return qs


Expand Down
55 changes: 41 additions & 14 deletions edc_pharmacy/admin/stock/repack_request_admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from decimal import Decimal

from celery.result import AsyncResult
from celery.states import SUCCESS
from django.contrib import admin
from django.contrib.admin.widgets import AutocompleteSelect
from django.template.loader import render_to_string
Expand All @@ -9,7 +13,11 @@
from ...forms import RepackRequestForm
from ...models import RepackRequest
from ...utils import format_qty
from ..actions import confirm_repacked_stock_action, print_labels_from_repack_request
from ..actions import (
confirm_repacked_stock_action,
print_labels_from_repack_request,
process_repack_request_action,
)
from ..model_admin_mixin import ModelAdminMixin


Expand All @@ -23,7 +31,11 @@ class RequestRepackAdmin(ModelAdminMixin, admin.ModelAdmin):

autocomplete_fields = ["from_stock", "container"]
form = RepackRequestForm
actions = [print_labels_from_repack_request, confirm_repacked_stock_action]
actions = [
process_repack_request_action,
print_labels_from_repack_request,
confirm_repacked_stock_action,
]

change_list_note = render_to_string(
"edc_pharmacy/stock/instructions/repack_instructions.html"
Expand All @@ -38,8 +50,8 @@ class RequestRepackAdmin(ModelAdminMixin, admin.ModelAdmin):
"repack_datetime",
"from_stock",
"container",
"qty",
"processed",
"requested_qty",
"processed_qty",
)
},
),
Expand All @@ -50,11 +62,12 @@ class RequestRepackAdmin(ModelAdminMixin, admin.ModelAdmin):
"identifier",
"repack_date",
"from_stock_changelist",
"formatted_qty",
"stock_changelist",
"formatted_requested_qty",
"formatted_processed_qty",
"container",
"from_stock__product__name",
"processed",
"stock_changelist",
"task_status",
)

search_fields = (
Expand All @@ -63,6 +76,8 @@ class RequestRepackAdmin(ModelAdminMixin, admin.ModelAdmin):
"from_stock__code",
)

readonly_fields = ("processed_qty", "task_id")

@admin.display(description="Repack date", ordering="repack_datetime")
def repack_date(self, obj):
return to_local(obj.repack_datetime).date()
Expand All @@ -74,7 +89,7 @@ def stock_changelist(self, obj):
context = dict(url=url, label="Stock", title="Go to stock")
return render_to_string("edc_pharmacy/stock/items_as_link.html", context=context)

@admin.display(description="From stock")
@admin.display(description="From stock", ordering="from_stock__code")
def from_stock_changelist(self, obj):
url = reverse("edc_pharmacy_admin:edc_pharmacy_stock_changelist")
url = f"{url}?q={obj.from_stock.code}"
Expand All @@ -85,19 +100,31 @@ def from_stock_changelist(self, obj):
def identifier(self, obj):
return obj.repack_identifier

@admin.display(description="QTY", ordering="qty")
def formatted_qty(self, obj):
return format_qty(obj.qty, obj.container)
@admin.display(description="Requested", ordering="requested_qty")
def formatted_requested_qty(self, obj):
return format_qty(obj.requested_qty, obj.container)

@admin.display(description="Processed", ordering="processed_qty")
def formatted_processed_qty(self, obj):
result = AsyncResult(str(obj.task_id)) if obj.task_id else None
if result and result.status != SUCCESS:
return None
return format_qty(obj.processed_qty, obj.container)

@admin.display(description="Task")
def task_status(self, obj):
if obj.task_id:
result = AsyncResult(str(obj.task_id))
return getattr(result, "status", None)
return None

def get_readonly_fields(self, request, obj=None):
if obj and obj.processed:
if obj and (obj.processed_qty or Decimal(0)) > Decimal(0):
f = [
"repack_identifier",
"repack_datetime",
"container",
"from_stock",
"processed",
"qty",
]
return self.readonly_fields + tuple(f)
return self.readonly_fields
Expand Down
16 changes: 9 additions & 7 deletions edc_pharmacy/admin/stock/stock_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.urls import reverse
from django.utils.html import format_html
from django_audit_fields.admin import audit_fieldset_tuple
from edc_constants.constants import YES

from ...admin_site import edc_pharmacy_admin
from ...exceptions import AllocationError, AssignmentError
Expand All @@ -20,6 +21,7 @@
HasReceiveNumFilter,
HasRepackNumFilter,
ProductAssignmentListFilter,
TransferredListFilter,
)
from ..model_admin_mixin import ModelAdminMixin
from ..remove_fields_for_blinded_users import remove_fields_for_blinded_users
Expand Down Expand Up @@ -91,7 +93,7 @@ class StockAdmin(ModelAdminMixin, admin.ModelAdmin):
"from_stock_changelist",
"formatted_confirmed",
"allocated",
"formatted_at_location",
"transferred",
"formulation",
"verified_assignment",
"qty",
Expand All @@ -109,7 +111,7 @@ class StockAdmin(ModelAdminMixin, admin.ModelAdmin):
list_filter = (
"confirmed",
AllocationListFilter,
"at_location",
TransferredListFilter,
ProductAssignmentListFilter,
"product__formulation__description",
"product__assignment__name",
Expand Down Expand Up @@ -195,7 +197,11 @@ def formatted_code(self, obj):
def qty(self, obj):
return format_qty(obj.qty_in - obj.qty_out, obj.container)

@admin.display(description="Units", ordering="qty")
@admin.display(description="T", boolean=True)
def transferred(self, obj):
return True if obj.transferred == YES else False

@admin.display(description="Units", ordering="unit_qty_out")
def unit_qty(self, obj):
return format_qty(obj.unit_qty_in - obj.unit_qty_out, obj.container)

Expand All @@ -207,10 +213,6 @@ def identifier(self, obj):
def formatted_confirmed(self, obj):
return obj.confirmed

@admin.display(description="T", ordering="at_location", boolean=True)
def formatted_at_location(self, obj):
return obj.at_location

@admin.display(description="A", ordering="allocation", boolean=True)
def allocated(self, obj):
if obj.allocation:
Expand Down
5 changes: 5 additions & 0 deletions edc_pharmacy/forms/stock/repack_request_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ def clean(self):
> cleaned_data.get("from_stock").container.qty
):
raise forms.ValidationError({"container": "Cannot pack into larger container."})
if cleaned_data.get("requested_qty") and self.instance.processed_qty:
if cleaned_data.get("requested_qty") < self.instance.processed_qty:
raise forms.ValidationError(
{"requested_qty": "Cannot be less than the number of containers processed"}
)
return cleaned_data

class Meta:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.1.2 on 2024-11-15 14:46

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("edc_pharmacy", "0026_historicalstockrequest_cutoff_datetime_and_more"),
]

operations = [
migrations.RenameField(
model_name="historicalstock",
old_name="at_location",
new_name="transferred",
),
migrations.RenameField(
model_name="stock",
old_name="at_location",
new_name="transferred",
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 5.1.2 on 2024-11-15 14:54

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("edc_pharmacy", "0027_rename_at_location_historicalstock_transferred_and_more"),
]

operations = [
migrations.RemoveField(
model_name="historicalstock",
name="transferred",
),
migrations.RemoveField(
model_name="stock",
name="transferred",
),
]
Loading

0 comments on commit 200b146

Please sign in to comment.