Skip to content

Commit

Permalink
Merge pull request #5161 from msupply-foundation/5153-prevent-backdat…
Browse files Browse the repository at this point in the history
…e-with-lines

Don't allow changing backdated date with lines assigned.
  • Loading branch information
jmbrunskill authored Oct 17, 2024
2 parents 09f9739 + be4be4b commit c9d29b7
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 65 deletions.
3 changes: 2 additions & 1 deletion client/packages/common/src/intl/locales/en/dispensary.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"messages.confirm-delete-lines_many": "This will permanently remove {{count}} lines from this prescription",
"messages.confirm-delete-lines_one": "This will permanently remove 1 line from this prescription",
"messages.confirm-delete-lines_other": "This will permanently remove {{count}} lines from this prescription",
"messages.confirm-delete-all-lines": "This will permanently remove all lines from this prescription",
"messages.confirm-delete-prescription": "This will permanently remove Prescription #{{number}}",
"messages.confirm-delete-prescriptions_few": "This will permanently remove {{count}} prescriptions",
"messages.confirm-delete-prescriptions_many": "This will permanently remove {{count}} prescriptions",
Expand Down Expand Up @@ -124,4 +125,4 @@
"title.patient-retrieval-modal": "Patient Data Retrieval",
"warning.cannot-create-placeholder-packs": "There is a total of {{allocatedQuantity}} packs available. Unable to allocate all {{requestedQuantity}} packs.",
"warning.cannot-create-placeholder-units": "There is a total of {{allocatedQuantity}} units available. Unable to allocate all {{requestedQuantity}} units."
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useAppTheme } from '@common/styles';
import { StandardTextFieldProps, TextFieldProps } from '@mui/material';
import { DateUtils, useIntlUtils, useTranslation } from '@common/intl';
import { getFormattedDateError } from '../BaseDatePickerInput';
import { useBufferState } from '@common/hooks';

const TextField = (params: TextFieldProps) => {
const textInputProps: StandardTextFieldProps = {
Expand Down Expand Up @@ -38,26 +39,32 @@ export const DateTimePickerInput: FC<
}) => {
const theme = useAppTheme();
const [internalError, setInternalError] = useState<string | null>(null);
const [value, setValue] = useBufferState<Date | null>(props.value ?? null);
const [isInitialEntry, setIsInitialEntry] = useState(true);
const t = useTranslation();
const { getLocale } = useIntlUtils();
const dateParseOptions = { locale: getLocale() };
const format =
props.format === undefined ? (showTime ? 'P p' : 'P') : props.format;

const updateDate = (date: Date | null) => {
setValue(date);
onChange(date);
};

// Max/Min should be restricted by the UI, but it's not restricting TIME input
// (only Date component). So this function will enforce the max/min after
// input
const handleDateInput = (date: Date | null) => {
if (minDate && date && date < minDate) {
onChange(minDate);
updateDate(minDate);
return;
}
if (maxDate && date && date > maxDate) {
onChange(maxDate);
updateDate(maxDate);
return;
}
onChange(date);
updateDate(date);
};

return (
Expand Down Expand Up @@ -138,7 +145,7 @@ export const DateTimePickerInput: FC<
minDate={minDate}
maxDate={maxDate}
{...props}
value={props.value}
value={value}
/>
);
};
50 changes: 42 additions & 8 deletions client/packages/invoices/src/Prescriptions/DetailView/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import {
DateTimePickerInput,
Formatter,
DateUtils,
useConfirmationModal,
} from '@openmsupply-client/common';
import { PatientSearchInput } from '@openmsupply-client/system';
import { usePrescription } from '../api';
import { ClinicianSearchInput } from '../../../../system/src/Clinician';
import { usePrescriptionRows } from '../api/hooks/line/usePrescriptionRows';

