From 1115a8fe9a4de6be090b34fec42872abf9c89fa6 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Tue, 2 Apr 2024 22:37:20 +0530 Subject: [PATCH 1/3] Dropped `age` column from patient registration and annotated age in patient viewset (#1966) * drop field `age` and compute age on demand * Update filters and related usages * add min value validator for year of birth * remove age from test utils * remove unnecessary default * Add field `death_datetime` in patient registration * annotate age in days * suggestion from code review * revert unintended changes * skip serializing age * rebase migrations * rebase migrations * drop age from dummy data * Revert "drop age from dummy data" This reverts commit 9964b853e24e30ff332a8c38128cd484510f8c6f. * drop patient registration's age from dummy data * select only `last_consultation` in custom migration Co-authored-by: Aakash Singh * format code * rebase migrations * rebase migrations --------- Co-authored-by: Aakash Singh --- care/facility/admin.py | 2 +- care/facility/api/serializers/patient.py | 23 +++-- care/facility/api/viewsets/patient.py | 87 ++++++++++++++----- ..._add_patientregistration_death_datetime.py | 65 ++++++++++++++ care/facility/models/patient.py | 29 ++----- care/facility/models/patient_consultation.py | 14 +++ care/facility/models/patient_icmr.py | 27 +++--- care/utils/tests/test_utils.py | 1 - data/dummy/facility.json | 18 ---- 9 files changed, 177 insertions(+), 89 deletions(-) create mode 100644 care/facility/migrations/0424_remove_patientregistration_age_and_add_patientregistration_death_datetime.py diff --git a/care/facility/admin.py b/care/facility/admin.py index bc5b3fb650..db0ac6a173 100644 --- a/care/facility/admin.py +++ b/care/facility/admin.py @@ -147,7 +147,7 @@ class AmbulanceDriverAdmin(DjangoQLSearchMixin, admin.ModelAdmin): class PatientAdmin(DjangoQLSearchMixin, admin.ModelAdmin): - list_display = ("id", "name", "age", "gender") + list_display = ("id", "name", "year_of_birth", "gender") djangoql_completion_enabled_by_default = True diff --git a/care/facility/api/serializers/patient.py b/care/facility/api/serializers/patient.py index 28993af4ca..5ba675a734 100644 --- a/care/facility/api/serializers/patient.py +++ b/care/facility/api/serializers/patient.py @@ -121,13 +121,12 @@ class Meta: "created_by", "deleted", "ongoing_medication", - "year_of_birth", "meta_info", "countries_travelled_old", "allergies", "external_id", ) - read_only = TIMESTAMP_FIELDS + read_only = TIMESTAMP_FIELDS + ("death_datetime",) class PatientContactDetailsSerializer(serializers.ModelSerializer): @@ -223,12 +222,16 @@ class Meta: model = PatientRegistration exclude = ( "deleted", - "year_of_birth", "countries_travelled_old", "external_id", ) include = ("contacted_patients",) - read_only = TIMESTAMP_FIELDS + ("last_edited", "created_by", "is_active") + read_only = TIMESTAMP_FIELDS + ( + "last_edited", + "created_by", + "is_active", + "death_datetime", + ) # def get_last_consultation(self, obj): # last_consultation = PatientConsultation.objects.filter(patient=obj).last() @@ -250,13 +253,15 @@ def validate_countries_travelled(self, value): def validate(self, attrs): validated = super().validate(attrs) - if ( - not self.partial - and not validated.get("age") - and not validated.get("date_of_birth") + if not self.partial and not ( + validated.get("year_of_birth") or validated.get("date_of_birth") ): raise serializers.ValidationError( - {"non_field_errors": ["Either age or date_of_birth should be passed"]} + { + "non_field_errors": [ + "Either year_of_birth or date_of_birth should be passed" + ] + } ) if validated.get("is_vaccinated"): diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 704dc98bbb..f55a60030a 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -5,7 +5,18 @@ from django.conf import settings from django.contrib.postgres.search import TrigramSimilarity from django.db import models -from django.db.models import Case, OuterRef, Q, Subquery, When +from django.db.models import ( + Case, + ExpressionWrapper, + F, + Func, + OuterRef, + Q, + Subquery, + Value, + When, +) +from django.db.models.functions import Coalesce, ExtractDay, Now from django.db.models.query import QuerySet from django_filters import rest_framework as filters from djqscsv import render_to_csv_response @@ -333,25 +344,61 @@ class PatientViewSet( ] permission_classes = (IsAuthenticated, DRYPermissions) lookup_field = "external_id" - queryset = PatientRegistration.objects.all().select_related( - "local_body", - "district", - "state", - "ward", - "assigned_to", - "facility", - "facility__ward", - "facility__local_body", - "facility__district", - "facility__state", - # "nearest_facility", - # "nearest_facility__local_body", - # "nearest_facility__district", - # "nearest_facility__state", - "last_consultation", - "last_consultation__assigned_to", - "last_edited", - "created_by", + queryset = ( + PatientRegistration.objects.all() + .select_related( + "local_body", + "district", + "state", + "ward", + "assigned_to", + "facility", + "facility__ward", + "facility__local_body", + "facility__district", + "facility__state", + # "nearest_facility", + # "nearest_facility__local_body", + # "nearest_facility__district", + # "nearest_facility__state", + "last_consultation", + "last_consultation__assigned_to", + "last_edited", + "created_by", + ) + .annotate( + coalesced_dob=Coalesce( + "date_of_birth", + Func( + F("year_of_birth"), + Value(1), + Value(1), + function="MAKE_DATE", + output_field=models.DateField(), + ), + output_field=models.DateField(), + ), + age_end=Case( + When(death_datetime__isnull=True, then=Now()), + default=F("death_datetime__date"), + ), + ) + .annotate( + age=Func( + Value("year"), + Func( + F("age_end"), + F("coalesced_dob"), + function="age", + ), + function="date_part", + output_field=models.IntegerField(), + ), + age_days=ExpressionWrapper( + ExtractDay(F("age_end") - F("coalesced_dob")), + output_field=models.IntegerField(), + ), + ) ) ordering_fields = [ "facility__name", diff --git a/care/facility/migrations/0424_remove_patientregistration_age_and_add_patientregistration_death_datetime.py b/care/facility/migrations/0424_remove_patientregistration_age_and_add_patientregistration_death_datetime.py new file mode 100644 index 0000000000..e051559ba8 --- /dev/null +++ b/care/facility/migrations/0424_remove_patientregistration_age_and_add_patientregistration_death_datetime.py @@ -0,0 +1,65 @@ +# Generated by Django 4.2.8 on 2024-03-13 07:03 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0423_patientconsultation_consent_records_and_more"), + ] + + def populate_patientregistration_death_datetime(apps, schema_editor): + PatientRegistration = apps.get_model("facility", "PatientRegistration") + + patients = ( + PatientRegistration.objects.only("last_consultation") + .filter(last_consultation__death_datetime__isnull=False) + .annotate(new_death_datetime=models.F("last_consultation__death_datetime")) + ) + + for patient in patients: + patient.death_datetime = patient.new_death_datetime + + PatientRegistration.objects.bulk_update( + patients, ["death_datetime"], batch_size=1000 + ) + + operations = [ + migrations.RemoveField( + model_name="historicalpatientregistration", + name="age", + ), + migrations.RemoveField( + model_name="patientregistration", + name="age", + ), + migrations.AddField( + model_name="historicalpatientregistration", + name="death_datetime", + field=models.DateTimeField(default=None, null=True), + ), + migrations.AddField( + model_name="patientregistration", + name="death_datetime", + field=models.DateTimeField(default=None, null=True), + ), + migrations.AlterField( + model_name="historicalpatientregistration", + name="year_of_birth", + field=models.IntegerField( + null=True, validators=[django.core.validators.MinValueValidator(1900)] + ), + ), + migrations.AlterField( + model_name="patientregistration", + name="year_of_birth", + field=models.IntegerField( + null=True, validators=[django.core.validators.MinValueValidator(1900)] + ), + ), + migrations.RunPython( + populate_patientregistration_death_datetime, + migrations.RunPython.noop, + ), + ] diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 1c5408bebe..090fee13fe 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -1,10 +1,10 @@ -import datetime import enum from django.contrib.postgres.aggregates import ArrayAgg from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models import JSONField +from django.utils import timezone from simple_history.models import HistoricalRecords from care.abdm.models import AbhaNumber @@ -109,7 +109,6 @@ class TestTypeEnum(enum.Enum): # name_old = EncryptedCharField(max_length=200, default="") name = models.CharField(max_length=200, default="") - age = models.PositiveIntegerField(null=True, blank=True) gender = models.IntegerField(choices=GENDER_CHOICES, blank=False) # phone_number_old = EncryptedCharField(max_length=14, validators=[phone_number_regex], default="") @@ -128,7 +127,8 @@ class TestTypeEnum(enum.Enum): pincode = models.IntegerField(default=0, blank=True, null=True) date_of_birth = models.DateField(default=None, null=True) - year_of_birth = models.IntegerField(default=0, null=True) + year_of_birth = models.IntegerField(validators=[MinValueValidator(1900)], null=True) + death_datetime = models.DateTimeField(default=None, null=True) nationality = models.CharField( max_length=255, default="", verbose_name="Nationality of Patient" @@ -426,7 +426,7 @@ class TestTypeEnum(enum.Enum): objects = BaseManager() def __str__(self): - return "{} - {} - {}".format(self.name, self.age, self.get_gender_display()) + return f"{self.name} - {self.year_of_birth} - {self.get_gender_display()}" @property def tele_consultation_history(self): @@ -458,30 +458,13 @@ def save(self, *args, **kwargs) -> None: if self.district is not None: self.state = self.district.state - self.year_of_birth = ( - self.date_of_birth.year - if self.date_of_birth is not None - else datetime.datetime.now().year - self.age - ) - - today = datetime.date.today() - if self.date_of_birth: - self.age = ( - today.year - - self.date_of_birth.year - - ( - (today.month, today.day) - < (self.date_of_birth.month, self.date_of_birth.day) - ) - ) - elif self.year_of_birth: - self.age = today.year - self.year_of_birth + self.year_of_birth = self.date_of_birth.year self.date_of_receipt_of_information = ( self.date_of_receipt_of_information if self.date_of_receipt_of_information is not None - else datetime.datetime.now() + else timezone.now() ) self._alias_recovery_to_recovered() diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py index 8f6797f997..91dad91de4 100644 --- a/care/facility/models/patient_consultation.py +++ b/care/facility/models/patient_consultation.py @@ -286,6 +286,20 @@ def get_related_consultation(self): def __str__(self): return f"{self.patient.name}<>{self.facility.name}" + def save(self, *args, **kwargs): + """ + # Removing Patient Hospital Change on Referral + if not self.pk or self.referred_to is not None: + # pk is None when the consultation is created + # referred to is not null when the person is being referred to a new facility + self.patient.facility = self.referred_to or self.facility + self.patient.save() + """ + if self.death_datetime and self.patient.death_datetime != self.death_datetime: + self.patient.death_datetime = self.death_datetime + self.patient.save(update_fields=["death_datetime"]) + super(PatientConsultation, self).save(*args, **kwargs) + class Meta: constraints = [ models.CheckConstraint( diff --git a/care/facility/models/patient_icmr.py b/care/facility/models/patient_icmr.py index 5d33c25ed3..677b278322 100644 --- a/care/facility/models/patient_icmr.py +++ b/care/facility/models/patient_icmr.py @@ -1,6 +1,7 @@ import datetime from dateutil.relativedelta import relativedelta +from django.utils import timezone from care.facility.models import ( DISEASE_CHOICES_MAP, @@ -45,26 +46,18 @@ class Meta: # instance.__class__ = PatientSampleICMR # return instance + def get_age_delta(self): + start = self.date_of_birth or timezone.datetime(self.year_of_birth, 1, 1).date() + end = (self.death_datetime or timezone.now()).date() + return relativedelta(end, start) + @property - def age_years(self): - if self.date_of_birth is not None: - age_years = relativedelta(datetime.datetime.now(), self.date_of_birth).years - else: - age_years = relativedelta( - datetime.datetime.now(), - datetime.datetime(year=self.year_of_birth, month=1, day=1), - ).years - return age_years + def age_years(self) -> int: + return self.get_age_delta().year @property - def age_months(self): - if self.date_of_birth is None or self.year_of_birth is None: - age_months = 0 - else: - age_months = relativedelta( - datetime.datetime.now(), self.date_of_birth - ).months - return age_months + def age_months(self) -> int: + return self.get_age_delta().months @property def email(self): diff --git a/care/utils/tests/test_utils.py b/care/utils/tests/test_utils.py index 446249e949..fcc93584b4 100644 --- a/care/utils/tests/test_utils.py +++ b/care/utils/tests/test_utils.py @@ -238,7 +238,6 @@ def create_facility( def get_patient_data(cls, district, state) -> dict: return { "name": "Foo", - "age": 32, "date_of_birth": date(1992, 4, 1), "gender": 2, "is_medical_worker": True, diff --git a/data/dummy/facility.json b/data/dummy/facility.json index 2104a23cd1..9976ca3646 100644 --- a/data/dummy/facility.json +++ b/data/dummy/facility.json @@ -4393,7 +4393,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient", - "age": 18, "gender": 1, "phone_number": "+919987455444", "emergency_phone_number": "+919898797775", @@ -4474,7 +4473,6 @@ "nearest_facility": null, "meta_info": null, "name": "Test E2E User", - "age": 22, "gender": 1, "phone_number": "+919765259927", "emergency_phone_number": "+919228973557", @@ -4555,7 +4553,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 1", - "age": 22, "gender": 1, "phone_number": "+919192495353", "emergency_phone_number": "+919460491040", @@ -4636,7 +4633,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 2", - "age": 22, "gender": 1, "phone_number": "+919112608904", "emergency_phone_number": "+919110616234", @@ -4717,7 +4713,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 3", - "age": 22, "gender": 1, "phone_number": "+919640229897", "emergency_phone_number": "+919135436547", @@ -4798,7 +4793,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 4", - "age": 22, "gender": 1, "phone_number": "+919762277015", "emergency_phone_number": "+919342634016", @@ -4879,7 +4873,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 5", - "age": 22, "gender": 1, "phone_number": "+919303212282", "emergency_phone_number": "+919229738916", @@ -4960,7 +4953,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 6", - "age": 22, "gender": 1, "phone_number": "+919740701377", "emergency_phone_number": "+919321666516", @@ -5041,7 +5033,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 7", - "age": 22, "gender": 1, "phone_number": "+919148299129", "emergency_phone_number": "+919267280161", @@ -5122,7 +5113,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 8", - "age": 22, "gender": 1, "phone_number": "+919490490290", "emergency_phone_number": "+919828674710", @@ -5203,7 +5193,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 9", - "age": 22, "gender": 1, "phone_number": "+919983927490", "emergency_phone_number": "+919781111140", @@ -5284,7 +5273,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 10", - "age": 22, "gender": 1, "phone_number": "+919849511866", "emergency_phone_number": "+919622326248", @@ -5365,7 +5353,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 11", - "age": 22, "gender": 1, "phone_number": "+919343556704", "emergency_phone_number": "+919967920474", @@ -5446,7 +5433,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 12", - "age": 22, "gender": 1, "phone_number": "+919320374643", "emergency_phone_number": "+919493558024", @@ -5527,7 +5513,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 13", - "age": 22, "gender": 1, "phone_number": "+919292990239", "emergency_phone_number": "+919992258784", @@ -5608,7 +5593,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 14", - "age": 22, "gender": 1, "phone_number": "+919650206292", "emergency_phone_number": "+919596454242", @@ -5689,7 +5673,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 15", - "age": 22, "gender": 1, "phone_number": "+919266236581", "emergency_phone_number": "+919835286558", @@ -5770,7 +5753,6 @@ "nearest_facility": null, "meta_info": null, "name": "Dummy Patient 16", - "age": 22, "gender": 1, "phone_number": "+919243083817", "emergency_phone_number": "+919924971004", From 182b43fcc15e1d61328d7b5284de39734f5c3296 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 11:12:52 +0530 Subject: [PATCH 2/3] Bump the boto group with 2 updates (#2044) Bumps the boto group with 2 updates: [boto3](https://github.com/boto/boto3) and [boto3-stubs](https://github.com/youtype/mypy_boto3_builder). Updates `boto3` from 1.34.65 to 1.34.75 - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.34.65...1.34.75) Updates `boto3-stubs` from 1.34.65 to 1.34.75 - [Release notes](https://github.com/youtype/mypy_boto3_builder/releases) - [Commits](https://github.com/youtype/mypy_boto3_builder/commits) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: boto - dependency-name: boto3-stubs dependency-type: direct:development update-type: version-update:semver-patch dependency-group: boto ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vignesh Hari --- Pipfile | 4 ++-- Pipfile.lock | 29 +++++++++++++++-------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Pipfile b/Pipfile index 61b01767b0..d4ffa91468 100644 --- a/Pipfile +++ b/Pipfile @@ -6,7 +6,7 @@ name = "pypi" [packages] argon2-cffi = "==23.1.0" authlib = "==1.2.1" -boto3 = "==1.34.65" +boto3 = "==1.34.75" celery = "==5.3.6" django = "==4.2.10" django-environ = "==0.11.2" @@ -48,7 +48,7 @@ redis-om = "==0.2.1" [dev-packages] black = "==23.9.1" -boto3-stubs = {extras = ["s3", "boto3"], version = "==1.34.65"} +boto3-stubs = {extras = ["s3", "boto3"], version = "==1.34.75"} coverage = "==7.4.0" debugpy = "==1.8.1" django-coverage-plugin = "==3.1.0" diff --git a/Pipfile.lock b/Pipfile.lock index 90f6080148..8bbef9e4ff 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ea4fe23094588fde2796a4107473d14dc5a47a9795a08582da5182dac2b5c4fb" + "sha256": "d3f8439435571930893eb20d0599cf4de93bfb4646965c3725af4fdd966b8138" }, "pipfile-spec": 6, "requires": { @@ -94,20 +94,20 @@ }, "boto3": { "hashes": [ - "sha256:b611de58ab28940a36c77d7ef9823427ebf25d5ee8277b802f9979b14e780534", - "sha256:db97f9c29f1806cf9020679be0dd5ffa2aff2670e28e0e2046f98b979be498a4" + "sha256:ba5d2104bba4370766036d64ad9021eb6289d154265852a2a821ec6a5e816faa", + "sha256:eaec72fda124084105a31bcd67eafa1355b34df6da70cadae0c0f262d8a4294f" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.34.65" + "version": "==1.34.75" }, "botocore": { "hashes": [ - "sha256:92560f8fbdaa9dd221212a3d3a7609219ba0bbf308c13571674c0cda9d8f39e1", - "sha256:fd7d8742007c220f897cb126b8916ca0cf3724a739d4d716aa5385d7f9d8aeb1" + "sha256:06113ee2587e6160211a6bd797e135efa6aa21b5bde97bf455c02f7dff40203c", + "sha256:1d7f683d99eba65076dfb9af3b42fa967c64f11111d9699b65757420902aa002" ], "markers": "python_version >= '3.8'", - "version": "==1.34.66" + "version": "==1.34.75" }, "celery": { "hashes": [ @@ -1309,7 +1309,7 @@ "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" ], - "markers": "python_version >= '3.8'", + "markers": "python_version >= '3.6'", "version": "==2.2.1" }, "vine": { @@ -1412,11 +1412,12 @@ "s3" ], "hashes": [ - "sha256:105da4a04dcb5e4ddc90f21ab8b24a3423ecfacb4775b8ccd3879574e5dce358", - "sha256:2afd696c8bb4daf8890ecd75a720e1733cd8b8556eaecc92c36f9b56fc6013bd" + "sha256:78093a0bf5a03bc66a79d6cddb9f0eb67b67ed6b008cba4cf394c0c9d11de2c1", + "sha256:bb55fe97f474ea800c762592d81369bb6c23a8e53a5b2d8497145f87c1d7640c" ], + "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.34.65" + "version": "==1.34.75" }, "botocore": { "hashes": [ @@ -1428,11 +1429,11 @@ }, "botocore-stubs": { "hashes": [ - "sha256:530ea7d66022ec6aa0ba0c5200a2aede5d30b839c632d00962f0cf4f806c6a51", - "sha256:a5aa1240c3c8ccc62d43916395943896afa81399dc5d4203127cc0ffba20f999" + "sha256:0c3835c775db1387246c1ba8063b197604462fba8603d9b36b5dc60297197b2f", + "sha256:463248fd1d6e7b68a0c57bdd758d04c6bd0c5c2c3bfa81afdf9d64f0930b59bc" ], "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==1.34.66" + "version": "==1.34.69" }, "certifi": { "hashes": [ From ce619ef580c596bbee7423e69b0c90bc78471569 Mon Sep 17 00:00:00 2001 From: Pranshu Aggarwal <70687348+Pranshu1902@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:05:27 +0530 Subject: [PATCH 3/3] Add validations for negative values in inventory and min quanityt (#2005) Add validations for negative values in inventory Co-authored-by: Aakash Singh --- ..._facilityinventorylog_quantity_and_more.py | 27 +++++++++++++++++++ care/facility/models/inventory.py | 5 ++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 care/facility/migrations/0422_alter_facilityinventorylog_quantity_and_more.py diff --git a/care/facility/migrations/0422_alter_facilityinventorylog_quantity_and_more.py b/care/facility/migrations/0422_alter_facilityinventorylog_quantity_and_more.py new file mode 100644 index 0000000000..561a0dbf61 --- /dev/null +++ b/care/facility/migrations/0422_alter_facilityinventorylog_quantity_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.10 on 2024-03-22 11:21 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0421_merge_20240318_1434"), + ] + + operations = [ + migrations.AlterField( + model_name="facilityinventorylog", + name="quantity", + field=models.FloatField( + default=0, validators=[django.core.validators.MinValueValidator(0.0)] + ), + ), + migrations.AlterField( + model_name="facilityinventoryminquantity", + name="min_quantity", + field=models.FloatField( + default=0, validators=[django.core.validators.MinValueValidator(0.0)] + ), + ), + ] diff --git a/care/facility/models/inventory.py b/care/facility/models/inventory.py index 934b0128f4..e0eb056e49 100644 --- a/care/facility/models/inventory.py +++ b/care/facility/models/inventory.py @@ -1,4 +1,5 @@ from django.contrib.auth import get_user_model +from django.core.validators import MinValueValidator from django.db import models from django.db.models import Index @@ -108,7 +109,7 @@ class FacilityInventoryLog(FacilityBaseModel, FacilityRelatedPermissionMixin): ) current_stock = models.FloatField(default=0) quantity_in_default_unit = models.FloatField(default=0) - quantity = models.FloatField(default=0) + quantity = models.FloatField(default=0, validators=[MinValueValidator(0.0)]) unit = models.ForeignKey( FacilityInventoryUnit, on_delete=models.SET_NULL, null=True, blank=False ) @@ -157,7 +158,7 @@ class FacilityInventoryMinQuantity(FacilityBaseModel, FacilityRelatedPermissionM item = models.ForeignKey( FacilityInventoryItem, on_delete=models.SET_NULL, null=True, blank=False ) - min_quantity = models.FloatField(default=0) + min_quantity = models.FloatField(default=0, validators=[MinValueValidator(0.0)]) class Meta: constraints = [