Repository: msupply-foundation/open-msupply
Pull request #7457: 7370 vaccine emergency order notifications
Author: login: fergie-nz
Labels: name: enhancement
description: New feature or request
name: feature: vaccine
Created: 2025-04-23T05:09:58Z | Merged: 2025-05-13T22:32:12Z

<!-- IMPORTANT!
  - Every PR must reference an issue; this helps to explain the intent of the PR
 -->

Fixes #7370

# 👩🏻‍💻 What does this PR do?

Adds requisition filter for 'is_emergency' and 'automatically_created'
Adds emergency requisition statistics to ReqyuisitionCounts
Adds emergency order filter toggle to requisition list view
Adds emergency order count to dashboard widget with redirect

<!-- Explain the changes you made -->

<!-- why are the changes needed -->

<!-- Add a screenshot if there are UI changes  -->

## 💌 Any notes for the reviewer?



<!-- Do you have any specific questions for the reviewer? -->

<!-- Is there a high risk/complicated change they should focus on? -->

<!-- any general areas of the codebase touched? any side effects caused? -->

<!-- Anything half cooked but going to be finished off in a different PR? -->

# 🧪 Testing

<!-- Explain the steps you'd take to test the changes of this PR manually -->

- [ ] Check dashboard includes emergency order count (response requisitions which are new, is_emergency, and is_automatically_created)
- [ ] Check clicking on this widget redirects to responseRequisition list view with appropriate filters set.
- [ ] Check that changing filters  in requisition list view works as expected with new isEmergencyAndAutomatic filters being set by one toggle.

# 📃 Documentation

- [x] **Part of an epic**: documentation will be completed for the feature as a whole



# 📃 Reviewer Checklist

The PR Reviewer(s) should fill out this section before approving the PR

**Breaking Changes**
- [ ] No Breaking Changes in the Graphql API
- [ ] Technically some Breaking Changes but not expected to impact any integrations

**Issue Review**
- [ ] All requirements in original issue have been covered
- [ ] A follow up issue(s) have been created to cover additional requirements

**Tests Pass**
- [ ] Postgres
- [ ] SQLite
- [ ] Frontend



Linked issue #7370: Vaccine Support: Notify users when an urgent order is received
## Tasks 📝
#### Backend
- [ ] Add ability to filter requisitions by the `is_emergency` field
- [ ] Add filter `automatically_created` where linked_requisition_id == null
- [ ] Add function to get statistics for # of new emergency automatically created requisitions under `RequisitionCountService`

#### Frontend
- [ ] Add `emergency_order` boolean toggle to the Requisition page
- [ ] Hook for function to get # of new emergency automatically created requisitions
- [ ] On the dashboard under `Distribution` -> `Requisitions`, display `Emergency Order`
- [ ] When users click on `Emergency Order`, they will be redirected to the Requisition page with the filters:
   - [ ] Status == new
   - [ ] is_emergency == true
   - [ ] automatically_created == true 

### Acceptance Criteria ✅
- [ ] Users can see # new emergency orders on their dashboard
- [ ] Users are redirected to the Requisition page where they will see a list of new emergency orders when they click on the link on the dashboard
- [ ] Users can filter requisitions by emergency/automatically created

### Moneyworks Jobcode 🧰
NIGGF:VAX

Diff:
diff --git a/client/packages/common/src/intl/locales/en/common.json b/client/packages/common/src/intl/locales/en/common.json
index 46895f03924..05705ebd175 100644
--- a/client/packages/common/src/intl/locales/en/common.json
+++ b/client/packages/common/src/intl/locales/en/common.json
@@ -2029,5 +2029,7 @@
   "warning.caps-lock": "Warning: Caps lock is on",
   "warning.check-before-vaccinating": "Vaccination records from other stores may not be up to date. Ensure you have synced recently, and always confirm against the patient's physical vaccination card first.",
   "warning.field-not-parsed": "{{field}} not parsed",
-  "warning.nothing-to-supply": "Nothing left to supply!"
-}
+  "warning.nothing-to-supply": "Nothing left to supply!",
+  "label.emergency": "Emergency",
+  "label.needs-attention": "Needs attention"
+}
\ No newline at end of file
diff --git a/client/packages/common/src/types/schema.ts b/client/packages/common/src/types/schema.ts
index aaeb1ae8ad8..eeb785fc79d 100644
--- a/client/packages/common/src/types/schema.ts
+++ b/client/packages/common/src/types/schema.ts
@@ -2270,6 +2270,11 @@ export type DoseConfigurationNotAllowed = UpsertItemVariantErrorInterface & {
   description: Scalars['String']['output'];
 };
 