export const Toolbar: FC = () => {
const { id, patient, clinician, prescriptionDate, update } =
Expand All @@ -25,10 +27,49 @@ export const Toolbar: FC = () => {
'prescriptionDate',
]);
const onDelete = usePrescription.line.deleteSelected();
const onDeleteAll = usePrescription.line.deleteAll();
const { items } = usePrescriptionRows();

const isDisabled = usePrescription.utils.isDisabled();
const t = useTranslation('dispensary');

const getConfirmation = useConfirmationModal({
title: t('heading.are-you-sure'),
message: t('messages.confirm-delete-all-lines'),
});

const handleDateChange = async (newPrescriptionDate: Date | null) => {
if (!newPrescriptionDate) return;

const oldPrescriptionDate = DateUtils.getDateOrNull(prescriptionDate);

if (newPrescriptionDate === oldPrescriptionDate) return;

if (!items || items.length === 0) {
// If there are no lines, we can just update the prescription date
await update({
id,
prescriptionDate: Formatter.toIsoString(
DateUtils.endOfDayOrNull(newPrescriptionDate)
),
});
return;
}

// Otherwise, we need to delete all the lines first
getConfirmation({
onConfirm: async () => {
await onDeleteAll();
await update({
id,
prescriptionDate: Formatter.toIsoString(
DateUtils.endOfDayOrNull(newPrescriptionDate)
),
});
},
});
};

