diff --git a/.github/workflows/deployment.yaml b/.github/workflows/deployment.yaml index 762f965284..38d6537ef5 100644 --- a/.github/workflows/deployment.yaml +++ b/.github/workflows/deployment.yaml @@ -3,9 +3,11 @@ name: Deploy Care on: workflow_dispatch: push: + tags: + - 'v*' branches: - - master - - production + - develop + - staging paths-ignore: - "docs/**" @@ -33,32 +35,35 @@ jobs: test: uses: ./.github/workflows/test-base.yml - build-staging: + build: needs: test - name: Build & Push Staging to container registries - if: github.ref == 'refs/heads/master' + name: Build & Push to container registries runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Docker meta + - name: Generate docker tags id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: | ghcr.io/${{ github.repository }} ${{ secrets.DOCKER_HUB_USERNAME }}/${{ github.event.repository.name }} tags: | - type=raw,value=latest-${{ github.run_number }} + type=raw,value=production-latest,enable=${{ github.ref == 'refs/heads/v*' }} + type=raw,value=production-latest-${{ github.run_number }}-{{date 'YYYYMMDD'}}-{{sha}},enable=${{ github.ref == 'refs/heads/v*' }} + type=raw,value=staging-latest,enable=${{ github.ref == 'refs/heads/staging' }} + type=raw,value=staging-latest-${{ github.run_number }}-{{date 'YYYYMMDD'}}-{{sha}},enable=${{ github.ref == 'refs/heads/staging' }} + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/develop' }} + type=raw,value=latest-${{ github.run_number }},enable=${{ github.ref == 'refs/heads/develop' }} type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} flavor: | - latest=true + latest=false - - name: Set up QEMU + - name: Setup QEMU uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx + - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to DockerHub @@ -75,14 +80,14 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Cache Docker layers - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ hashFiles('Pipfile.lock', 'docker/prod.Dockerfile') }} + key: ${{ runner.os }}-buildx-build-${{ hashFiles('Pipfile.lock', 'docker/prod.Dockerfile') }} restore-keys: | - ${{ runner.os }}-buildx- + ${{ runner.os }}-buildx-build- - - name: Build image + - name: Build and push image uses: docker/build-push-action@v5 with: context: . @@ -110,86 +115,19 @@ jobs: rm -rf /tmp/.buildx-cache mv /tmp/.buildx-cache-new /tmp/.buildx-cache - build-production: - needs: test - name: Build & Push Production to container registries - if: github.ref == 'refs/heads/production' + notify-release: + needs: build + if: github.ref == 'refs/tags/v*' + name: Notify release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - - name: Docker meta - id: meta - uses: docker/metadata-action@v4 - with: - images: | - ghcr.io/${{ github.repository }} - ${{ secrets.DOCKER_HUB_USERNAME }}/${{ github.event.repository.name }} - tags: | - type=raw,value=production-latest,enable=${{ github.ref == 'refs/heads/production' }} - type=raw,value=production-latest-${{ github.run_number }}-{{date 'YYYYMMDD'}}-{{sha}} - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - flavor: | - latest=false - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Cache Docker layers - uses: actions/cache@v3 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ hashFiles('Pipfile.lock', 'docker/prod.Dockerfile') }} - restore-keys: | - ${{ runner.os }}-buildx- - - - name: Build image - uses: docker/build-push-action@v5 - with: - context: . - file: docker/prod.Dockerfile - push: true - provenance: false - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.meta.outputs.tags }} - build-args: | - APP_VERSION=${{ github.sha }} - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max - - - name: Create Sentry release - uses: getsentry/action-release@v1 - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_ORG: ${{ secrets.SENTRY_ORG }} - SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - with: - version: ${{ github.sha }} - - - name: Move cache + - name: Notify release run: | - rm -rf /tmp/.buildx-cache - mv /tmp/.buildx-cache-new /tmp/.buildx-cache + echo "Release ${{ github.sha }} is ready to be deployed to production" deploy-staging-egov: - needs: build-staging + needs: build + if: github.ref == 'refs/heads/develop' name: Deploy to ECS API Egov runs-on: ubuntu-latest environment: @@ -253,7 +191,8 @@ jobs: wait-for-service-stability: true deploy-staging-gcp: - needs: build-staging + needs: build + if: github.ref == 'refs/heads/staging' name: Deploy to staging GCP cluster runs-on: ubuntu-latest environment: @@ -299,7 +238,7 @@ jobs: kubectl apply -f care-celery-worker.yaml deploy-production-manipur: - needs: build-production + needs: notify-release name: Deploy to GKE Manipur runs-on: ubuntu-latest environment: @@ -345,7 +284,7 @@ jobs: kubectl apply -f care-celery-worker.yaml deploy-production-karnataka: - needs: build-production + needs: notify-release name: Deploy to GKE Karnataka runs-on: ubuntu-latest environment: @@ -391,7 +330,7 @@ jobs: kubectl apply -f care-celery-worker.yaml deploy-production-assam: - needs: build-production + needs: notify-release name: Deploy to GKE Assam runs-on: ubuntu-latest environment: @@ -437,7 +376,7 @@ jobs: kubectl apply -f care-celery-worker.yaml deploy-production-sikkim: - needs: build-production + needs: notify-release name: Deploy to GKE Sikkim runs-on: ubuntu-latest environment: @@ -483,7 +422,7 @@ jobs: kubectl apply -f care-celery-worker.yaml deploy-production-nagaland: - needs: build-production + needs: notify-release name: Deploy to GKE Nagaland runs-on: ubuntu-latest environment: @@ -529,7 +468,7 @@ jobs: kubectl apply -f care-celery-worker.yaml deploy-production-meghalaya: - needs: build-production + needs: notify-release name: Deploy to GKE Meghalaya runs-on: ubuntu-latest environment: diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index f3df403581..6b992b414c 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -519,16 +519,19 @@ def validate(self, attrs): # TODO Add Bed Authorisation Validation if ( - not self.instance - and "suggestion" in validated - and validated["suggestion"] == SuggestionChoices.A - ): + not self.instance or validated.get("patient_no") != self.instance.patient_no + ) and "suggestion" in validated: + suggestion = validated["suggestion"] patient_no = validated.get("patient_no") - if not patient_no: + + if suggestion == SuggestionChoices.A and not patient_no: raise ValidationError( - {"ip_no": ["This field is required for admission."]} + {"patient_no": "This field is required for admission."} ) - if PatientConsultation.objects.filter( + + if ( + suggestion == SuggestionChoices.A or patient_no + ) and PatientConsultation.objects.filter( patient_no=patient_no, facility=( self.instance.facility @@ -537,7 +540,9 @@ def validate(self, attrs): ), ).exists(): raise ValidationError( - "Patient number must be unique within the facility." + { + "patient_no": "Consultation with this IP/OP number already exists within the facility." + } ) if ( diff --git a/care/facility/migrations/0420_migrate_shifting_facility_type.py b/care/facility/migrations/0420_migrate_shifting_facility_type.py new file mode 100644 index 0000000000..2f3e9de30e --- /dev/null +++ b/care/facility/migrations/0420_migrate_shifting_facility_type.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.10 on 2024-03-14 10:18 + +from django.db import migrations + + +def update_facility_types(apps, schema_editor): + Facility = apps.get_model("facility", "ShiftingRequest") + facilities_to_update = { + 801: 800, # 24x7 Public Health Centres to Primary Health Centres + 820: 800, # Urban Primary Health Center to Primary Health Centres + 831: 830, # Taluk Headquarters Hospitals to Taluk Hospitals + 850: 860, # General hospitals to District Hospitals + 900: 910, # Co-operative hospitals to Autonomous healthcare facility + 950: 870, # Corona Testing Labs to Govt. Labs + 1000: 3, # Corona Care Centre to Other + 8: 870, # Govt Hospital to Govt Medical College Hospitals + } + + for old_id, new_id in facilities_to_update.items(): + Facility.objects.filter(assigned_facility_type=old_id).update( + assigned_facility_type=new_id + ) + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0419_alter_patientconsultation_patient_no"), + ] + + operations = [ + migrations.RunPython(update_facility_types, migrations.RunPython.noop), + ] diff --git a/care/facility/tests/test_patient_consultation_api.py b/care/facility/tests/test_patient_consultation_api.py index 1fa6f056af..758163f404 100644 --- a/care/facility/tests/test_patient_consultation_api.py +++ b/care/facility/tests/test_patient_consultation_api.py @@ -546,6 +546,7 @@ def test_create_consultations_with_duplicate_patient_no_within_facility(self): ) res = self.client.post(self.get_url(), data, format="json") self.assertEqual(res.status_code, status.HTTP_201_CREATED) + data.update( { "patient_no": "IP1234", @@ -557,6 +558,10 @@ def test_create_consultations_with_duplicate_patient_no_within_facility(self): res = self.client.post(self.get_url(), data, format="json") self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + data.update({"suggestion": SuggestionChoices.A}) + res = self.client.post(self.get_url(), data, format="json") + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + def test_create_consultations_with_same_patient_no_in_different_facilities(self): facility2 = self.create_facility( self.super_user, self.district, self.local_body, name="bar"