+export type EmergencyResponseRequisitionCounts = {
+  __typename: 'EmergencyResponseRequisitionCounts';
+  new: Scalars['Int']['output'];
+};
+
 export type EncounterConnector = {
   __typename: 'EncounterConnector';
   nodes: Array<EncounterNode>;
@@ -7364,12 +7369,14 @@ export type RequisitionConnector = {
 
 export type RequisitionCounts = {
   __typename: 'RequisitionCounts';
+  emergency: EmergencyResponseRequisitionCounts;
   request: RequestRequisitionCounts;
   response: ResponseRequisitionCounts;
 };
 
 export type RequisitionFilterInput = {
   aShipmentHasBeenCreated?: InputMaybe<Scalars['Boolean']['input']>;
+  automaticallyCreated?: InputMaybe<Scalars['Boolean']['input']>;
   colour?: InputMaybe<EqualFilterStringInput>;
   comment?: InputMaybe<StringFilterInput>;
   createdDatetime?: InputMaybe<DatetimeFilterInput>;
@@ -7377,6 +7384,7 @@ export type RequisitionFilterInput = {
   expectedDeliveryDate?: InputMaybe<DateFilterInput>;
   finalisedDatetime?: InputMaybe<DatetimeFilterInput>;
   id?: InputMaybe<EqualFilterStringInput>;
+  isEmergency?: InputMaybe<Scalars['Boolean']['input']>;
   orderType?: InputMaybe<EqualFilterStringInput>;
   otherPartyId?: InputMaybe<EqualFilterStringInput>;
   otherPartyName?: InputMaybe<StringFilterInput>;
diff --git a/client/packages/common/src/ui/components/domain/Dashboard/StatsPanel.tsx b/client/packages/common/src/ui/components/domain/Dashboard/StatsPanel.tsx
index e0683d24fb7..d99a5899431 100644
--- a/client/packages/common/src/ui/components/domain/Dashboard/StatsPanel.tsx
+++ b/client/packages/common/src/ui/components/domain/Dashboard/StatsPanel.tsx
@@ -5,11 +5,13 @@ import { useTranslation } from '@common/intl';
 import { ApiException, isPermissionDeniedException } from '@common/types';
 import { SimpleLink } from '../../navigation/AppNavLink/SimpleLink';
 import { Grid } from '@openmsupply-client/common';
+import { StatusChip } from '../../panels/StatusChip';
 
 export type Stat = {
   label: string;
   value?: string;
   link?: string;
+  alertFlag?: boolean;
 };
 export interface StatsPanelProps {
   error?: ApiException;
@@ -19,48 +21,74 @@ export interface StatsPanelProps {
   title: string;
   width?: number;
   link?: string;
+  alertFlag?: boolean;
 }
 
-const Statistic: FC<Stat> = ({ label, value, link }) => {
+const Statistic: FC<Stat> = ({ label, value, link, alertFlag = false }) => {
   const t = useTranslation();
   return (
-    <Grid container alignItems="center" sx={{ marginTop: 1 }}>
-      <Grid
-        sx={{ minWidth: '43px', display: 'flex', justifyContent: 'flex-end' }}
-      >
-        {value ? (
-          <Typography
-            style={{ fontSize: 24, fontWeight: 'bold', lineHeight: 1.2 }}
-          >
-            {value}
-          </Typography>
-        ) : (
-          <Tooltip title={t('messages.no-data-available')}>
+    <Grid container flexDirection={'column'}>
+      <Grid container alignItems="center" sx={{ marginTop: 1 }}>
+        <Grid
+          sx={{ minWidth: '43px', display: 'flex', justifyContent: 'flex-end' }}
+        >
+          {value ? (
             <Typography
               style={{
-                cursor: 'help',
-                fontSize: 16,
+                fontSize: 24,
                 fontWeight: 'bold',
-                paddingTop: 8,
-                paddingBottom: 8,
+                lineHeight: 1.2,
               }}
             >
-              {t('messages.not-applicable')}
+              {value}
             </Typography>
-          </Tooltip>
-        )}
-      </Grid>
-      <Grid
-        sx={{
-          color: 'gray.main',
-          flex: 1,
-          fontSize: '12px',
-          fontWeight: 500,
-          marginInlineStart: '8px',
-        }}
-      >
-        {link ? <SimpleLink to={link}>{label}</SimpleLink> : label}
+          ) : (
+            <Tooltip title={t('messages.no-data-available')}>
+              <Typography
+                style={{
+                  cursor: 'help',
+                  fontSize: 16,
+                  fontWeight: 'bold',
+                  paddingTop: 8,
+                  paddingBottom: 8,
+                }}
+              >
+                {t('messages.not-applicable')}
+              </Typography>
+            </Tooltip>
+          )}
+        </Grid>
+        <Grid
+          sx={{
+            color: 'gray.main',
+            flex: 1,
+            fontSize: '12px',
+            fontWeight: 500,
+            marginInlineStart: '8px',
+          }}
+        >
+          {link ? <SimpleLink to={link}>{label}</SimpleLink> : label}
+        </Grid>
       </Grid>
+      {alertFlag && (
+        <Grid
+          container
+          alignItems="center"
+          sx={{
+            color: 'gray.main',
+            flex: 1,
+            fontSize: '12px',
+            fontWeight: 500,
+            marginInlineStart: '28px',
+          }}
+        >
+          <StatusChip
+            label={t('label.needs-attention')}
+            color={'red'}
+            typographySx={{ fontSize: '12px', fontWeight: 500 }}
+          />
+        </Grid>
+      )}
     </Grid>
   );
 };
diff --git a/client/packages/common/src/ui/components/panels/StatusChip.tsx b/client/packages/common/src/ui/components/panels/StatusChip.tsx
new file mode 100644
index 00000000000..8ef5ae3ee18
--- /dev/null
+++ b/client/packages/common/src/ui/components/panels/StatusChip.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import {
+  CircleIcon,
+  Box,
+  Typography,
+  SxProps,
+  Theme,
+} from '@openmsupply-client/common';
+
+interface StatusChipProps {
+  label: string | undefined;
+  color: string | undefined;
+  bgColor?: string;
+  typographySx?: SxProps<Theme> | undefined;
+}
+
+export const StatusChip = ({
+  label,
+  color,
+  bgColor,
+  typographySx,
+}: StatusChipProps) => {
+  if (!label) return null;
+  return (
+    <Box
+      sx={{ textAlign: 'center' }}
+      paddingY={0.1}
+      paddingX={0.5}
+      display="flex"
+      alignItems="center"
+      width="fit-content"
+      position="relative"
+    >
+      {/* If bgColor is not specified, we use a faded (low opacity) version of the dot color as the background */}
+      <Box
+        sx={{
+          backgroundColor: bgColor ?? color,
+          width: '100%',
+          height: '100%',
+          position: 'absolute',
+          left: 0,
+          top: 0,
+          borderRadius: 4,
+          opacity: bgColor ? 1 : 0.2,
+        }}
+      />
+      <CircleIcon sx={{ color, transform: 'scale(0.4)' }} />
+      <Typography sx={{ paddingRight: 1, zIndex: 1, ...typographySx }}>
+        {label}
+      </Typography>
+    </Box>
+  );
+};
diff --git a/client/packages/common/src/ui/layout/tables/components/Cells/StatusCell/StatusCell.tsx b/client/packages/common/src/ui/layout/tables/components/Cells/StatusCell/StatusCell.tsx
index 7ab237e4b30..ee276245daa 100644
--- a/client/packages/common/src/ui/layout/tables/components/Cells/StatusCell/StatusCell.tsx
+++ b/client/packages/common/src/ui/layout/tables/components/Cells/StatusCell/StatusCell.tsx
@@ -1,8 +1,8 @@
 import React from 'react';
 
 import { CellProps } from '../../../columns';
-import { Box, CircleIcon, Typography } from '@openmsupply-client/common';
 import { RecordWithId } from '@common/types';
+import { StatusChip } from '@openmsupply-client/common/src/ui/components/panels/StatusChip';
 
 interface StatusProps {
   statusMap: Record<
@@ -18,33 +18,5 @@ export const StatusCell = <T extends RecordWithId>({
 }: CellProps<T> & StatusProps) => {
   const { label, color, bgColor } =
     statusMap?.[String(column.accessor({ rowData }))] ?? {};
-
-  if (!label) return null;
-  return (
-    <Box
-      sx={{ textAlign: 'center' }}
-      paddingY={0.1}
-      paddingX={0.5}
-      display="flex"
-      alignItems="center"
-      width="fit-content"
-      position="relative"
-    >
-      {/* If bgColor is not specified, we use a faded (low opacity) version of the dot color as the background */}
-      <Box
-        sx={{
-          backgroundColor: bgColor ?? color,
-          width: '100%',
-          height: '100%',
-          position: 'absolute',
-          left: 0,
-          top: 0,
-          borderRadius: 4,
-          opacity: bgColor ? 1 : 0.2,
-        }}
-      />
-      <CircleIcon sx={{ color, transform: 'scale(0.4)' }} />
-      <Typography sx={{ paddingRight: 1, zIndex: 1 }}>{label}</Typography>
-    </Box>
-  );
+  return <StatusChip label={label} color={color} bgColor={bgColor} />;
 };
diff --git a/client/packages/dashboard/src/api/operations.generated.ts b/client/packages/dashboard/src/api/operations.generated.ts
index 41c1f7c81b0..0a33eba555e 100644
--- a/client/packages/dashboard/src/api/operations.generated.ts
+++ b/client/packages/dashboard/src/api/operations.generated.ts
@@ -47,6 +47,10 @@ export type RequisitionCountsQuery = {
     __typename: 'RequisitionCounts';
     request: { __typename: 'RequestRequisitionCounts'; draft: number };
     response: { __typename: 'ResponseRequisitionCounts'; new: number };
+    emergency: {
+      __typename: 'EmergencyResponseRequisitionCounts';
+      new: number;
+    };
   };
 };
 
@@ -129,6 +133,9 @@ export const RequisitionCountsDocument = gql`
       response {
         new
       }
+      emergency {
+        new
+      }
     }
   }
 `;
diff --git a/client/packages/dashboard/src/api/operations.graphql b/client/packages/dashboard/src/api/operations.graphql
index e129bb91a85..f69557b90c7 100644
--- a/client/packages/dashboard/src/api/operations.graphql
+++ b/client/packages/dashboard/src/api/operations.graphql
@@ -32,6 +32,9 @@ query requisitionCounts($storeId: String!) {
     response {
       new
     }
+    emergency {
+      new
+    }
   }
 }
 
diff --git a/client/packages/dashboard/src/widgets/DistributionWidget.tsx b/client/packages/dashboard/src/widgets/DistributionWidget.tsx
index a9404ef0817..9f9a5195231 100644
--- a/client/packages/dashboard/src/widgets/DistributionWidget.tsx
+++ b/client/packages/dashboard/src/widgets/DistributionWidget.tsx
@@ -116,6 +116,18 @@ export const DistributionWidget: React.FC = () => {
                     .addQuery({ status: RequisitionNodeStatus.New })
                     .build(),
                 },
+                {
+                  label: t('label.emergency'),
+                  value: formatNumber.round(requisitionCount?.emergency?.new),
+                  link: RouteBuilder.create(AppRoute.Distribution)
+                    .addPart(AppRoute.CustomerRequisition)
+                    .addQuery({ isEmergency: true })
+                    .addQuery({ status: RequisitionNodeStatus.New })
+                    .build(),
+                  alertFlag:
+                    !!requisitionCount?.emergency?.new &&
+                    requisitionCount?.emergency?.new > 0,
+                },
               ]}
               link={RouteBuilder.create(AppRoute.Distribution)
                 .addPart(AppRoute.CustomerRequisition)
diff --git a/client/packages/requisitions/src/ResponseRequisition/ListView/ListView.tsx b/client/packages/requisitions/src/ResponseRequisition/ListView/ListView.tsx
index 51a0ff60082..594207f096f 100644
--- a/client/packages/requisitions/src/ResponseRequisition/ListView/ListView.tsx
+++ b/client/packages/requisitions/src/ResponseRequisition/ListView/ListView.tsx
@@ -60,6 +60,10 @@ export const ResponseRequisitionListView: FC = () => {
         key: 'aShipmentHasBeenCreated',
         condition: '=',
       },
+      {
+        key: 'isEmergency',
+        condition: '=',
+      },
     ],
   });
   const pagination = { page, first, offset };
@@ -166,7 +170,6 @@ export const ResponseRequisitionListView: FC = () => {
           />
         }
       />
-
       <Footer />
     </>
   );
