@openmrs/esm-stock-management-app 1.0.1-pre.575 → 1.0.1-pre.586
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/769.js +1 -1
- package/dist/769.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-stock-management-app.js.buildmanifest.json +6 -6
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/config-schema.ts +1 -1
- package/src/core/api/types/stockItem/StockItemInventory.ts +1 -0
- package/src/stock-items/add-stock-item/transactions/transactions.component.tsx +5 -0
- package/src/stock-operations/add-stock-operation/add-stock-operation.component.tsx +7 -5
- package/src/stock-operations/add-stock-operation/add-stock-operation.utils.tsx +23 -27
- package/src/stock-operations/add-stock-operation/base-operation-details.component.tsx +350 -223
- package/src/stock-operations/add-stock-operation/stock-items-addition-row.component.tsx +151 -25
- package/src/stock-operations/add-stock-operation/stock-items-addition.component.tsx +2 -0
- package/src/stock-operations/batch-no-selector/batch-no-selector.component.tsx +8 -4
- package/src/stock-operations/edit-stock-operation/edit-stock-operation-action-menu.component.tsx +26 -51
- package/src/stock-operations/party-selector/party-selector.component.tsx +35 -30
- package/src/stock-operations/stock-item-selector/stock-item-selector.component.tsx +1 -1
- package/src/stock-operations/stock-operation.utils.tsx +2 -13
- package/src/stock-operations/stock-operations-table.component.tsx +29 -20
- package/src/stock-operations/stock-operations.resource.ts +1 -1
@@ -35,6 +35,7 @@ import { useStockOperationPages } from "../stock-operations-table.resource";
|
|
35
35
|
import { createBaseOperationPayload } from "./add-stock-utils";
|
36
36
|
import { showSnackbar, useSession } from "@openmrs/esm-framework";
|
37
37
|
|
38
|
+
import { Party } from "../../core/api/types/Party";
|
38
39
|
import styles from "../add-stock-operation/base-operation-details.scss";
|
39
40
|
|
40
41
|
interface BaseOperationDetailsProps {
|
@@ -54,8 +55,6 @@ const BaseOperationDetails: React.FC<BaseOperationDetailsProps> = ({
|
|
54
55
|
isEditing,
|
55
56
|
setup: {
|
56
57
|
requiresStockAdjustmentReason: showReason,
|
57
|
-
shouldLockSource: lockSource,
|
58
|
-
shouldLockDestination: lockDestination,
|
59
58
|
sourcePartyList,
|
60
59
|
destinationPartyList,
|
61
60
|
},
|
@@ -114,249 +113,377 @@ const BaseOperationDetails: React.FC<BaseOperationDetailsProps> = ({
|
|
114
113
|
setIsSaving(false);
|
115
114
|
}
|
116
115
|
};
|
116
|
+
|
117
|
+
const isCompleteStatus = model?.status === "COMPLETED";
|
118
|
+
const sourceTags =
|
119
|
+
operation?.stockOperationTypeLocationScopes
|
120
|
+
?.filter((p) => operation?.hasSource && p.isSource)
|
121
|
+
.map((p) => p.locationTag) ?? [];
|
122
|
+
|
123
|
+
const destinationTags =
|
124
|
+
operation?.stockOperationTypeLocationScopes
|
125
|
+
?.filter((p) => operation?.hasDestination && p.isDestination)
|
126
|
+
.map((p) => p.locationTag) ?? [];
|
127
|
+
|
128
|
+
const sourcePartyListFilter = (sourcePartyList: Party) => {
|
129
|
+
const isValid =
|
130
|
+
(sourcePartyList.locationUuid &&
|
131
|
+
operation?.sourceType === "Location" &&
|
132
|
+
(sourceTags.length === 0 ||
|
133
|
+
(sourcePartyList.tags &&
|
134
|
+
sourceTags.some((x) => sourcePartyList.tags.includes(x))))) ||
|
135
|
+
(sourcePartyList.stockSourceUuid && operation?.sourceType === "Other");
|
136
|
+
return isValid;
|
137
|
+
};
|
138
|
+
|
139
|
+
const destinationPartyListFilter = (destinationPartyList: Party) => {
|
140
|
+
const isValid =
|
141
|
+
(destinationPartyList.locationUuid &&
|
142
|
+
operation?.destinationType === "Location" &&
|
143
|
+
(destinationTags.length === 0 ||
|
144
|
+
(destinationPartyList.tags &&
|
145
|
+
destinationTags.some((x) =>
|
146
|
+
destinationPartyList.tags.includes(x)
|
147
|
+
)))) ||
|
148
|
+
(destinationPartyList.stockSourceUuid &&
|
149
|
+
operation?.destinationType === "Other");
|
150
|
+
return isValid;
|
151
|
+
};
|
117
152
|
return (
|
118
153
|
<div style={{ margin: "10px" }}>
|
119
154
|
<form className={`${styles.formContainer} ${styles.verticalForm}`}>
|
120
|
-
{
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
155
|
+
{isCompleteStatus ? (
|
156
|
+
<>
|
157
|
+
{model?.operationDate && (
|
158
|
+
<TextInput
|
159
|
+
id="operationDateLbl"
|
160
|
+
value={formatForDatePicker(model.operationDate)}
|
161
|
+
readOnly={true}
|
162
|
+
labelText={t("operationDate", "Operation Date")}
|
163
|
+
/>
|
164
|
+
)}
|
165
|
+
{model?.operationNumber && (
|
166
|
+
<TextInput
|
167
|
+
id="operationNoLbl"
|
168
|
+
value={model.operationNumber}
|
169
|
+
readOnly={true}
|
170
|
+
labelText={t("operationNumber", "Operation Number")}
|
171
|
+
/>
|
172
|
+
)}
|
173
|
+
{model?.atLocationName && (
|
174
|
+
<TextInput
|
175
|
+
id="sourceLbl"
|
176
|
+
value={model.atLocationName}
|
177
|
+
readOnly={true}
|
178
|
+
labelText={t("source", "Source")}
|
179
|
+
/>
|
180
|
+
)}
|
181
|
+
{model?.destinationName && (
|
182
|
+
<TextInput
|
183
|
+
id="destinationLbl"
|
184
|
+
value={model.destinationName}
|
185
|
+
readOnly={true}
|
186
|
+
labelText={t("destination", "Destination")}
|
187
|
+
/>
|
188
|
+
)}
|
189
|
+
{model?.responsiblePersonGivenName &&
|
190
|
+
model?.responsiblePersonFamilyName && (
|
191
|
+
<TextInput
|
192
|
+
id="responsiblePersonLbl"
|
193
|
+
value={`${model.responsiblePersonGivenName} ${model.responsiblePersonFamilyName}`}
|
194
|
+
readOnly={true}
|
195
|
+
labelText={t("responsiblePerson", "Responsible Person")}
|
141
196
|
/>
|
142
|
-
|
197
|
+
)}
|
198
|
+
{showReason && model?.reasonName && (
|
199
|
+
<TextInput
|
200
|
+
id="reasonLbl"
|
201
|
+
value={model.reasonName}
|
202
|
+
readOnly={true}
|
203
|
+
labelText={t("reason", "Reason")}
|
204
|
+
/>
|
205
|
+
)}
|
206
|
+
{model?.remarks && (
|
207
|
+
<TextInput
|
208
|
+
id="remarksLbl"
|
209
|
+
value={model.remarks}
|
210
|
+
readOnly={true}
|
211
|
+
labelText={t("remarks", "Remarks")}
|
212
|
+
/>
|
143
213
|
)}
|
144
|
-
name="operationDate"
|
145
|
-
/>
|
146
|
-
)}
|
147
|
-
|
148
|
-
{!canEdit && (
|
149
|
-
<>
|
150
|
-
<TextInput
|
151
|
-
id="operationDateLbl"
|
152
|
-
value={formatForDatePicker(model?.operationDate)}
|
153
|
-
readOnly={true}
|
154
|
-
labelText="Operation Date"
|
155
|
-
/>
|
156
214
|
</>
|
157
|
-
)
|
215
|
+
) : (
|
216
|
+
<>
|
217
|
+
{canEdit && (
|
218
|
+
<Controller
|
219
|
+
control={control}
|
220
|
+
render={({ field: { onChange } }) => (
|
221
|
+
<DatePicker
|
222
|
+
datePickerType="single"
|
223
|
+
maxDate={formatForDatePicker(today())}
|
224
|
+
locale="en"
|
225
|
+
dateFormat={DATE_PICKER_CONTROL_FORMAT}
|
226
|
+
onChange={([newDate]) => {
|
227
|
+
onChange(newDate);
|
228
|
+
}}
|
229
|
+
>
|
230
|
+
<DatePickerInput
|
231
|
+
invalid={!!errors.operationDate}
|
232
|
+
invalidText={errors?.operationDate?.message}
|
233
|
+
id="operationDate"
|
234
|
+
name="operationDate"
|
235
|
+
placeholder={DATE_PICKER_FORMAT}
|
236
|
+
labelText={t("operationDate", "Operation Date")}
|
237
|
+
defaultValue={formatForDatePicker(model?.operationDate)}
|
238
|
+
/>
|
239
|
+
</DatePicker>
|
240
|
+
)}
|
241
|
+
name="operationDate"
|
242
|
+
/>
|
243
|
+
)}
|
158
244
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
245
|
+
{!canEdit && (
|
246
|
+
<>
|
247
|
+
<TextInput
|
248
|
+
id="operationDateLbl"
|
249
|
+
value={formatForDatePicker(model?.operationDate)}
|
250
|
+
readOnly={true}
|
251
|
+
labelText="Operation Date"
|
252
|
+
/>
|
253
|
+
</>
|
254
|
+
)}
|
167
255
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
: t("location", "Location")
|
177
|
-
}
|
178
|
-
placeholder={
|
179
|
-
operation.hasDestination
|
180
|
-
? t("chooseASource", "Choose a source")
|
181
|
-
: t("chooseALocation", "Choose a location")
|
182
|
-
}
|
183
|
-
invalid={!!errors.sourceUuid}
|
184
|
-
invalidText={errors.sourceUuid && errors?.sourceUuid?.message}
|
185
|
-
parties={sourcePartyList || []}
|
186
|
-
/>
|
187
|
-
)}
|
256
|
+
{isEditing && model?.operationNumber && (
|
257
|
+
<TextInput
|
258
|
+
id="operationNoLbl"
|
259
|
+
value={model?.operationNumber}
|
260
|
+
readOnly={true}
|
261
|
+
labelText={"Operation Number"}
|
262
|
+
/>
|
263
|
+
)}
|
188
264
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
265
|
+
{canEdit && (operation?.hasSource || model?.atLocationUuid) && (
|
266
|
+
<PartySelector
|
267
|
+
controllerName="sourceUuid"
|
268
|
+
name="sourceUuid"
|
269
|
+
control={control}
|
270
|
+
partyUuid={model?.atLocationUuid}
|
271
|
+
title={
|
272
|
+
operation?.hasDestination || model?.destinationUuid
|
273
|
+
? t("from", "From")
|
274
|
+
: t("location", "Location")
|
275
|
+
}
|
276
|
+
placeholder={
|
277
|
+
operation.hasDestination || model?.destinationUuid
|
278
|
+
? t("chooseASource", "Choose a source")
|
279
|
+
: t("chooseALocation", "Choose a location")
|
280
|
+
}
|
281
|
+
invalid={!!errors.sourceUuid}
|
282
|
+
invalidText={errors.sourceUuid && errors?.sourceUuid?.message}
|
283
|
+
parties={sourcePartyList?.filter(sourcePartyListFilter) || []}
|
284
|
+
filterFunction={sourcePartyListFilter}
|
285
|
+
/>
|
286
|
+
)}
|
205
287
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
288
|
+
{!canEdit && isEditing && (
|
289
|
+
<PartySelector
|
290
|
+
controllerName="sourceUuid"
|
291
|
+
name="sourceUuid"
|
292
|
+
control={control}
|
293
|
+
partyUuid={model?.atLocationUuid}
|
294
|
+
title={
|
295
|
+
operation?.hasDestination || model?.destinationUuid
|
296
|
+
? "From"
|
297
|
+
: "Location"
|
298
|
+
}
|
299
|
+
placeholder={
|
300
|
+
operation.hasDestination || model?.destinationUuid
|
301
|
+
? t("chooseASource", "Choose a source")
|
302
|
+
: t("chooseALocation", "Choose a location")
|
303
|
+
}
|
304
|
+
invalid={!!errors.sourceUuid}
|
305
|
+
invalidText={errors.sourceUuid && errors?.sourceUuid?.message}
|
306
|
+
parties={sourcePartyList?.filter(sourcePartyListFilter) || []}
|
307
|
+
filterFunction={sourcePartyListFilter}
|
308
|
+
/>
|
309
|
+
)}
|
310
|
+
{canEdit &&
|
311
|
+
(operation?.hasDestination || model?.destinationUuid) && (
|
312
|
+
<PartySelector
|
313
|
+
controllerName="destinationUuid"
|
314
|
+
name="destinationUuid"
|
315
|
+
control={control}
|
316
|
+
partyUuid={model?.destinationUuid}
|
317
|
+
title={
|
318
|
+
operation?.hasSource || model?.atLocationUuid
|
319
|
+
? t("to", "To")
|
320
|
+
: t("location", "Location")
|
321
|
+
}
|
322
|
+
placeholder={
|
323
|
+
operation?.hasSource || model?.atLocationUuid
|
324
|
+
? t("chooseADestination", "Choose a destination")
|
325
|
+
: "Location"
|
326
|
+
}
|
327
|
+
invalid={!!errors.destinationUuid}
|
328
|
+
invalidText={
|
329
|
+
errors.destinationUuid && errors?.destinationUuid?.message
|
330
|
+
}
|
331
|
+
parties={
|
332
|
+
destinationPartyList?.filter(destinationPartyListFilter) ||
|
333
|
+
[]
|
334
|
+
}
|
335
|
+
filterFunction={destinationPartyListFilter}
|
336
|
+
/>
|
337
|
+
)}
|
224
338
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
339
|
+
{!canEdit && isEditing && (
|
340
|
+
<PartySelector
|
341
|
+
controllerName="destinationUuid"
|
342
|
+
name="destinationUuid"
|
343
|
+
control={control}
|
344
|
+
partyUuid={model?.destinationUuid}
|
345
|
+
title={
|
346
|
+
operation?.hasSource || model?.atLocationUuid
|
347
|
+
? t("to", "To")
|
348
|
+
: t("location", "Location")
|
349
|
+
}
|
350
|
+
placeholder={
|
351
|
+
operation?.hasSource || model?.atLocationUuid
|
352
|
+
? t("chooseADestination", "Choose a destination")
|
353
|
+
: "Location"
|
354
|
+
}
|
355
|
+
invalid={!!errors.destinationUuid}
|
356
|
+
invalidText={
|
357
|
+
errors.destinationUuid && errors?.destinationUuid?.message
|
358
|
+
}
|
359
|
+
parties={
|
360
|
+
destinationPartyList?.filter(destinationPartyListFilter) || []
|
361
|
+
}
|
362
|
+
filterFunction={destinationPartyListFilter}
|
363
|
+
/>
|
364
|
+
)}
|
243
365
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
366
|
+
{canEdit && (
|
367
|
+
<UsersSelector
|
368
|
+
controllerName="responsiblePersonUuid"
|
369
|
+
name="responsiblePersonUuid"
|
370
|
+
control={control}
|
371
|
+
userUuid={model?.responsiblePersonUuid}
|
372
|
+
title={t("responsiblePerson", "Responsible Person")}
|
373
|
+
placeholder={t("filter", "Filter ...")}
|
374
|
+
invalid={!!errors.responsiblePersonUuid}
|
375
|
+
invalidText={
|
376
|
+
errors.responsiblePersonUuid &&
|
377
|
+
errors?.responsiblePersonUuid?.message
|
378
|
+
}
|
379
|
+
onUserChanged={(user) => {
|
380
|
+
if (user?.uuid === otherUser.uuid) {
|
381
|
+
setIsOtherUser(true);
|
382
|
+
} else {
|
383
|
+
setIsOtherUser(false);
|
384
|
+
}
|
385
|
+
}}
|
386
|
+
/>
|
387
|
+
)}
|
266
388
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
389
|
+
{isOtherUser && (
|
390
|
+
<ControlledTextInput
|
391
|
+
id="responsiblePersonOther"
|
392
|
+
name="responsiblePersonOther"
|
393
|
+
control={control}
|
394
|
+
controllerName="responsiblePersonOther"
|
395
|
+
maxLength={255}
|
396
|
+
size={"md"}
|
397
|
+
value={`${model?.responsiblePersonOther ?? ""}`}
|
398
|
+
labelText={t("responsiblePerson", "Responsible Person")}
|
399
|
+
placeholder={t("pleaseSpecify", "Please Specify")}
|
400
|
+
invalid={!!errors.responsiblePersonOther}
|
401
|
+
invalidText={
|
402
|
+
errors.responsiblePersonOther &&
|
403
|
+
errors?.responsiblePersonOther?.message
|
404
|
+
}
|
405
|
+
/>
|
406
|
+
)}
|
285
407
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
408
|
+
{!canEdit && isEditing && (
|
409
|
+
<UsersSelector
|
410
|
+
controllerName="responsiblePersonUuid"
|
411
|
+
name="responsiblePersonUuid"
|
412
|
+
control={control}
|
413
|
+
userUuid={model?.responsiblePersonUuid}
|
414
|
+
title={t("responsiblePerson", "Responsible Person")}
|
415
|
+
placeholder={t("filter", "Filter ...")}
|
416
|
+
invalid={!!errors.responsiblePersonUuid}
|
417
|
+
invalidText={
|
418
|
+
errors.responsiblePersonUuid &&
|
419
|
+
errors?.responsiblePersonUuid?.message
|
420
|
+
}
|
421
|
+
onUserChanged={(user) => {
|
422
|
+
if (user?.uuid === otherUser.uuid) {
|
423
|
+
setIsOtherUser(true);
|
424
|
+
} else {
|
425
|
+
setIsOtherUser(false);
|
426
|
+
}
|
427
|
+
}}
|
428
|
+
/>
|
429
|
+
)}
|
307
430
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
431
|
+
{showReason && canEdit && (
|
432
|
+
<StockOperationReasonSelector
|
433
|
+
controllerName="reasonUuid"
|
434
|
+
name="reasonUuid"
|
435
|
+
control={control}
|
436
|
+
reasonUuid={model?.reasonUuid}
|
437
|
+
placeholder={t("chooseAReason", "Choose a reason")}
|
438
|
+
title={t("reason", "Reason")}
|
439
|
+
invalid={!!errors.reasonUuid}
|
440
|
+
invalidText={errors.reasonUuid && errors?.reasonUuid?.message}
|
441
|
+
onReasonChange={(reason) => {
|
442
|
+
setValue("reasonUuid", reason.uuid);
|
443
|
+
}}
|
444
|
+
/>
|
445
|
+
)}
|
322
446
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
447
|
+
{showReason && !canEdit && (
|
448
|
+
<TextInput
|
449
|
+
id="reasonUuidLbl"
|
450
|
+
value={model?.reasonName ?? ""}
|
451
|
+
readOnly={true}
|
452
|
+
labelText={"Reason:"}
|
453
|
+
/>
|
454
|
+
)}
|
331
455
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
456
|
+
<ControlledTextArea
|
457
|
+
id="remarks"
|
458
|
+
name="remarks"
|
459
|
+
control={control}
|
460
|
+
controllerName="remarks"
|
461
|
+
maxLength={255}
|
462
|
+
value={`${model?.remarks ?? ""}`}
|
463
|
+
labelText={t("remarks", "Remarks")}
|
464
|
+
invalid={!!errors.remarks}
|
465
|
+
invalidText={errors.remarks && errors?.remarks?.message}
|
466
|
+
/>
|
343
467
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
468
|
+
<div style={{ display: "flex", flexDirection: "row-reverse" }}>
|
469
|
+
<Button
|
470
|
+
name="save"
|
471
|
+
type="button"
|
472
|
+
className="submitButton"
|
473
|
+
onClick={handleSubmit(handleSave)}
|
474
|
+
kind="primary"
|
475
|
+
renderIcon={ArrowRight}
|
476
|
+
>
|
477
|
+
{isSaving ? <InlineLoading /> : t("next", "Next")}
|
478
|
+
</Button>
|
479
|
+
</div>
|
480
|
+
</>
|
481
|
+
)}
|
356
482
|
</form>
|
357
483
|
</div>
|
358
484
|
);
|
359
485
|
};
|
486
|
+
|
360
487
|
function mapIssueStockLocations(stockOperation) {
|
361
488
|
/** Since we are using requisition information to issue stock,
|
362
489
|
please note that the locations will be inverted: the destination listed on the requisition will become the issuing location.
|