diff --git a/src/genlab_bestilling/libs/genlabid.py b/src/genlab_bestilling/libs/genlabid.py
index 32af0235..1616c471 100644
--- a/src/genlab_bestilling/libs/genlabid.py
+++ b/src/genlab_bestilling/libs/genlabid.py
@@ -63,7 +63,11 @@ def get_current_sequences(order_id: int | str) -> Any:
return sequences
-def generate(order_id: int | str) -> None:
+def generate(
+ order_id: int | str,
+ sorting_order: list[str] | None = None,
+ selected_samples: list[Any] | None = None,
+) -> None:
"""
wrapper to handle errors and reset the sequence to the current sequence value
"""
diff --git a/src/genlab_bestilling/models.py b/src/genlab_bestilling/models.py
index 4e5bff44..5a86aa2f 100644
--- a/src/genlab_bestilling/models.py
+++ b/src/genlab_bestilling/models.py
@@ -1,9 +1,10 @@
import uuid
from datetime import timedelta
-from typing import Any
+from typing import TYPE_CHECKING, Any
from django.conf import settings
from django.db import models, transaction
+from django.db.models import QuerySet
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
@@ -12,6 +13,9 @@
from rest_framework.exceptions import ValidationError
from taggit.managers import TaggableManager
+if TYPE_CHECKING:
+ from .models import Sample
+
from . import managers
from .libs.helpers import position_to_coordinates
@@ -301,6 +305,10 @@ def to_draft(self) -> None:
def get_type(self) -> str:
return "order"
+ @property
+ def filled_genlab_count(self) -> int:
+ return self.samples.filter(genlab_id__isnull=False).count()
+
@property
def next_status(self) -> OrderStatus | None:
current_index = self.STATUS_ORDER.index(self.status)
@@ -461,6 +469,27 @@ def order_manually_checked(self) -> None:
self.save()
app.configure_task(name="generate-genlab-ids").defer(order_id=self.id)
+ def order_selected_checked(
+ self,
+ sorting_order: list[str] | None = None,
+ selected_samples: QuerySet["Sample"] | None = None,
+ ) -> None:
+ """
+ Partially set the order as checked by the lab staff,
+ generate a genlab id for the samples selected
+ """
+ self.internal_status = self.Status.CHECKED
+ self.status = self.OrderStatus.PROCESSING
+ self.save()
+
+ selected_sample_names = list(selected_samples.values_list("id", flat=True))
+
+ app.configure_task(name="generate-genlab-ids").defer(
+ order_id=self.id,
+ sorting_order=sorting_order,
+ selected_samples=selected_sample_names,
+ )
+
class AnalysisOrder(Order):
samples = models.ManyToManyField(
diff --git a/src/genlab_bestilling/tasks.py b/src/genlab_bestilling/tasks.py
index 607f301b..d92b0a78 100644
--- a/src/genlab_bestilling/tasks.py
+++ b/src/genlab_bestilling/tasks.py
@@ -1,4 +1,6 @@
# from .libs.isolation import isolate
+from typing import Any
+
from django.db.utils import OperationalError
from procrastinate import RetryStrategy
from procrastinate.contrib.django import app
@@ -12,6 +14,14 @@
max_attempts=5, linear_wait=5, retry_exceptions={OperationalError}
),
)
-def generate_ids(order_id: str | int) -> None:
- generate_genlab_id(order_id=order_id)
+def generate_ids(
+ order_id: int | str,
+ sorting_order: list[str] | None = None,
+ selected_samples: list[Any] | None = None,
+) -> None:
+ generate_genlab_id(
+ order_id=order_id,
+ sorting_order=sorting_order,
+ selected_samples=selected_samples,
+ )
# isolate(order_id=order_id)
diff --git a/src/staff/tables.py b/src/staff/tables.py
index f5f8575d..ebb149fd 100644
--- a/src/staff/tables.py
+++ b/src/staff/tables.py
@@ -1,6 +1,8 @@
from typing import Any
import django_tables2 as tables
+from django.db.models import IntegerField
+from django.db.models.functions import Cast
from django.utils.safestring import mark_safe
from genlab_bestilling.models import (
@@ -139,6 +141,17 @@ class SampleBaseTable(tables.Table):
verbose_name="",
)
+ checked = tables.CheckBoxColumn(
+ attrs={
+ "th__input": {"type": "checkbox", "id": "select-all-checkbox"},
+ },
+ accessor="pk",
+ orderable=False,
+ empty_values=(),
+ )
+
+ name = tables.Column(order_by=("name_as_int",))
+
class Meta:
model = Sample
fields = [
@@ -154,17 +167,41 @@ class Meta:
"plate_positions",
]
attrs = {"class": "w-full table-auto tailwind-table table-sm"}
- sequence = ("is_prioritised", "genlab_id", "guid", "name", "species", "type")
- order_by = ("-is_prioritised", "species", "genlab_id", "name")
+ sequence = (
+ "checked",
+ "is_prioritised",
+ "genlab_id",
+ "guid",
+ "name",
+ "species",
+ "type",
+ )
+ order_by = (
+ "-is_prioritised",
+ "species",
+ "genlab_id",
+ "name_as_int",
+ )
empty_text = "No Samples"
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ if hasattr(self.data, "data"):
+ self.data.data = self.data.data.annotate(
+ name_as_int=Cast("name", output_field=IntegerField())
+ )
+
def render_plate_positions(self, value: Any) -> str:
if value:
return ", ".join([str(v) for v in value.all()])
return ""
+ def render_checked(self, record: Any) -> str:
+ return mark_safe(f'') # noqa: S308
+
def create_sample_table(base_fields: list[str] | None = None) -> type[tables.Table]:
class CustomSampleTable(tables.Table):
diff --git a/src/staff/templates/staff/analysisorder_filter.html b/src/staff/templates/staff/analysisorder_filter.html
index 0a0d6946..a0ac89a4 100644
--- a/src/staff/templates/staff/analysisorder_filter.html
+++ b/src/staff/templates/staff/analysisorder_filter.html
@@ -1,5 +1,19 @@
{% extends "staff/base_filter.html" %}
+{% load crispy_forms_tags static %}
+{% load render_table from django_tables2 %}
{% block page-title %}
Analysis Orders
{% endblock page-title %}
+
+{% block page-inner %}
+
+
+{% render_table table %}
+
+{% endblock page-inner %}
diff --git a/src/staff/templates/staff/base_filter.html b/src/staff/templates/staff/base_filter.html
index 66b774cf..96f0cd28 100644
--- a/src/staff/templates/staff/base_filter.html
+++ b/src/staff/templates/staff/base_filter.html
@@ -5,19 +5,23 @@
{% block content %}
{% block page-title %}{% endblock page-title %}
{% block page-inner %}{% endblock page-inner %}
-
-
-
- {% render_table table %}
{% endblock %}
{% block body_javascript %}
{{ block.super }}
{{ filter.form.media }}
+
{% endblock body_javascript %}
diff --git a/src/staff/templates/staff/equipmentorder_filter.html b/src/staff/templates/staff/equipmentorder_filter.html
index 25b2704d..434f0c13 100644
--- a/src/staff/templates/staff/equipmentorder_filter.html
+++ b/src/staff/templates/staff/equipmentorder_filter.html
@@ -1,5 +1,19 @@
{% extends "staff/base_filter.html" %}
+{% load crispy_forms_tags static %}
+{% load render_table from django_tables2 %}
{% block page-title %}
Equipment Orders
{% endblock page-title %}
+
+{% block page-inner %}
+
+
+{% render_table table %}
+
+{% endblock page-inner %}
diff --git a/src/staff/templates/staff/extractionorder_filter.html b/src/staff/templates/staff/extractionorder_filter.html
index f0ff30b7..e2c1e8d9 100644
--- a/src/staff/templates/staff/extractionorder_filter.html
+++ b/src/staff/templates/staff/extractionorder_filter.html
@@ -1,5 +1,19 @@
{% extends "staff/base_filter.html" %}
+{% load crispy_forms_tags static %}
+{% load render_table from django_tables2 %}
{% block page-title %}
Extraction Orders
{% endblock page-title %}
+
+{% block page-inner %}
+
+
+{% render_table table %}
+
+{% endblock page-inner %}
diff --git a/src/staff/templates/staff/extractionplate_filter.html b/src/staff/templates/staff/extractionplate_filter.html
index 877fca41..26e046dd 100644
--- a/src/staff/templates/staff/extractionplate_filter.html
+++ b/src/staff/templates/staff/extractionplate_filter.html
@@ -1,4 +1,6 @@
{% extends "staff/base_filter.html" %}
+{% load crispy_forms_tags static %}
+{% load render_table from django_tables2 %}
{% block page-title %}
Extraction plates
@@ -8,4 +10,13 @@
+
+
+
+{% render_table table %}
{% endblock page-inner %}
diff --git a/src/staff/templates/staff/project_filter.html b/src/staff/templates/staff/project_filter.html
index 22279374..b0434b4c 100644
--- a/src/staff/templates/staff/project_filter.html
+++ b/src/staff/templates/staff/project_filter.html
@@ -1,5 +1,19 @@
{% extends "staff/base_filter.html" %}
+{% load crispy_forms_tags static %}
+{% load render_table from django_tables2 %}
{% block page-title %}
Projects
{% endblock page-title %}
+
+{% block page-inner %}
+
+
+{% render_table table %}
+
+{% endblock page-inner %}
diff --git a/src/staff/templates/staff/sample_filter.html b/src/staff/templates/staff/sample_filter.html
index 221b70d9..55f3d5d8 100644
--- a/src/staff/templates/staff/sample_filter.html
+++ b/src/staff/templates/staff/sample_filter.html
@@ -1,7 +1,22 @@
{% extends "staff/base_filter.html" %}
+{% load crispy_forms_tags static %}
+{% load render_table from django_tables2 %}
{% block page-title %}
-{% if order %}{{ order }} - Samples{% else %}Samples{% endif %}
+
+
+ {% if order %}
+ {{ order }} - Samples
+ {% else %}
+ Samples
+ {% endif %}
+
+ {% if order %}
+
+ {{ order.filled_genlab_count }} / {{ order.samples.count }} Genlabs generated
+
+ {% endif %}
+
{% endblock page-title %}
{% block page-inner %}
@@ -11,5 +26,17 @@
Download CSV
Lab
+
+
{% endif %}
{% endblock page-inner %}
diff --git a/src/staff/templates/staff/samplemarkeranalysis_filter.html b/src/staff/templates/staff/samplemarkeranalysis_filter.html
index 4f81078d..227e0a18 100644
--- a/src/staff/templates/staff/samplemarkeranalysis_filter.html
+++ b/src/staff/templates/staff/samplemarkeranalysis_filter.html
@@ -1,4 +1,6 @@
{% extends "staff/base_filter.html" %}
+{% load crispy_forms_tags static %}
+{% load render_table from django_tables2 %}
{% block page-title %}
{% if order %}{{ order }} - Samples{% else %}Samples{% endif %}
@@ -10,4 +12,14 @@
back
{% endif %}
+
+
+
+{% render_table table %}
+
{% endblock page-inner %}
diff --git a/src/staff/urls.py b/src/staff/urls.py
index 28be320b..6dc62299 100644
--- a/src/staff/urls.py
+++ b/src/staff/urls.py
@@ -11,6 +11,7 @@
ExtractionPlateCreateView,
ExtractionPlateDetailView,
ExtractionPlateListView,
+ GenerateGenlabIDsView,
ManaullyCheckedOrderActionView,
OrderAnalysisSamplesListView,
OrderExtractionSamplesListView,
@@ -85,6 +86,11 @@
SampleLabView.as_view(),
name="order-extraction-samples-lab",
),
+ path(
+ "orders/extraction//samples/generate-genlab-ids/",
+ GenerateGenlabIDsView.as_view(),
+ name="generate-genlab-ids",
+ ),
path(
"orders/analysis//samples/",
OrderAnalysisSamplesListView.as_view(),
diff --git a/src/staff/views.py b/src/staff/views.py
index 9292a26b..dbcddfdd 100644
--- a/src/staff/views.py
+++ b/src/staff/views.py
@@ -381,11 +381,7 @@ def form_valid(self, form: Form) -> HttpResponse:
_("The order was checked, GenLab IDs will be generated"),
)
except Exception as e:
- messages.add_message(
- self.request,
- messages.ERROR,
- f"Error: {str(e)}",
- )
+ messages.error(self.request, f"Error: {str(e)}")
return super().form_valid(form)
@@ -513,6 +509,54 @@ def form_invalid(self, form: Form) -> HttpResponse:
return HttpResponseRedirect(self.get_success_url())
+class GenerateGenlabIDsView(
+ SingleObjectMixin, StaffMixin, SingleTableMixin, FilterView
+):
+ model = ExtractionOrder
+
+ def get_object(self) -> ExtractionOrder:
+ return ExtractionOrder.objects.get(pk=self.kwargs["pk"])
+
+ def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
+ self.object = self.get_object()
+ selected_ids = request.POST.getlist("checked")
+
+ if not selected_ids:
+ messages.error(request, "No samples were selected.")
+ return HttpResponseRedirect(self.get_return_url())
+
+ sort_param = request.POST.get("sort", "")
+ sorting_order = [s.strip() for s in sort_param.split(",") if s.strip()]
+
+ selected_samples = Sample.objects.filter(pk__in=selected_ids)
+
+ if sorting_order:
+ selected_samples = selected_samples.order_by(*sorting_order)
+
+ try:
+ self.object.order_selected_checked(
+ sorting_order=sorting_order, selected_samples=selected_samples
+ )
+ messages.add_message(
+ request,
+ messages.SUCCESS,
+ _(f"Genlab IDs generated for {selected_samples.count()} samples."),
+ )
+ except Exception as e:
+ messages.add_message(
+ request,
+ messages.ERROR,
+ f"Error: {str(e)}",
+ )
+
+ return HttpResponseRedirect(self.get_return_url())
+
+ def get_return_url(self) -> str:
+ return reverse_lazy(
+ "staff:order-extraction-samples", kwargs={"pk": self.object.pk}
+ )
+
+
class ExtractionPlateCreateView(StaffMixin, CreateView):
model = ExtractionPlate
form_class = ExtractionPlateForm