diff --git a/client/packages/requisitions/src/ResponseRequisition/ListView/Toolbar.tsx b/client/packages/requisitions/src/ResponseRequisition/ListView/Toolbar.tsx
index 639526f7346..9bd82b31284 100644
--- a/client/packages/requisitions/src/ResponseRequisition/ListView/Toolbar.tsx
+++ b/client/packages/requisitions/src/ResponseRequisition/ListView/Toolbar.tsx
@@ -48,6 +48,11 @@ export const Toolbar: FC<{
               name: t('label.shipment-created'),
               urlParameter: 'aShipmentHasBeenCreated',
             },
+            {
+              type: 'boolean',
+              name: t('label.emergency'),
+              urlParameter: 'isEmergency',
+            },
           ]}
         />
       </Box>
diff --git a/server/graphql/general/src/queries/requisition_counts.rs b/server/graphql/general/src/queries/requisition_counts.rs
index 225b01ceccb..c08995c3c21 100644
--- a/server/graphql/general/src/queries/requisition_counts.rs
+++ b/server/graphql/general/src/queries/requisition_counts.rs
@@ -17,6 +17,10 @@ pub struct RequestRequisitionCounts {
     store_id: String,
 }
 
+pub struct EmergencyResponseRequisitionCounts {
+    store_id: String,
+}
+
 #[Object]
 impl ResponseRequisitionCounts {
     async fn new(&self, ctx: &Context<'_>) -> Result<i64> {
@@ -45,6 +49,20 @@ impl RequestRequisitionCounts {
     }
 }
 
+#[Object]
+impl EmergencyResponseRequisitionCounts {
+    async fn new(&self, ctx: &Context<'_>) -> Result<i64> {
+        let service_provider = ctx.service_provider();
+        let service_ctx = service_provider.context(self.store_id.clone(), "".to_string())?;
+        let service = &service_provider.requisition_count_service;
+        let count = service
+            .new_emergency_response_requisition_count(&service_ctx, &self.store_id)
+            .map_err(StandardGraphqlError::from)?;
+
+        Ok(count)
+    }
+}
+
 #[Object]
 impl RequisitionCounts {
     async fn response(&self) -> ResponseRequisitionCounts {
@@ -58,6 +76,12 @@ impl RequisitionCounts {
             store_id: self.store_id.clone(),
         }
     }
+
+    async fn emergency(&self) -> EmergencyResponseRequisitionCounts {
+        EmergencyResponseRequisitionCounts {
+            store_id: self.store_id.clone(),
+        }
+    }
 }
 
 pub fn requisition_counts(ctx: &Context<'_>, store_id: String) -> Result<RequisitionCounts> {
diff --git a/server/graphql/requisition/src/query_tests/requisition.rs b/server/graphql/requisition/src/query_tests/requisition.rs
index a955f151803..a1eac68dd3d 100644
--- a/server/graphql/requisition/src/query_tests/requisition.rs
+++ b/server/graphql/requisition/src/query_tests/requisition.rs
@@ -193,6 +193,8 @@ mod graphql {
                 period_id: _,
                 elmis_code: _,
                 program_id: _,
+                is_emergency: _,
+                automatically_created: _,
             } = filter.unwrap();
 
             assert_eq!(id, Some(EqualFilter::not_equal_to("id_not_equal_to")));
diff --git a/server/graphql/requisition/src/query_tests/requisitions.rs b/server/graphql/requisition/src/query_tests/requisitions.rs
index f993346d924..af4e551627c 100644
--- a/server/graphql/requisition/src/query_tests/requisitions.rs
+++ b/server/graphql/requisition/src/query_tests/requisitions.rs
@@ -192,6 +192,8 @@ mod graphql {
                 period_id: _,
                 elmis_code: _,
                 program_id: _,
+                is_emergency: _,
+                automatically_created: _,
             } = filter.unwrap();
 
             assert_eq!(id, Some(EqualFilter::not_equal_to("id_not_equal_to")));
diff --git a/server/graphql/requisition/src/requisition_queries.rs b/server/graphql/requisition/src/requisition_queries.rs
index 4b1a7b85ca1..0d50eb941d2 100644
--- a/server/graphql/requisition/src/requisition_queries.rs
+++ b/server/graphql/requisition/src/requisition_queries.rs
@@ -79,6 +79,8 @@ pub struct RequisitionFilterInput {
     pub period_id: Option<EqualFilterStringInput>,
     pub program_id: Option<EqualFilterStringInput>,
     pub elmis_code: Option<EqualFilterStringInput>,
+    pub is_emergency: Option<bool>,
+    pub automatically_created: Option<bool>,
 }
 
 #[derive(Union)]
@@ -247,6 +249,8 @@ impl RequisitionFilterInput {
             elmis_code: self.elmis_code.map(EqualFilter::from),
             linked_requisition_id: None,
             store_id: None,
+            is_emergency: self.is_emergency,
+            automatically_created: self.automatically_created,
         }
     }
 }
