Skip to content

Commit 01ad9b4

Browse files
authored
Merge pull request #6330 from msupply-foundation/5675.2-link-requisition-modal
5675.2 link requisition modal
2 parents e35b319 + 451d9f1 commit 01ad9b4

File tree

11 files changed

+300
-32
lines changed

11 files changed

+300
-32
lines changed

client/packages/common/src/intl/locales/en/common.json

+8-6
Original file line numberDiff line numberDiff line change
@@ -277,16 +277,17 @@
277277
"error.login-support": "Please contact [email protected] if you are unable to login to your account",
278278
"error.manufacturer-model-unique": "An item already exists with this manufacturer and model",
279279
"error.master-list-not-found": "Master list not found",
280+
"error.max-orders-reached-for-period": "Maximum requisitions for this period already created",
280281
"error.missing-central-sync": "Could not reach mSupply central server",
281282
"error.missing-inputs": "{{count}}",
282283
"error.more-info": "More information",
283284
"error.name-program-duplicate": "Vaccine course name already exists for this program",
284285
"error.no-asset-create-permission": "You do not have permission to create a new asset.",
285286
"error.no-asset-edit-permission": "You do not have permission to edit assets.",
286287
"error.no-asset-view-permission": "You do not have permission to view assets.",
288+
"error.no-create-outbound-shipment-permission": "You do not have permission to create an Outbound Shipment from a Requisition",
287289
"error.no-customer-return-items": "No items have been added to this return.",
288290
"error.no-customer-returns": "There are no Customer Returns to display.",
289-
"error.no-create-outbound-shipment-permission": "You do not have permission to create an Outbound Shipment from a Requisition",
290291
"error.no-data": "No data available",
291292
"error.no-immunisation-programs": "No Immunization programs found",
292293
"error.no-inbound-items": "No items have been added to this shipment.",
@@ -375,6 +376,7 @@
375376
"error.unable-to-connect-to-printer": "Unable to connect to printer.",
376377
"error.unable-to-create-cce": "Unable to create CCE",
377378
"error.unable-to-create-immunisation-program": "Unable to create Immunisation Program",
379+
"error.unable-to-create-requisition": "Unable to creare requisition",
378380
"error.unable-to-detect-scanner": "Unable to detect a scanner",
379381
"error.unable-to-initialise": "Unable to initialise",
380382
"error.unable-to-insert-vaccine-course": "Unable to insert vaccine course",
@@ -399,8 +401,6 @@
399401
"error.v6-server-not-configured-hint": "Check the central server URL",
400402
"error.vaccine-course-update-failed": "Vaccine course failed to save",
401403
"error.value-type-not-correct": "Value type not correct",
402-
"error.unable-to-create-requisition": "Unable to creare requisition",
403-
"error.max-orders-reached-for-period": "Maximum requisitions for this period already created",
404404
"facilities": "Facilities",
405405
"filename.asset-categories": "asset-categories",
406406
"filename.asset-import-example": "Example Asset Item Import",
@@ -422,6 +422,7 @@
422422
"filename.stocktakes": "stocktakes",
423423
"filename.supplier-returns": "supplier-returns",
424424
"format.comment": "Acknowledged by {{name}} on {{date}}: {{comment}}.",
425+
"header.link-internal-order": "Link Internal Order",
425426
"heading.404": "Feeling lost?",
426427
"heading.acknowledgeBreach": "Acknowledge Breach",
427428
"heading.actions": "Actions",
@@ -597,8 +598,8 @@
597598
"label.calculated-demand": "Calculated Demand",
598599
"label.cant-change-location": "Can only change location of lines when status is New",
599600
"label.cant-delete-disabled": "Can only delete lines when status is New",
600-
"label.cant-delete-disabled-requisition": "Can only delete lines when requisition is in Draft",
601601
"label.cant-delete-disabled-internal-order": "Can only delete lines when internal order is in Draft",
602+
"label.cant-delete-disabled-requisition": "Can only delete lines when requisition is in Draft",
602603
"label.cant-zero-quantity-disabled": "Quantities of lines can only be set to 0 when status is New",
603604
"label.cant-zero-stock-lines-disabled": "Quantites of lines can only be reduced to 0 when status is New",
604605
"label.catalogue-item": "Catalogue item",
@@ -753,6 +754,7 @@
753754
"label.forecast-quantity": "Suggested Quantity",
754755
"label.from-age": "From age",
755756
"label.from-created-datetime": "From created date / time",
757+
"label.from-date": "From date",
756758
"label.from-datetime": "From date / time",
757759
"label.from-expiry": "From expiry",
758760
"label.from-start-datetime": "From start date / time",
@@ -1063,10 +1065,9 @@
10631065
"label.time": "Time",
10641066
"label.to-age": "To age",
10651067
"label.to-created-datetime": "To created date / time",
1068+
"label.to-date": "To date",
10661069
"label.to-datetime": "To date / time",
10671070
"label.to-expiry": "To expiry",
1068-
"label.from-date": "From date",
1069-
"label.to-date": "To date",
10701071
"label.to-start-datetime": "To start date / time",
10711072
"label.today": "Today",
10721073
"label.toggle-password-visibility": "Toggle password visibility",
@@ -1199,6 +1200,7 @@
11991200
"message.confirm-delete-encounter": "Are you sure?",
12001201
"message.confirm-save-new": "Click OK to save the new item, or Cancel to continue editing",
12011202
"message.contact-support": "Please contact [email protected] about configuring reports",
1203+
"message.continue-to-make-inbound-shipment": "Click Next to continue to make an Inbound Shipment without linking an Internal Order.",
12021204
"message.copy-success": "Copied to clipboard successfully",
12031205
"message.database-not-local": "Database is running on a server, so can't be downloaded here",
12041206
"message.database-not-sqlite": "Database download is only available for SQLite databases",

