= ({
diff --git a/client/packages/common/src/hooks/useDialog/useDialog.stories.tsx b/client/packages/common/src/hooks/useDialog/useDialog.stories.tsx
index 08d4ca63f5..5e8aabcf15 100644
--- a/client/packages/common/src/hooks/useDialog/useDialog.stories.tsx
+++ b/client/packages/common/src/hooks/useDialog/useDialog.stories.tsx
@@ -29,7 +29,7 @@ const Template: StoryFn = () => {
}
- nextButton={}
+ nextButton={}
okButton={}
>
This is an example dialog. There's nothing much to see
diff --git a/client/packages/common/src/intl/locales/en/common.json b/client/packages/common/src/intl/locales/en/common.json
index 4b741783f7..4dafb56594 100644
--- a/client/packages/common/src/intl/locales/en/common.json
+++ b/client/packages/common/src/intl/locales/en/common.json
@@ -67,11 +67,13 @@
"button.new-return": "New Return",
"button.new-shipment": "New Shipment",
"button.new-stock": "New Stock",
+ "button.next": "Next",
"button.next-step": "Next step",
"button.ok": "OK",
"button.ok-and-next": "OK & Next",
"button.open-the-menu": "Open the menu",
"button.order-more": "Order more",
+ "button.previous": "Previous",
"button.print": "Print",
"button.print-qr": "Print QR code",
"button.reduce-lines-to-zero": "Reduce lines to 0",
@@ -142,6 +144,7 @@
"customers": "Customers",
"dashboard": "Dashboard",
"description.already-issued": "Quantity already issued in shipments.",
+ "description.available-stock": "The customer's initial stock on hand + incoming stock +/- inventory adjustments - outgoing stock",
"description.average-monthly-consumption": "Average Monthly Consumption",
"description.breach-type": "Ongoing breach type",
"description.bundle-ratio": "The number of units of the bundled item for every unit of the base item",
@@ -157,7 +160,6 @@
"description.fc-sell-price": "Foreign currency sell price per pack",
"description.forecast-quantity": "Forecast Quantity to Reach Target",
"description.initial-stock-on-hand": "Stock on hand on the first day of the program period",
- "description.available-stock": "The customer's initial stock on hand + incoming stock +/- inventory adjustments - outgoing stock",
"description.invoice-number": "Shipment number",
"description.last-reading-datetime": "Date and time of the last reading",
"description.max-min-temperature": "Maximum or minimum temperature reading",
@@ -225,6 +227,7 @@
"error.date_minDate": "Date value is too low",
"error.dose-ages-out-of-order": "Each dose needs a higher minimum age than the previous dose",
"error.duplicate-asset-number": "duplicate asset number in csv",
+ "error.duplicate-item-variant-name": "Item variant with the same name already exists for this item",
"error.duplicated-field": "{{field}} already exists in the import file",
"error.encounter-not-created": "Unable to create encounter",
"error.encounter-not-found": "Encounter not found.",
@@ -241,7 +244,6 @@
"error.failed-to-save-item-variant": "Failed to save item variant",
"error.failed-to-save-service-charges": "Failed to save service charges",
"error.failed-to-save-vaccination": "Failed to save vaccination!",
- "error.duplicate-item-variant-name": "Item variant with the same name already exists for this item",
"error.failed-to-save-vaccine-course": "Failed to save vaccine course",
"error.fetch-notifications": "Unable to display cold chain notifications",
"error.field-must-be-specified": "{{field}} must be specified",
@@ -738,11 +740,11 @@
"label.invoice-number": "Number",
"label.is-vaccine": "Vaccine",
"label.issue": "Issue",
+ "label.item-variant": "Item Variant",
"label.item_few": "Items",
"label.item_many": "Items",
"label.item_one": "Item",
"label.item_other": "Items",
- "label.item-variant": "Item Variant",
"label.items-expiring-before": "Items expiring before",
"label.items-no-stock_few": "Items with no stock",
"label.items-no-stock_many": "Items with no stock",
@@ -1366,8 +1368,8 @@
"messages.new-sensor": "A new sensor has been added as a result of importing these logs. Would you like to assign a location to the new sensor now?",
"messages.no": "No",
"messages.no-available-periods": "No available periods found for schedule",
- "messages.no-bundled-items": "No bundled items configured",
"messages.no-batch-selected": "You have not selected a batch for this vaccination. No stock transaction will be created. Are you sure you want to continue?",
+ "messages.no-bundled-items": "No bundled items configured",
"messages.no-contact-traces": "This patient has no contact traces",
"messages.no-data-available": "No data available",
"messages.no-data-found": "No data found",
@@ -1634,8 +1636,8 @@
"text.months": "Months",
"title.adjustment-details": "Adjustment Details",
"title.amc": "AMC",
- "title.bundled-on": "Bundled on",
"title.bundle-with": "Bundle with",
+ "title.bundled-on": "Bundled on",
"title.categories": "Categories",
"title.confirm-delete-encounter": "Delete encounter",
"title.details": "Details",
diff --git a/client/packages/common/src/types/schema.ts b/client/packages/common/src/types/schema.ts
index 50ff60f2e2..b13b063d9f 100644
--- a/client/packages/common/src/types/schema.ts
+++ b/client/packages/common/src/types/schema.ts
@@ -94,6 +94,10 @@ export enum ActivityLogNodeType {
AssetLogReasonDeleted = 'ASSET_LOG_REASON_DELETED',
AssetPropertyCreated = 'ASSET_PROPERTY_CREATED',
AssetUpdated = 'ASSET_UPDATED',
+ DemographicIndicatorCreated = 'DEMOGRAPHIC_INDICATOR_CREATED',
+ DemographicIndicatorUpdated = 'DEMOGRAPHIC_INDICATOR_UPDATED',
+ DemographicProjectionCreated = 'DEMOGRAPHIC_PROJECTION_CREATED',
+ DemographicProjectionUpdated = 'DEMOGRAPHIC_PROJECTION_UPDATED',
InventoryAdjustment = 'INVENTORY_ADJUSTMENT',
InvoiceCreated = 'INVOICE_CREATED',
InvoiceDeleted = 'INVOICE_DELETED',
@@ -2969,21 +2973,9 @@ export type InsertResponseRequisitionLineErrorInterface = {
};
export type InsertResponseRequisitionLineInput = {
- additionInUnits?: InputMaybe;
- averageMonthlyConsumption?: InputMaybe;
- comment?: InputMaybe;
- daysOutOfStock?: InputMaybe;
- expiringUnits?: InputMaybe;
id: Scalars['String']['input'];
- incomingUnits?: InputMaybe;
itemId: Scalars['String']['input'];
- lossInUnits?: InputMaybe;
- optionId?: InputMaybe;
- outgoingUnits?: InputMaybe;
- requestedQuantity?: InputMaybe;
requisitionId: Scalars['String']['input'];
- stockOnHand?: InputMaybe;
- supplyQuantity?: InputMaybe;
};
export type InsertResponseRequisitionLineResponse = InsertResponseRequisitionLineError | RequisitionLineNode;
@@ -6510,6 +6502,7 @@ export type RequisitionLineNode = {
remainingQuantityToSupply: Scalars['Float']['output'];
/** Quantity requested */
requestedQuantity: Scalars['Float']['output'];
+ requisitionNumber: Scalars['Int']['output'];
/**
* Calculated quantity
* When months_of_stock < requisition.min_months_of_stock, calculated = average_monthly_consumption * requisition.max_months_of_stock - months_of_stock
diff --git a/client/packages/common/src/ui/components/buttons/ButtonShowcase.stories.tsx b/client/packages/common/src/ui/components/buttons/ButtonShowcase.stories.tsx
index f16816fc02..f3fb0700b1 100644
--- a/client/packages/common/src/ui/components/buttons/ButtonShowcase.stories.tsx
+++ b/client/packages/common/src/ui/components/buttons/ButtonShowcase.stories.tsx
@@ -115,7 +115,10 @@ const Template: StoryFn<{ color: 'primary' | 'secondary' }> = ({ color }) => {
-
+
diff --git a/client/packages/common/src/ui/components/buttons/standard/DialogButton.tsx b/client/packages/common/src/ui/components/buttons/standard/DialogButton.tsx
index d6dbbd76a5..b8559a57d5 100644
--- a/client/packages/common/src/ui/components/buttons/standard/DialogButton.tsx
+++ b/client/packages/common/src/ui/components/buttons/standard/DialogButton.tsx
@@ -15,6 +15,8 @@ import { ButtonWithIcon } from './ButtonWithIcon';
type DialogButtonVariant =
| 'cancel'
| 'back'
+ | 'previous'
+ | 'next-and-ok'
| 'next'
| 'ok'
| 'save'
@@ -63,12 +65,18 @@ const getButtonProps = (
labelKey: 'button.ok',
variant: 'contained',
};
- case 'next':
+ case 'next-and-ok':
return {
icon: ,
labelKey: 'button.ok-and-next',
variant: 'contained',
};
+ case 'next':
+ return {
+ icon: ,
+ labelKey: 'button.next',
+ variant: 'contained',
+ };
case 'save':
return {
icon: ,
@@ -99,6 +107,12 @@ const getButtonProps = (
labelKey: 'button.close',
variant: 'outlined',
};
+ case 'previous':
+ return {
+ icon: ,
+ labelKey: 'button.previous',
+ variant: 'outlined',
+ };
}
};
diff --git a/client/packages/common/src/ui/components/navigation/ListOptions/ListOptions.tsx b/client/packages/common/src/ui/components/navigation/ListOptions/ListOptions.tsx
new file mode 100644
index 0000000000..8f7665cc87
--- /dev/null
+++ b/client/packages/common/src/ui/components/navigation/ListOptions/ListOptions.tsx
@@ -0,0 +1,87 @@
+import { CheckIcon, ChevronDownIcon } from '@common/icons';
+import {
+ List,
+ ListItemIcon,
+ ListItem,
+ ListItemText,
+ Divider,
+ Box,
+} from '@mui/material';
+import React from 'react';
+
+export type ListOptionValues = {
+ id: string;
+ value: string;
+};
+
+interface ListProps {
+ onClick: (id: string) => void;
+ options: ListOptionValues[];
+ currentId?: string;
+ enteredLineIds?: string[];
+}
+
+export const ListOptions = ({
+ onClick,
+ options,
+ currentId,
+ enteredLineIds,
+}: ListProps) => {
+ const startIcon = (
+
+ );
+
+ const endIcon = (
+
+ );
+
+ return (
+
+ {options?.map((option, _) => (
+
+ onClick(option.id)}
+ >
+
+
+ {startIcon}
+
+
+
+
+
+ {endIcon}
+
+
+
+
+
+ ))}
+
+ );
+};
diff --git a/client/packages/common/src/ui/components/navigation/ListOptions/index.ts b/client/packages/common/src/ui/components/navigation/ListOptions/index.ts
new file mode 100644
index 0000000000..225a6a5b73
--- /dev/null
+++ b/client/packages/common/src/ui/components/navigation/ListOptions/index.ts
@@ -0,0 +1 @@
+export * from './ListOptions';
diff --git a/client/packages/common/src/ui/components/navigation/index.ts b/client/packages/common/src/ui/components/navigation/index.ts
index 4e8b74981d..1c37703ad9 100644
--- a/client/packages/common/src/ui/components/navigation/index.ts
+++ b/client/packages/common/src/ui/components/navigation/index.ts
@@ -3,3 +3,4 @@ export * from './AppNavSection';
export * from './Breadcrumbs';
export * from './ExternalNavLink';
export * from './Tabs';
+export * from './ListOptions';
diff --git a/client/packages/inventory/src/Stocktake/DetailView/modal/StocktakeLineEdit/StocktakeLineEditModal.tsx b/client/packages/inventory/src/Stocktake/DetailView/modal/StocktakeLineEdit/StocktakeLineEditModal.tsx
index ce4116bfc5..3b3e434d36 100644
--- a/client/packages/inventory/src/Stocktake/DetailView/modal/StocktakeLineEdit/StocktakeLineEditModal.tsx
+++ b/client/packages/inventory/src/Stocktake/DetailView/modal/StocktakeLineEdit/StocktakeLineEditModal.tsx
@@ -38,7 +38,7 @@ export const StocktakeLineEditModal: FC<
cancelButton={}
nextButton={
diff --git a/client/packages/invoices/src/InboundShipment/DetailView/modals/InboundLineEdit/InboundLineEdit.tsx b/client/packages/invoices/src/InboundShipment/DetailView/modals/InboundLineEdit/InboundLineEdit.tsx
index f6283441fb..45b49b807b 100644
--- a/client/packages/invoices/src/InboundShipment/DetailView/modals/InboundLineEdit/InboundLineEdit.tsx
+++ b/client/packages/invoices/src/InboundShipment/DetailView/modals/InboundLineEdit/InboundLineEdit.tsx
@@ -155,7 +155,7 @@ export const InboundLineEdit: FC = ({
cancelButton={}
nextButton={
{
await saveLines();
diff --git a/client/packages/invoices/src/OutboundShipment/DetailView/OutboundLineEdit/OutboundLineEdit.tsx b/client/packages/invoices/src/OutboundShipment/DetailView/OutboundLineEdit/OutboundLineEdit.tsx
index 0313bb54a8..e5a4a87be5 100644
--- a/client/packages/invoices/src/OutboundShipment/DetailView/OutboundLineEdit/OutboundLineEdit.tsx
+++ b/client/packages/invoices/src/OutboundShipment/DetailView/OutboundLineEdit/OutboundLineEdit.tsx
@@ -222,7 +222,7 @@ export const OutboundLineEdit: React.FC = ({
nextButton={
}
diff --git a/client/packages/invoices/src/Prescriptions/DetailView/PrescriptionLineEdit/PrescriptionLineEdit.tsx b/client/packages/invoices/src/Prescriptions/DetailView/PrescriptionLineEdit/PrescriptionLineEdit.tsx
index 9bcaeb3d30..ba0903e560 100644
--- a/client/packages/invoices/src/Prescriptions/DetailView/PrescriptionLineEdit/PrescriptionLineEdit.tsx
+++ b/client/packages/invoices/src/Prescriptions/DetailView/PrescriptionLineEdit/PrescriptionLineEdit.tsx
@@ -196,7 +196,7 @@ export const PrescriptionLineEdit: React.FC = ({
nextButton={
}
diff --git a/client/packages/invoices/src/Returns/modals/CustomerReturn/CustomerReturn.tsx b/client/packages/invoices/src/Returns/modals/CustomerReturn/CustomerReturn.tsx
index 2f881b1351..ac6d342ba8 100644
--- a/client/packages/invoices/src/Returns/modals/CustomerReturn/CustomerReturn.tsx
+++ b/client/packages/invoices/src/Returns/modals/CustomerReturn/CustomerReturn.tsx
@@ -120,7 +120,7 @@ export const CustomerReturnEditModal = ({
const NextStepButton = (
@@ -129,7 +129,7 @@ export const CustomerReturnEditModal = ({
const OkAndNextButton = (
@@ -127,7 +127,7 @@ export const SupplierReturnEditModal = ({
const OkAndNextButton = (
{
await save();
setPreviousItemLineId(null);
diff --git a/client/packages/requisitions/src/RequisitionService.tsx b/client/packages/requisitions/src/RequisitionService.tsx
index 181aa9c1db..8d776808b6 100644
--- a/client/packages/requisitions/src/RequisitionService.tsx
+++ b/client/packages/requisitions/src/RequisitionService.tsx
@@ -7,6 +7,7 @@ import {
import {
ListView as ResponseRequisitionListView,
DetailView as ResponseRequisitionDetailView,
+ ResponseLineEditPage,
} from './ResponseRequisition';
import { RouteBuilder, Routes, Route } from '@openmsupply-client/common';
import { AppRoute } from '@openmsupply-client/config';
@@ -21,6 +22,13 @@ const customerRequisitionRoute = RouteBuilder.create(
.addPart(':requisitionNumber')
.build();
+const customerRequisitionLineRoute = RouteBuilder.create(
+ AppRoute.CustomerRequisition
+)
+ .addPart(':requisitionNumber')
+ .addPart(':itemId')
+ .build();
+
const internalOrdersRoute = RouteBuilder.create(AppRoute.InternalOrder).build();
const internalOrderRoute = RouteBuilder.create(AppRoute.InternalOrder)
.addPart(':requisitionNumber')
@@ -43,6 +51,10 @@ export const RequisitionService: FC = () => {
path={customerRequisitionRoute}
element={}
/>
+ }
+ />
}
diff --git a/client/packages/requisitions/src/ResponseRequisition/DetailView/AppBarButtons/AppBarButtons.tsx b/client/packages/requisitions/src/ResponseRequisition/DetailView/AppBarButtons/AppBarButtons.tsx
index 88a2c97e61..16d0d2ddc1 100644
--- a/client/packages/requisitions/src/ResponseRequisition/DetailView/AppBarButtons/AppBarButtons.tsx
+++ b/client/packages/requisitions/src/ResponseRequisition/DetailView/AppBarButtons/AppBarButtons.tsx
@@ -1,8 +1,10 @@
import React from 'react';
import {
AppBarButtonsPortal,
+ ButtonWithIcon,
Grid,
LoadingButton,
+ PlusCircleIcon,
PrinterIcon,
ReportContext,
useDetailPanel,
@@ -17,11 +19,24 @@ import { SupplyRequestedQuantityButton } from './SupplyRequestedQuantityButton';
import { useResponse } from '../../api';
import { JsonData } from '@openmsupply-client/programs';
-export const AppBarButtonsComponent = () => {
+interface AppBarButtonProps {
+ isDisabled: boolean;
+ hasLinkedRequisition: boolean;
+ isProgram: boolean;
+ onAddItem: (newState: boolean) => void;
+}
+
+export const AppBarButtonsComponent = ({
+ isDisabled,
+ hasLinkedRequisition,
+ isProgram,
+ onAddItem,
+}: AppBarButtonProps) => {
+ const t = useTranslation();
const { OpenButton } = useDetailPanel();
const { data } = useResponse.document.get();
const { print, isPrinting } = usePrintReport();
- const t = useTranslation();
+ const disableAddButton = isDisabled || isProgram || hasLinkedRequisition;
const printReport = (
report: ReportRowFragment,
@@ -34,6 +49,13 @@ export const AppBarButtonsComponent = () => {
return (
+ }
+ onClick={() => onAddItem(true)}
+ />
+
void;
onRowClick: null | ((line: ResponseLineFragment) => void);
+ disableAddLine: boolean;
}
-export const ContentArea = ({ onRowClick }: ContentAreaProps) => {
+export const ContentArea = ({
+ onRowClick,
+ onAddItem,
+ disableAddLine,
+}: ContentAreaProps) => {
+ const t = useTranslation();
const { columns, lines } = useResponse.line.list();
return (
@@ -15,7 +26,12 @@ export const ContentArea = ({ onRowClick }: ContentAreaProps) => {
onRowClick={onRowClick}
columns={columns}
data={lines}
- noDataElement={}
+ noDataElement={
+
+ }
/>
);
};
diff --git a/client/packages/requisitions/src/ResponseRequisition/DetailView/DetailView.tsx b/client/packages/requisitions/src/ResponseRequisition/DetailView/DetailView.tsx
index 23ab4b0ac5..6323205c57 100644
--- a/client/packages/requisitions/src/ResponseRequisition/DetailView/DetailView.tsx
+++ b/client/packages/requisitions/src/ResponseRequisition/DetailView/DetailView.tsx
@@ -10,37 +10,55 @@ import {
useEditModal,
createQueryParamsStore,
DetailTabs,
+ BasicModal,
+ Box,
+ FnUtils,
} from '@openmsupply-client/common';
import { AppRoute } from '@openmsupply-client/config';
-import { ActivityLogList } from '@openmsupply-client/system';
+import {
+ ActivityLogList,
+ ItemRowFragment,
+ StockItemSearchInput,
+} from '@openmsupply-client/system';
import { Toolbar } from './Toolbar/Toolbar';
import { Footer } from './Footer';
import { AppBarButtons } from './AppBarButtons';
import { SidePanel } from './SidePanel';
import { ContentArea } from './ContentArea';
import { useResponse, ResponseLineFragment } from '../api';
-import { ResponseLineEdit } from './ResponseLineEdit';
export const DetailView: FC = () => {
- const isDisabled = useResponse.utils.isDisabled();
- const { onOpen, onClose, entity, isOpen } =
- useEditModal();
- const { data, isLoading } = useResponse.document.get();
- const navigate = useNavigate();
const t = useTranslation();
+ const navigate = useNavigate();
+ const { data, isLoading } = useResponse.document.get();
+ const isDisabled = useResponse.utils.isDisabled();
+ const { onOpen, isOpen, onClose } = useEditModal();
+ const { mutateAsync } = useResponse.line.insert();
- const onRowClick = useCallback(
- (line: ResponseLineFragment) => {
- onOpen(line);
- },
- [onOpen]
- );
+ const onRowClick = useCallback((line: ResponseLineFragment) => {
+ navigate(
+ RouteBuilder.create(AppRoute.Distribution)
+ .addPart(AppRoute.CustomerRequisition)
+ .addPart(String(line.requisitionNumber))
+ .addPart(String(line.item.id))
+ .build(),
+ { replace: true }
+ );
+ }, []);
if (isLoading) return ;
const tabs = [
{
- Component: ,
+ Component: (
+ onOpen(null)}
+ onRowClick={!isDisabled ? onRowClick : null}
+ disableAddLine={
+ isDisabled || !!data?.linkedRequisition || !!data?.programName
+ }
+ />
+ ),
value: 'Details',
},
{
@@ -56,14 +74,45 @@ export const DetailView: FC = () => {
initialSortBy: { key: 'itemName' },
})}
>
-
+ onOpen(null)}
+ />
- {entity && (
-
+ {isOpen && (
+
+
+ {
+ if (newItem) {
+ mutateAsync({
+ id: FnUtils.generateUUID(),
+ requisitionId: data.id,
+ itemId: newItem.id,
+ });
+ navigate(
+ RouteBuilder.create(AppRoute.Distribution)
+ .addPart(AppRoute.CustomerRequisition)
+ .addPart(String(data.requisitionNumber))
+ .addPart(String(newItem.id))
+ .build(),
+ { replace: true }
+ );
+ }
+ }}
+ openOnFocus={true}
+ extraFilter={item =>
+ !data.lines.nodes.some(line => line.item.id === item.id)
+ }
+ />
+
+
)}
) : (
diff --git a/client/packages/requisitions/src/ResponseRequisition/DetailView/ResponseLineEdit/Footer.tsx b/client/packages/requisitions/src/ResponseRequisition/DetailView/ResponseLineEdit/Footer.tsx
new file mode 100644
index 0000000000..164e597939
--- /dev/null
+++ b/client/packages/requisitions/src/ResponseRequisition/DetailView/ResponseLineEdit/Footer.tsx
@@ -0,0 +1,79 @@
+import React, { FC } from 'react';
+import {
+ Box,
+ AppFooterPortal,
+ DialogButton,
+ RouteBuilder,
+ useNavigate,
+} from '@openmsupply-client/common';
+import { ItemRowFragment } from 'packages/system/src';
+import { AppRoute } from 'packages/config/src';
+
+interface FooterProps {
+ hasNext: boolean;
+ next: ItemRowFragment | null;
+ hasPrevious: boolean;
+ previous: ItemRowFragment | null;
+ requisitionNumber?: number;
+}
+
+export const Footer: FC = ({
+ hasNext,
+ next,
+ hasPrevious,
+ previous,
+ requisitionNumber,
+}) => {
+ const navigate = useNavigate();
+
+ return (
+
+
+
+ navigate(
+ RouteBuilder.create(AppRoute.Distribution)
+ .addPart(AppRoute.CustomerRequisition)
+ .addPart(String(requisitionNumber))
+ .addPart(String(previous?.id))
+ .build(),
+ { replace: true }
+ )
+ }
+ />
+
+ navigate(
+ RouteBuilder.create(AppRoute.Distribution)
+ .addPart(AppRoute.CustomerRequisition)
+ .addPart(String(requisitionNumber))
+ .addPart(String(next?.id))
+ .build(),
+ { replace: true }
+ )
+ }
+ />
+
+
+ }
+ />
+ );
+};
diff --git a/client/packages/requisitions/src/ResponseRequisition/DetailView/ResponseLineEdit/ResponseEditPage.tsx b/client/packages/requisitions/src/ResponseRequisition/DetailView/ResponseLineEdit/ResponseEditPage.tsx
new file mode 100644
index 0000000000..065f95131e
--- /dev/null
+++ b/client/packages/requisitions/src/ResponseRequisition/DetailView/ResponseLineEdit/ResponseEditPage.tsx
@@ -0,0 +1,99 @@
+import React, { useEffect } from 'react';
+import {
+ BasicSpinner,
+ DetailContainer,
+ Grid,
+ NothingHere,
+ RouteBuilder,
+ useBreadcrumbs,
+ useParams,
+} from 'packages/common/src';
+import { useResponse } from '../../api';
+import { ListItems } from 'packages/system/src';
+import { ResponseLineEdit } from './ResponseLineEdit';
+import { AppRoute } from 'packages/config/src';
+import { useDraftRequisitionLine, usePreviousNextResponseLine } from './hooks';
+
+interface ResponseLineEditFormLayoutProps {
+ Left: React.ReactElement;
+ Right: React.ReactElement;
+}
+
+const ResponseLineEditFormLayout = ({
+ Left,
+ Right,
+}: ResponseLineEditFormLayoutProps) => {
+ return (
+
+
+ {Left}
+
+
+ {Right}
+
+
+ );
+};
+
+export const ResponseLineEditPage = () => {
+ const { data, isLoading } = useResponse.document.get();
+ const lines =
+ data?.lines.nodes.sort((a, b) => a.item.name.localeCompare(b.item.name)) ??
+ [];
+ const { itemId } = useParams();
+ const currentItem = lines.find(l => l.item.id === itemId)?.item;
+ const { setCustomBreadcrumbs } = useBreadcrumbs();
+ const { draft, update, save } = useDraftRequisitionLine(currentItem);
+ const { hasNext, next, hasPrevious, previous } = usePreviousNextResponseLine(
+ lines,
+ currentItem
+ );
+
+ const enteredLineIds = lines
+ .filter(line => line.supplyQuantity !== 0)
+ .map(line => line.item.id);
+
+ useEffect(() => {
+ setCustomBreadcrumbs({
+ 2: currentItem?.name || '',
+ });
+ }, [currentItem]);
+
+ if (isLoading) return ;
+ if (!data || !currentItem) return ;
+
+ return (
+
+
+ l.item)}
+ route={RouteBuilder.create(AppRoute.Distribution)
+ .addPart(AppRoute.CustomerRequisition)
+ .addPart(String(data?.requisitionNumber))}
+ enteredLineIds={enteredLineIds}
+ />
+ >
+ }
+ Right={
+ <>
+
+ >
+ }
+ />
+
+ );
+};
diff --git a/client/packages/requisitions/src/ResponseRequisition/DetailView/ResponseLineEdit/ResponseLineEdit.tsx b/client/packages/requisitions/src/ResponseRequisition/DetailView/ResponseLineEdit/ResponseLineEdit.tsx
index 19e77ffd68..2d5471dd05 100644
--- a/client/packages/requisitions/src/ResponseRequisition/DetailView/ResponseLineEdit/ResponseLineEdit.tsx
+++ b/client/packages/requisitions/src/ResponseRequisition/DetailView/ResponseLineEdit/ResponseLineEdit.tsx
@@ -1,117 +1,306 @@
import React from 'react';
+import { useTranslation } from '@common/intl';
import {
- useDialog,
- DialogButton,
- BasicSpinner,
- useBufferState,
- ModalTabs,
- useKeyboardHeightAdjustment,
+ ItemRowFragment,
+ ReasonOptionsSearchInput,
+} from '@openmsupply-client/system';
+import { DraftResponseLine } from './hooks';
+import {
+ Box,
+ InputWithLabelRow,
+ NumericTextInput,
+ ReasonOptionNodeType,
} from '@openmsupply-client/common';
-import { ResponseLineEditForm } from './ResponseLineEditForm';
-import { useResponse, ResponseLineFragment } from '../../api';
-import { useDraftRequisitionLine, useNextResponseLine } from './hooks';
-import { RequestStoreStats } from '../ReponseStats/RequestStoreStats';
-import { ResponseStoreStats } from '../ReponseStats/ResponseStoreStats';
+import { Footer } from './Footer';
+const INPUT_WIDTH = 100;
+const LABEL_WIDTH = '150px';
interface ResponseLineEditProps {
- isOpen: boolean;
- onClose: () => void;
- line: ResponseLineFragment;
+ item?: ItemRowFragment | null;
+ hasLinkedRequisition?: boolean | undefined;
+ draft?: DraftResponseLine | null;
+ update: (patch: Partial) => void;
+ save?: () => void;
+ hasNext: boolean;
+ next: ItemRowFragment | null;
+ hasPrevious: boolean;
+ previous: ItemRowFragment | null;
+ isProgram: boolean;
}
export const ResponseLineEdit = ({
- isOpen,
- onClose,
- line,
+ hasLinkedRequisition,
+ draft,
+ update,
+ save,
+ hasNext,
+ next,
+ hasPrevious,
+ previous,
+ isProgram,
}: ResponseLineEditProps) => {
- const [currentLine, setCurrentLine] = useBufferState(line);
- const isDisabled = useResponse.utils.isDisabled();
- const { Modal } = useDialog({ onClose, isOpen });
- const { draft, isLoading, save, update } =
- useDraftRequisitionLine(currentLine);
- const { next, hasNext } = useNextResponseLine(currentLine);
- const { data } = useResponse.line.stats(draft?.id);
+ const t = useTranslation();
- const height = useKeyboardHeightAdjustment(600);
+ const incomingStock =
+ (draft?.incomingUnits ?? 0) + (draft?.additionInUnits ?? 0);
+ const outgoingStock = (draft?.lossInUnits ?? 0) + (draft?.outgoingUnits ?? 0);
- const tabs = [
- {
- Component: (
-
- ),
- value: 'label.my-store',
- },
- {
- Component: (
-
- ),
- value: 'label.customer',
- },
- ];
+ const available =
+ (draft?.initialStockOnHandUnits ?? 0) + incomingStock - outgoingStock;
+
+ const MOS =
+ draft?.averageMonthlyConsumption !== 0
+ ? available / (draft?.averageMonthlyConsumption ?? 1)
+ : 0;
return (
- }
- nextButton={
- {
- await save();
- next && setCurrentLine(next);
- // Returning true triggers the animation/slide out
- return true;
- }}
- />
- }
- okButton={
- {
- await save();
- onClose();
- }}
- />
- }
- height={height}
- width={1024}
- >
- {!isLoading ? (
- <>
-
+
+
+ {/* Left column content */}
+ {!isProgram ? (
+ update({ availableStockOnHand: value })}
+ onBlur={save}
+ disabled={!!hasLinkedRequisition}
+ />
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.stock-on-hand')}
+ sx={{ marginBottom: 1 }}
+ />
+ ) : (
+ update({ initialStockOnHandUnits: value })}
+ onBlur={save}
+ disabled={!!hasLinkedRequisition}
+ />
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.initial-stock-on-hand')}
+ sx={{ marginBottom: 1 }}
+ />
+ )}
+ update({ incomingUnits: value })}
+ onBlur={save}
+ />
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.incoming-stock')}
+ sx={{ marginBottom: 1 }}
+ />
+ update({ outgoingUnits: value })}
+ onBlur={save}
+ />
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.outgoing')}
+ sx={{ marginBottom: 1 }}
+ />
+ update({ lossInUnits: value })}
+ onBlur={save}
+ />
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.losses')}
+ sx={{ marginBottom: 1 }}
+ />
+ update({ additionInUnits: value })}
+ onBlur={save}
+ />
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.additions')}
+ sx={{ marginBottom: 1 }}
+ />
+ update({ expiringUnits: value })}
+ onBlur={save}
+ />
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.short-expiry')}
+ sx={{ marginBottom: 1 }}
+ />
+ update({ daysOutOfStock: value })}
+ onBlur={save}
+ />
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.days-out-of-stock')}
+ sx={{ marginBottom: 1 }}
/>
- theme.shadows[2],
- }}
+ update({ averageMonthlyConsumption: value })}
+ onBlur={save}
+ disabled={!!hasLinkedRequisition}
+ />
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.amc')}
+ sx={{ marginBottom: 1 }}
/>
- >
- ) : (
-
- )}
-
+
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.months-of-stock')}
+ />
+
+
+ {/* Right column content */}
+ update({ requestedQuantity: value })}
+ disabled={!!hasLinkedRequisition}
+ />
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.requested-quantity')}
+ sx={{ marginBottom: 1 }}
+ />
+
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.available')}
+ sx={{ marginBottom: 1 }}
+ />
+
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.already-issued')}
+ sx={{ marginBottom: 1 }}
+ />
+
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.suggested-quantity')}
+ sx={{ marginBottom: 1 }}
+ />
+
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.remaining-to-supply')}
+ sx={{ marginBottom: 1 }}
+ />
+ update({ supplyQuantity: value })}
+ onBlur={save}
+ />
+ }
+ labelWidth={LABEL_WIDTH}
+ label={t('label.supply-quantity')}
+ sx={{ marginBottom: 1 }}
+ />
+
+ {
+ update({ reason: value });
+ }}
+ width={200}
+ type={ReasonOptionNodeType.RequisitionLineVariance}
+ isDisabled={
+ draft?.requestedQuantity === draft?.suggestedQuantity ||
+ !!hasLinkedRequisition
+ }
+ onBlur={save}
+ />
+ }
+ labelWidth={'60px'}
+ label={t('label.reason')}
+ />
+
+
+
+
+
+
);
};
diff --git a/client/packages/requisitions/src/ResponseRequisition/DetailView/ResponseLineEdit/ResponseLineEditForm.tsx b/client/packages/requisitions/src/ResponseRequisition/DetailView/ResponseLineEdit/ResponseLineEditForm.tsx
deleted file mode 100644
index 5477047ec8..0000000000
--- a/client/packages/requisitions/src/ResponseRequisition/DetailView/ResponseLineEdit/ResponseLineEditForm.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import React from 'react';
-import {
- Box,
- Grid,
- InputWithLabelRow,
- NumericTextInput,
- TextArea,
- Tooltip,
- Typography,
- useTranslation,
-} from '@openmsupply-client/common';
-import { DraftResponseLine } from './hooks';
-
-interface ResponseLineEditFormProps {
- disabled: boolean;
- update: (patch: Partial) => void;
- draftLine: DraftResponseLine;
-}
-
-const InfoRow = ({ label, value }: { label: string; value: string }) => {
- return (
-
-
-
- {label}
-
-
-
-
- {value}
-
-
-
- );
-};
-
-interface ResponseLineEditFormLayoutProps {
- Left: React.ReactElement;
- Middle: React.ReactElement;
- Right: React.ReactElement;
-}
-
-export const ResponseLineEditFormLayout = ({
- Left,
- Middle,
- Right,
-}: ResponseLineEditFormLayoutProps) => {
- return (
-
-
- {Left}
-
-
- {Middle}
-
-
- {Right}
-
-
- );
-};
-
-export const ResponseLineEditForm = ({
- disabled,
- update,
- draftLine,
-}: ResponseLineEditFormProps) => {
- const t = useTranslation();
- const supplyQuantity = draftLine.supplyQuantity ?? 0;
-
- const { item } = draftLine;
-
- return (
-
-
- {t('heading.stock-details')}
-
-
-
-
-
-
-
- {item.unitName ? (
-
- ) : null}
- >
- }
- Middle={
- <>
-
- {t('heading.order')}
-
-
- }
- labelWidth="150px"
- labelProps={{ sx: { fontWeight: 500 } }}
- label={t('label.requested-quantity')}
- />
-
- update({
- supplyQuantity: q,
- })
- }
- />
- }
- labelWidth="150px"
- labelProps={{ sx: { fontWeight: 500 } }}
- label={t('label.supply-quantity')}
- />
- >
- }
- Right={
- <>
-
- {t('heading.comment')}
-
-