diff --git a/server/repository/src/db_diesel/requisition/mod.rs b/server/repository/src/db_diesel/requisition/mod.rs
index b2d10b6806d..2875b2df44e 100644
--- a/server/repository/src/db_diesel/requisition/mod.rs
+++ b/server/repository/src/db_diesel/requisition/mod.rs
@@ -31,6 +31,8 @@ pub struct RequisitionFilter {
     pub period_id: Option<EqualFilter<String>>,
     pub elmis_code: Option<EqualFilter<String>>,
     pub program_id: Option<EqualFilter<String>>,
+    pub is_emergency: Option<bool>,
+    pub automatically_created: Option<bool>,
 }
 
 #[derive(PartialEq, Debug)]
@@ -169,6 +171,16 @@ impl RequisitionFilter {
         self.program_id = Some(filter);
         self
     }
+
+    pub fn is_emergency(mut self, filter: bool) -> Self {
+        self.is_emergency = Some(filter);
+        self
+    }
+
+    pub fn automatically_created(mut self, filter: bool) -> Self {
+        self.automatically_created = Some(filter);
+        self
+    }
 }
 
 impl RequisitionStatus {
diff --git a/server/repository/src/db_diesel/requisition/requisition.rs b/server/repository/src/db_diesel/requisition/requisition.rs
index 4f5a7b54d06..a1158d282f8 100644
--- a/server/repository/src/db_diesel/requisition/requisition.rs
+++ b/server/repository/src/db_diesel/requisition/requisition.rs
@@ -13,7 +13,7 @@ use crate::{
         apply_sort_no_case, apply_string_filter,
     },
     repository_error::RepositoryError,
-    DBType, NameLinkRow, NameRow, PeriodRow, ProgramRow, StorageConnection, StoreRow,
+    DBType, EqualFilter, NameLinkRow, NameRow, PeriodRow, ProgramRow, StorageConnection, StoreRow,
 };
 
 use crate::Pagination;