client/packages/invoices/src/InboundShipment/ListView/AppBarButtons.tsx

+80-23
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FC } from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import { AppRoute } from '@openmsupply-client/config';
33
import {
44
FnUtils,
@@ -16,23 +16,38 @@ import {
1616
EnvUtils,
1717
useNavigate,
1818
RouteBuilder,
19+
useAuthContext,
1920
} from '@openmsupply-client/common';
20-
import { SupplierSearchModal } from '@openmsupply-client/system';
21-
import { useInbound } from '../api';
21+
import {
22+
NameRowFragment,
23+
SupplierSearchModal,
24+
} from '@openmsupply-client/system';
25+
import { LinkedRequestRowFragment, useInbound } from '../api';
2226
import { inboundsToCsv } from '../../utils';
27+
import { LinkInternalOrderModal } from './LinkInternalOrderModal';
2328

24-
export const AppBarButtons: FC<{
25-
modalController: ToggleState;
26-
}> = ({ modalController }) => {
29+
export const AppBarButtons = ({
30+
invoiceModalController,
31+
linkRequestModalController,
32+
}: {
33+
invoiceModalController: ToggleState;
34+
linkRequestModalController: ToggleState;
35+
}) => {
2736
const t = useTranslation();
2837
const navigate = useNavigate();
29-
const { mutateAsync: onCreate } = useInbound.document.insert();
3038
const { success, error } = useNotification();
39+
const { store } = useAuthContext();
40+
const [name, setName] = useState<NameRowFragment | null>(null);
41+
const { mutateAsync: onCreate } = useInbound.document.insert();
3142
const { isLoading, fetchAsync } = useInbound.document.listAll({
3243
key: 'createdDateTime',
3344
direction: 'desc',
3445
isDesc: true,
3546
});
47+
const { data, isLoading: internalOrderIsLoading } =
48+
useInbound.document.listInternalOrders(name?.id ?? '');
49+
const manuallyLinkInternalOrder =
50+
store?.preferences.manuallyLinkInternalOrderToInboundShipment;
3651

3752
const csvExport = async () => {
3853
const data = await fetchAsync();
@@ -46,13 +61,47 @@ export const AppBarButtons: FC<{
4661
success(t('success'))();
4762
};
4863

64+
const createInvoice = async (nameId: string) => {
65+
const invoiceNumber = await onCreate({
66+
id: FnUtils.generateUUID(),
67+
otherPartyId: nameId,
68+
});
69+
70+
navigate(
71+
RouteBuilder.create(AppRoute.Replenishment)
72+
.addPart(AppRoute.InboundShipment)
73+
.addPart(String(invoiceNumber))
74+
.build()
75+
);
76+
};
77+
useEffect(() => {
78+
if (name && (data?.totalCount === 0 || !manuallyLinkInternalOrder)) {
79+
createInvoice(name.id);
80+
}
81+
}, [name, data]);
82+
83+
const onRowClick = async (row: LinkedRequestRowFragment) => {
84+
const invoiceNumber = await onCreate({
85+
id: FnUtils.generateUUID(),
86+
otherPartyId: name?.id ?? '',
87+
requisitionId: row.id,
88+
});
89+
90+
navigate(
91+
RouteBuilder.create(AppRoute.Replenishment)
92+
.addPart(AppRoute.InboundShipment)
93+
.addPart(String(invoiceNumber))
94+
.build()
95+
);
96+
};
97+
4998
return (
5099
<AppBarButtonsPortal>
51100
<Grid container gap={1}>
52101
<ButtonWithIcon
53102
Icon={<PlusCircleIcon />}
54103
label={t('button.new-shipment')}
55-
onClick={modalController.toggleOn}
104+
onClick={invoiceModalController.toggleOn}
56105
/>
57106
<LoadingButton
58107
startIcon={<DownloadIcon />}
@@ -63,22 +112,30 @@ export const AppBarButtons: FC<{
63112
label={t('button.export')}
64113
/>
65114
</Grid>
115+
116+
{data?.totalCount !== 0 && manuallyLinkInternalOrder && (
117+
<LinkInternalOrderModal
118+
requestRequisitions={data?.nodes}
119+
isOpen={linkRequestModalController.isOn}
120+
onClose={linkRequestModalController.toggleOff}
121+
onRowClick={onRowClick}
122+
isLoading={internalOrderIsLoading}
123+
onNextClick={() => {
124+
if (name) {
125+
createInvoice(name.id);
126+
}
127+
}}
128+
/>
129+
)}
66130
<SupplierSearchModal
67-
open={modalController.isOn}
68-
onClose={modalController.toggleOff}
69-
onChange={async name => {
70-
modalController.toggleOff();
71-
await onCreate({
72-
id: FnUtils.generateUUID(),
73-
otherPartyId: name?.id,
74-
}).then(invoiceNumber => {
75-
navigate(
76-
RouteBuilder.create(AppRoute.Replenishment)
77-
.addPart(AppRoute.InboundShipment)
78-
.addPart(String(invoiceNumber))
79-
.build()
80-
);
81-
});
131+
open={invoiceModalController.isOn}
132+
onClose={invoiceModalController.toggleOff}
133+
onChange={nameRow => {
134+
setName(nameRow);
135+
invoiceModalController.toggleOff();
136+
if (manuallyLinkInternalOrder) {
137+
linkRequestModalController.toggleOn();
138+
}
82139
}}
83140
/>
84141
</AppBarButtonsPortal>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import React from 'react';
2+
import { LinkedRequestRowFragment } from '../api';
3+
import {
4+
useColumns,
5+
getNotePopoverColumn,
6+
ColumnAlign,
7+
DataTable,
8+
useWindowDimensions,
9+
useTranslation,
10+
useDialog,
11+
DialogButton,
12+
Typography,
13+
} from '@openmsupply-client/common';
14+
15+
interface LinkInternalOrderModalProps {
16+
isOpen: boolean;
17+
onClose: () => void;
18+
requestRequisitions?: LinkedRequestRowFragment[];
19+
onRowClick: (row: LinkedRequestRowFragment) => void;
20+
isLoading: boolean;
21+
onNextClick: () => void;
22+
}
23+
24+
export const LinkInternalOrderModal = ({
25+
isOpen,
26+
onClose,
27+
requestRequisitions: data,
28+
onRowClick,
29+
isLoading,
30+
onNextClick: createInvoice,
31+
}: LinkInternalOrderModalProps) => {
32+
const t = useTranslation();
33+
const { width, height } = useWindowDimensions();
34+
const { Modal } = useDialog({ isOpen, onClose });
35+
36+
const columns = useColumns<LinkedRequestRowFragment>([
37+
{
38+
key: 'requisitionNumber',
39+
label: 'label.number',
40+
width: 80,
41+
align: ColumnAlign.Right,
42+
},
43+
['createdDatetime', { width: 80, align: ColumnAlign.Right }],
44+
{
45+
key: 'username',
46+
label: 'label.entered-by',
47+
width: 150,
48+
accessor: ({ rowData }) => rowData?.user?.username ?? '',
49+
},
50+
{
51+
key: 'programName',
52+
label: 'label.program',
53+
accessor: ({ rowData }) => rowData.program?.name ?? '',
54+
width: 200,
55+
},
56+
['theirReference', { width: 150 }],
57+
[
58+
getNotePopoverColumn(),
59+
{
60+
accessor: ({ rowData }) => {
61+
return { header: '', body: rowData.comment };
62+
},
63+
},
64+
],
65+
]);
66+
67+
return (
68+
<Modal
69+
title={t('header.link-internal-order')}
70+
width={width * 0.7}
71+
height={height * 0.8}
72+
nextButton={<DialogButton variant="next" onClick={createInvoice} />}
73+
cancelButton={<DialogButton variant="cancel" onClick={onClose} />}
74+
>
75+
<>
76+
<Typography
77+
sx={{
78+
fontStyle: 'italic',
79+
}}
80+
>
81+
{t('message.continue-to-make-inbound-shipment')}
82+
</Typography>
83+
<DataTable
84+
id="link-internal-order-to-inbound"
85+
columns={columns}
86+
data={data ?? []}
87+
dense
88+
onRowClick={onRowClick}
89+
isLoading={isLoading}
90+
/>
91+
</>
92+
</Modal>
93+
);
94+
};

client/packages/invoices/src/InboundShipment/ListView/ListView.tsx

+7-3
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ export const InboundListView: FC = () => {
5353
const queryParams = { ...filter, sortBy, first, offset };
5454

5555
const navigate = useNavigate();
56-
const modalController = useToggle();
56+
const invoiceModalController = useToggle();
57+
const linkRequestModalController = useToggle();
5758

5859
const { data, isError, isLoading } = useInbound.document.list(queryParams);
5960
useDisableInboundRows(data?.nodes);
@@ -96,7 +97,10 @@ export const InboundListView: FC = () => {
9697
return (
9798
<>
9899
<Toolbar filter={filter} />
99-
<AppBarButtons modalController={modalController} />
100+
<AppBarButtons
101+
invoiceModalController={invoiceModalController}
102+
linkRequestModalController={linkRequestModalController}
103+
/>
100104

101105
<DataTable
102106
id="inbound-line-list"
@@ -112,7 +116,7 @@ export const InboundListView: FC = () => {
112116
noDataElement={
113117
<NothingHere
114118
body={t('error.no-inbound-shipments')}
115-
onCreate={modalController.toggleOn}
119+
onCreate={invoiceModalController.toggleOn}
116120
/>
117121
}
118122
enableColumnSelection

client/packages/invoices/src/InboundShipment/api/api.ts

+17
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
InsertInboundShipmentServiceLineInput,
1717
UpdateInboundShipmentServiceLineInput,
1818
DeleteInboundShipmentServiceLineInput,
19+
RequisitionSortFieldInput,
20+
RequisitionNodeType,
1921
} from '@openmsupply-client/common';
2022
import { DraftInboundLine } from './../../types';
2123
import { isA } from '../../utils';
@@ -208,6 +210,21 @@ export const getInboundQueries = (sdk: Sdk, storeId: string) => ({
208210

209211
throw new Error('Could not find invoice!');
210212
},
213+
listInternalOrders: async (otherPartyId: string) => {
214+
const filter = {
215+
type: { equalTo: RequisitionNodeType.Request },
216+
otherPartyId: { equalTo: otherPartyId },
217+
};
218+
const result = await sdk.requests({
219+
storeId,
220+
sort: {
221+
key: RequisitionSortFieldInput.CreatedDatetime,
222+
desc: true,
223+
},
224+
filter,
225+
});
226+
return result?.requisitions;
227+
},
211228
},
212229
delete: async (invoices: InboundRowFragment[]): Promise<string[]> => {
213230
const result =

client/packages/invoices/src/InboundShipment/api/hooks/document/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useNextItem } from './useNextItem';
88
import { useUpdateInbound } from './useUpdateInbound';
99
import { useUpdateInboundServiceTax } from './useInboundUpdateServiceTax';
1010
import { useInboundDelete } from './useInboundDelete';
11+
import { useListInternalOrders } from './useListInternalOrders';
1112

1213
export const Document = {
1314
useInboundDeleteRows,
@@ -20,4 +21,5 @@ export const Document = {
2021
useNextItem,
2122
useUpdateInbound,
2223
useUpdateInboundServiceTax,
24+
useListInternalOrders,
2325
};

0 commit comments

Comments
 (0)