return (
<AppBarContentPortal sx={{ display: 'flex', flex: 1, marginBottom: 1 }}>
<Grid
Expand Down Expand Up @@ -84,14 +125,7 @@ export const Toolbar: FC = () => {
defaultValue={new Date()}
value={DateUtils.getDateOrNull(prescriptionDate)}
format="P"
onChange={async prescriptionDate => {
await update({
id,
prescriptionDate: Formatter.toIsoString(
DateUtils.endOfDayOrNull(prescriptionDate)
),
});
}}
onChange={handleDateChange}
maxDate={new Date()}
/>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const usePrescription = {
rows: Line.usePrescriptionRows,
delete: Line.usePrescriptionDeleteLines,
deleteSelected: Line.usePrescriptionDeleteSelectedLines,
deleteAll: Line.usePrescriptionDeleteAllLines,
save: Line.usePrescriptionSaveLines,
},
utils: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
usePrescriptionDeleteLines,
usePrescriptionDeleteSelectedLines,
usePrescriptionDeleteAllLines,
} from './usePrescriptionDeleteLines';
import { usePrescriptionLine } from './usePrescriptionLine';
import { usePrescriptionRows } from './usePrescriptionRows';
Expand All @@ -11,5 +12,6 @@ export const Line = {
usePrescriptionLine,
usePrescriptionDeleteLines,
usePrescriptionDeleteSelectedLines,
usePrescriptionDeleteAllLines,
usePrescriptionSaveLines,
};
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,24 @@ export const usePrescriptionDeleteSelectedLines = (): (() => void) => {

return confirmAndDelete;
};

export const usePrescriptionDeleteAllLines = (): (() => Promise<void>) => {
const { items } = usePrescriptionRows();
const { mutateAsync } = usePrescriptionDeleteLines();

// Select all rows!
const selectedRows =
(items ?? []).map(({ lines }) => lines.flat()).flat() ?? [];

if (selectedRows.length === 0) {
return async () => {};
}

const onDelete = async () => {
mutateAsync(selectedRows || []).catch(err => {
throw err;
});
};

return onDelete;
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "open-msupply",
"//": "Main version for the app, should be in semantic version format (any release candidate or test build should be separated by '-' i.e. 1.1.1-rc1 or 1.1.1-test",
"version": "2.3.00-rc9",
"version": "2.3.00-rc10",
"private": true,
"scripts": {
"start": "cd ./server && cargo run & cd ./client && yarn start-local",
Expand Down
6 changes: 3 additions & 3 deletions server/graphql/invoice/src/mutations/prescription/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ pub enum UpdatePrescriptionErrorInterface {
CannotReverseInvoiceStatus(CannotReverseInvoiceStatus),
InvoiceIsNotEditable(InvoiceIsNotEditable),
CanOnlyChangeToPickedWhenNoUnallocatedLines(CanOnlyChangeToPickedWhenNoUnallocatedLines),
StockNotAvailableAtDate(InvalidStockSelection),
CantBackDate(InvalidStockSelection),
}

impl UpdateInput {
Expand Down Expand Up @@ -130,8 +130,8 @@ fn map_error(error: ServiceError) -> Result<UpdatePrescriptionErrorInterface> {
))
}

ServiceError::StockNotAvailableAtDate(_) => {
return Ok(UpdatePrescriptionErrorInterface::StockNotAvailableAtDate(
ServiceError::CantBackDate(_) => {
return Ok(UpdatePrescriptionErrorInterface::CantBackDate(
InvalidStockSelection,
))
}
Expand Down
4 changes: 2 additions & 2 deletions server/service/src/invoice/prescription/update/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ pub enum UpdatePrescriptionError {
DatabaseError(RepositoryError),
/// Holds the id of the invalid invoice line
InvoiceLineHasNoStockLine(String),
/// Invalid stock for this date
StockNotAvailableAtDate(NaiveDateTime),
/// Can't backdate an invoice with allocated lines
CantBackDate(String),
}

type OutError = UpdatePrescriptionError;
Expand Down
56 changes: 10 additions & 46 deletions server/service/src/invoice/prescription/update/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ use crate::invoice::{
check_invoice_exists, check_invoice_is_editable, check_invoice_type, check_status_change,
check_store,
};
use crate::stock_line::historical_stock::get_historical_stock_lines_available_quantity;
use crate::validate::check_patient_exists;
use chrono::NaiveDateTime;
use repository::{
ClinicianRowRepository, EqualFilter, InvoiceLineFilter, InvoiceLineRepository, RepositoryError,
};
Expand Down Expand Up @@ -39,9 +37,16 @@ pub fn validate(
check_patient_exists(connection, patient_id)?.ok_or(PatientDoesNotExist)?;
}

if let Some(backdated_datetime) = &patch.backdated_datetime {
// Check that any lines already assigned won't create a ledger discrepancy
check_stock_available_at_date(connection, &invoice.id, backdated_datetime)?;
if patch.backdated_datetime.is_some() {
// Check if we have any lines allocated to this invoice, if so we can't backdate
let line_count = InvoiceLineRepository::new(connection).count(Some(
InvoiceLineFilter::new().invoice_id(EqualFilter::equal_to(&patch.id)),
))?;
if line_count > 0 {
return Err(CantBackDate(
"Can't backdate as invoice has allocated lines".to_string(),
));
}
}

Ok((invoice, status_changed))
Expand All @@ -60,44 +65,3 @@ fn check_clinician_exists(

Ok(result)
}

fn check_stock_available_at_date(
connection: &StorageConnection,
invoice_id: &str,
date: &NaiveDateTime,
) -> Result<bool, UpdatePrescriptionError> {
let repo = InvoiceLineRepository::new(connection);
let lines = repo
.query_by_filter(InvoiceLineFilter::new().invoice_id(EqualFilter::equal_to(invoice_id)))?;

let historic_stock_quantities = get_historical_stock_lines_available_quantity(
connection,
lines
.iter()
.filter_map(|r| {
r.stock_line_option
.as_ref()
.map(|s| (s, Some(r.invoice_line_row.number_of_packs)))
})
.collect(),
date,
)?;

for line in lines {
let Some(stock_line_id) = &line.invoice_line_row.stock_line_id else {
continue;
};

let Some(historical_stock) = historic_stock_quantities.get(stock_line_id) else {
continue;
};

if *historical_stock < line.invoice_line_row.number_of_packs {
return Err(UpdatePrescriptionError::StockNotAvailableAtDate(
date.clone(),
));
}
}

Ok(true)
}

0 comments on commit c9d29b7

Please sign in to comment.