@@ -180,6 +180,8 @@ fn create_filtered_query(
         elmis_code,
         period_id,
         program_id,
+        is_emergency,
+        automatically_created,
     }) = filter
     {
         apply_equal_filter!(query, id, requisition::id);
@@ -214,6 +216,18 @@ fn create_filtered_query(
         apply_equal_filter!(query, period_id, period::id);
         apply_equal_filter!(query, program_id, program::id);
 
+        if let Some(is_emergency) = is_emergency {
+            query = query.filter(requisition::is_emergency.eq(is_emergency))
+        }
+
+        if let Some(value) = automatically_created {
+            apply_equal_filter!(
+                query,
+                Some(EqualFilter::is_null(value)),
+                requisition::linked_requisition_id
+            );
+        }
+
         if let Some(a_shipment_has_been_created) = a_shipment_has_been_created {
             let requisition_ids = invoice::table.select(invoice::requisition_id).into_boxed();
 
diff --git a/server/service/src/dashboard/requisition_count.rs b/server/service/src/dashboard/requisition_count.rs
index 0e2879b1493..18aed9cf1f4 100644
--- a/server/service/src/dashboard/requisition_count.rs
+++ b/server/service/src/dashboard/requisition_count.rs
@@ -21,6 +21,14 @@ pub trait RequisitionCountServiceTrait: Send + Sync {
     ) -> Result<i64, RepositoryError> {
         RequisitionCountService {}.draft_request_requisition_count(ctx, store_id)
     }
+
+    fn new_emergency_response_requisition_count(
+        &self,
+        ctx: &ServiceContext,
+        store_id: &str,
+    ) -> Result<i64, RepositoryError> {
+        RequisitionCountService {}.new_emergency_response_requisition_count(ctx, store_id)
+    }
 }
 
 pub struct RequisitionCountService {}
@@ -53,4 +61,19 @@ impl RequisitionCountServiceTrait for RequisitionCountService {
                 .status(RequisitionStatus::Draft.equal_to()),
         ))
     }
+
+    fn new_emergency_response_requisition_count(
+        &self,
+        ctx: &ServiceContext,
+        store_id: &str,
+    ) -> Result<i64, RepositoryError> {
+        let repo = RequisitionRepository::new(&ctx.connection);
+        repo.count(Some(
+            RequisitionFilter::new()
+                .store_id(EqualFilter::equal_to(store_id))
+                .r#type(RequisitionType::Response.equal_to())
+                .status(RequisitionStatus::New.equal_to())
+                .is_emergency(true),
+        ))
+    }
 }


Reviews:
user: login: fergie-nz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T05:12:26Z
user: login: fergie-nz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T05:15:54Z
user: login: fergie-nz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T05:17:37Z
user: login: jmbrunskill
body: Thanks @fergie-nz good implementation of the known requirements!

I've discussed with @mark-prins though and we've agreed we should simplify the dashboard view and the filters to just filter on the is_emergency status.

Adding the `Automatically created` filter is potentially a bit confusing for the users as like you pointed out it's not obvious from the user perspective that additional filters are being applied.

If we were to do a pop up notification in future, then we would need to filter out any emergency orders created manually (or potentially just created by the current user/store) as you don't want to create something and immediately have something pop up to tell you that you have an emergency order, in that scenario we will need addtional filters.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T23:12:12Z
user: login: fergie-nz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T00:08:06Z
user: login: jmbrunskill
body: Thanks Fergus much simplier and clearer now.
I'm wondering if it would be better to go back to just Emergency on the dashbaoad.

@mark-prins do you have an opinion?

![image](https://github.com/user-attachments/assets/c95e1c2e-4e31-427a-bc90-5841b9d0cee6)

vs

![image](https://github.com/user-attachments/assets/c2c40140-ffd0-492a-aedc-84ebb788ec28)



state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T00:26:56Z
user: login: jmbrunskill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T00:28:56Z
user: login: fergie-nz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T02:28:39Z
user: login: jmbrunskill
body: Thanks Fergus, looks great.

I was a big confused by the diff but opening in VScode made it all make more sense.
I also merged develop for you.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T23:52:45Z

Review comments:
diff_hunk: @@ -116,6 +116,16 @@ export const DistributionWidget: React.FC = () => {
                     .addQuery({ status: RequisitionNodeStatus.New })
                     .build(),
                 },
+                {
+                  label: t('label.emergency'),
path: client/packages/dashboard/src/widgets/DistributionWidget.tsx
user: login: fergie-nz
body: Have left this as emergency for succinctness, but maybe needs to be more specific to show that it is emergcy AND automatically created AND new? 
created_at: 2025-04-23T05:12:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -105,6 +105,17 @@ const responseParser = {
     requestedQuantity: patch.requestedQuantity,
     optionId: patch?.reason?.id ?? null,
   }),
+  toListFilter: (
+    filterBy: FilterByWithBoolean | null
+  ): FilterByWithBoolean | null => {
+    Object.assign(filterBy ?? {},  {type: { equalTo: RequisitionNodeType.Response }});
+    if (filterBy?.['isEmergencyAndAutomatic']) {
+      filterBy['isEmergency'] = filterBy['isEmergencyAndAutomatic'];
+      filterBy['automaticallyCreated'] = filterBy['isEmergencyAndAutomatic'];
+    }
path: client/packages/requisitions/src/ResponseRequisition/api/api.ts
user: login: fergie-nz
body: I couldn't see if we have a pattern for multiple filters combined by one toggle already, so have mapped here.

I wanted to add is_emergency as one filter, but it needed to be split to two filters (is_emergency & automatically_created) in the back end.

Maybe there is a better pattern?

Have also moved the type filter up to the parser so it is all in one place.
created_at: 2025-04-23T05:15:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -32,6 +32,9 @@ query requisitionCounts($storeId: String!) {
     response {
       new
     }
+    emergency {
+      new
+    }
path: client/packages/dashboard/src/api/operations.graphql
user: login: fergie-nz
body: Have added emergency query into other requisition count queries.

This seemed a lot more simple than adding it to its own hook - but maybe I'm not aware of a use case where it would need its own hook?
created_at: 2025-04-23T05:17:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1255,6 +1255,7 @@
   "label.year": "Year",
   "label.years-abbreviation": "Y",
   "label.your-email-address": "Your email address",
+  "label.emergency-and-automatic": "Emergency and automatic",
path: client/packages/common/src/intl/locales/en/common.json
user: login: jmbrunskill
body: I don't see this used anywhere?
created_at: 2025-04-30T21:21:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -105,6 +105,17 @@ const responseParser = {
     requestedQuantity: patch.requestedQuantity,
     optionId: patch?.reason?.id ?? null,
   }),
+  toListFilter: (
+    filterBy: FilterByWithBoolean | null
+  ): FilterByWithBoolean | null => {
+    Object.assign(filterBy ?? {},  {type: { equalTo: RequisitionNodeType.Response }});
+    if (filterBy?.['isEmergencyAndAutomatic']) {
+      filterBy['isEmergency'] = filterBy['isEmergencyAndAutomatic'];
+      filterBy['automaticallyCreated'] = filterBy['isEmergencyAndAutomatic'];
+    }
path: client/packages/requisitions/src/ResponseRequisition/api/api.ts
user: login: jmbrunskill
body: I'm thinking we should probably make this a backend filter, as it seems like it's always combined?
Technically I think this would work ok, however, if say we had a separate selection for `automaticallyCreated` this could overwrite or create some confusion. Right now we don't have that though so it's ok :)
created_at: 2025-04-30T21:32:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -116,6 +116,16 @@ export const DistributionWidget: React.FC = () => {
                     .addQuery({ status: RequisitionNodeStatus.New })
                     .build(),
                 },
+                {
+                  label: t('label.emergency'),
path: client/packages/dashboard/src/widgets/DistributionWidget.tsx
user: login: jmbrunskill
body: Have discussed with @mark-prins lets make the dashboard just use the emergency filter and don't worry about the automatically created for now.
created_at: 2025-04-30T22:44:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1986,5 +1986,7 @@
   "warning.check-before-vaccinating": "Vaccination records from other stores may not be up to date. Ensure you have synced recently, and always confirm with the patient first.",
   "warning.field-not-parsed": "{{field}} not parsed",
   "warning.nothing-to-supply": "Nothing left to supply!",
-  "tooltip.defined-by-gs1-matrix": "Defined by GS1 datamatrix"
+  "tooltip.defined-by-gs1-matrix": "Defined by GS1 datamatrix",
+  "label.emergency": "Emergency",
+  "label.new-emergency": "New emergency requisitions"
path: client/packages/common/src/intl/locales/en/common.json
user: login: fergie-nz
body: @jmbrunskill FYI have added a more verbose description on dashboard.

I thought this would make more sense because just having 'emergency' doesn't seem clear to me that it would also filter for new given that another category is explicitly called 'New'
created_at: 2025-05-01T00:08:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1986,5 +1986,7 @@
   "warning.check-before-vaccinating": "Vaccination records from other stores may not be up to date. Ensure you have synced recently, and always confirm with the patient first.",
   "warning.field-not-parsed": "{{field}} not parsed",
   "warning.nothing-to-supply": "Nothing left to supply!",
-  "tooltip.defined-by-gs1-matrix": "Defined by GS1 datamatrix"
+  "tooltip.defined-by-gs1-matrix": "Defined by GS1 datamatrix",
+  "label.emergency": "Emergency",
+  "label.new-emergency": "New emergency requisitions"
path: client/packages/common/src/intl/locales/en/common.json
user: login: jmbrunskill
body: I've updated this to "label.new-emergency-requisitions", will help with translations if we make the key more explicit when we're using it for a specific purpose.
created_at: 2025-05-01T00:28:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,53 @@
+import React from 'react';
path: client/packages/common/src/ui/components/panels/StatusChip.tsx
user: login: fergie-nz
body: I've moved the status cell component into panels. Wasn't quite sure where best to put it. But have moved it out of the cell component to expose just the chip.
created_at: 2025-05-08T02:28:39Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-23T05:14:50Z
body: <!-- GITHUB_ACTION_COMMENT_PR -->
## Bundle size difference
Comparing this PR to `main`

|Old size      | New size     | Diff                     |
|:------------ |:-------------|:-------------------------|
|6.06 MB         |6.06 MB         | 1.15 KB (0.02%) |

author_association: CONTRIBUTOR
user: login: jmbrunskill
created_at: 2025-05-01T00:30:51Z
body: @andreievg I found a bug testing this with the open-msupply-plugins enabled.
The query for requisitionCounts is re-defined in that plugin, and it seems like on a refresh of the page we get the version without the `emergency` total in the request, this is something we need to watch out for and potentially safe guard against...
author_association: CONTRIBUTOR
user: login: mark-prins
created_at: 2025-05-01T05:49:57Z
body: > do you have an opinion?

the second option, with just 'Emergency'

but also.. can we have an alert icon / different colour or something plz? this emergency really needs to pop
author_association: COLLABORATOR
user: login: fergie-nz
created_at: 2025-05-02T00:40:55Z
body: > Member

How does this look?

Shows as red when there is more than 0 emergency requisitions

![Screenshot 2025-05-02 at 12 40 21 PM](https://github.com/user-attachments/assets/adf6a398-b810-4f5f-b05f-0caa3405d122)

author_association: CONTRIBUTOR
user: login: mark-prins
created_at: 2025-05-05T05:47:11Z
body: was hoping for a more distinct difference - we have these status chips elsewhere (vax card) and it was proposed to have these on the dashboard:

<img width="508" alt="attention" src="https://github.com/user-attachments/assets/d5a312bc-2ac1-4b97-a3dd-7cc187336a33" />

can you add in a status chip pls? 
author_association: COLLABORATOR