Skip to content

5535 Exclude requisition made from web entries in mSupply #5536

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions dev_scripts/clean.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ echo "Cleaning up temporary files..."
rm -rf $TMPDIR_PACKAGER $TMPDIR_METRO
echo "Cleaning up watchman files..."
npx watchman watch-del-all
echo "Cleaning up android build files..."
rm -rf android/.gradle
rm -rf android/.idea
rm -rf android/app/build
echo "Reinstalling node modules..."
rm -rf node_modules/ &&
yarn cache clean &&
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
},
"name": "mSupplyMobile",
"//": "version must be in the format ${majorNumber}.${minorNumber}.${patchNumber}-rc${releaseCandidateNumber}",
"version": "8.6.9",
"version": "8.7.0",
"private": false,
"license": "MIT",
"description": "Mobile app for use with the mSupply medical inventory control software",
Expand Down
41 changes: 40 additions & 1 deletion src/dataMigration.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const dataMigrations = [
// as supplying store during initial sync.
if (
settings.get(SETTINGS_KEYS.SUPPLYING_STORE_NAME_ID) ===
'B1938F4FFDC2074DB5408B435ACEB198' ||
'B1938F4FFDC2074DB5408B435ACEB198' ||
settings.get(SETTINGS_KEYS.SUPPLYING_STORE_ID) === '734CC3EC70283A4AABC4E645C8B1E11D'
) {
settings.set(SETTINGS_KEYS.SUPPLYING_STORE_NAME_ID, 'E5D7BB38571C1F428AF397240EEB285F');
Expand Down Expand Up @@ -286,6 +286,45 @@ const dataMigrations = [
clearNumberSequences(database);
},
},
{
version: '8.7.0',
migrate: database => {
try {
// Get all blank request requisitions (i.e requisitions they might have been created
// possibly from web entries - having status 'wp', 'wf' etc in the mSupply) or
// through transactions linked to requisitions they were already deleted from the mSupply
const blankRequisitions = database
.objects('Requisition')
.filtered('otherStoreName == null and status == "new" and type == "request"')
.snapshot();

// Delete all blank request requisitions and their requisition items
database.write(() => {
blankRequisitions.forEach(requisition => {
// Check for linked transactions and unlink them first before deleting it
// to avoid any data integrity issues
const { linkedTransaction } = requisition;
if (linkedTransaction) {
database.update('Transaction', {
id: linkedTransaction.id,
linkedRequisition: null,
});
database.update('Requisition', {
id: requisition.id,
linkedTransaction: null,
});
}

// delete the requisition
database.delete('Requisition', requisition);
});
});
} catch (e) {
// eslint-disable-next-line no-console
console.error('Migration 8.7.0 error:', e.message, e.stack);
}
},
},
];

export default dataMigrations;
1 change: 1 addition & 0 deletions src/database/DataTypes/Transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,7 @@ Transaction.schema = {
mode: { type: 'string', default: 'store' },
prescriber: { type: 'Prescriber', optional: true },
linkedRequisition: { type: 'Requisition', optional: true },
pendingRequisitionId: { type: 'string', optional: true, indexed: true },
Copy link
Contributor

@Chris-Petty Chris-Petty Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bro got LLM'd? I can't find an indexed property in the realm docs and we've never set this anywhere else in the schema for this repo. That said, I'm surprised realm didn't throw a wobbly, either I haven't found the docs or realm is ignoring this?

Copy link
Collaborator Author

@raviSussol raviSussol Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a real property of realm js schema. https://www.mongodb.com/docs/realm-sdks/js/latest/types/Realm.PropertySchema.html. Agree that it hadn't been used before (maybe cause in the lower version before Mongodb owned Realm)? Although I was searching using LLMs for field indexing in the realm (to make realm query quicker for processing huge requisitions like some of our clients have Fiji, Png, CIV, etc) I had literally looked around their official docs to make sure that its not an LLM hallucination until I found that docs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to make realm query quicker for processing huge requisitions like some of our clients have Fiji, Png, CIV, etc

Are we having performance issues on mobile ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh yea I think I wasn't going deep enough couldn't find it in here which is where my initial googling took me, https://www.mongodb.com/docs/atlas/device-sdks/sdk/react-native/. Well I did end up first on some ancient version of the type docs you found like v0.7... thought it looked odd, the OG realm docs were much cleaner than any incarnation that MongoDB has created since 🙄 especially when they're trying to stuff this new Atlas thing down people's throats but have since deprecated it lol.

That said just found it in the proper docs! Not sure how I missed it before, though even if you search for it you get results mostly for their PHP Library first haha

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to make realm query quicker for processing huge requisitions like some of our clients have Fiji, Png, CIV, etc

Are we having performance issues on mobile ?

I haven't caught that so far but I basically used indexed property as an idea of relational database here in this case since pendingRequisitionId is a mirror representation (or foreign field) of Requisition.id (unlike an entire record object linked to a field) which is a primary key and has indexed property true by default.

subtotal: { type: 'double', optional: true },
outstanding: { type: 'double', optional: true },
insurancePolicy: { type: 'InsurancePolicy', optional: true },
Expand Down
2 changes: 1 addition & 1 deletion src/database/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export const schema = {
VaccineVialMonitorStatus,
VaccineVialMonitorStatusLog,
],
schemaVersion: 30,
schemaVersion: 31,
};

export default schema;
53 changes: 35 additions & 18 deletions src/sync/incomingSyncUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export const sanityCheckIncomingRecord = (recordType, record) => {
canBeBlank: ['user_ID', 'network_ID'],
},
Requisition: {
cannotBeBlank: ['status', 'type', 'daysToSupply'],
cannotBeBlank: ['name_ID', 'store_ID', 'status', 'type', 'daysToSupply'],
canBeBlank: ['date_entered', 'serial_number', 'requester_reference', 'programID', 'periodID'],
},
RequisitionItem: {
Expand Down Expand Up @@ -675,19 +675,21 @@ export const createOrUpdateRecord = (database, settings, recordType, record) =>
break;
}
case 'Requisition': {
let status = REQUISITION_STATUSES.translate(record.status, EXTERNAL_TO_INTERNAL);
let period;
// If not a special 'wp' or 'wf' status, use the normal status translation.
if (!status) {
status = STATUSES.translate(record.status, EXTERNAL_TO_INTERNAL);
}
if (record.periodID) {
period = database.getOrCreate('Period', record.periodID);
}
if (record.store_ID !== settings.get(THIS_STORE_ID)) break; // Not for this store

const status = REQUISITION_STATUSES.translate(record.status, EXTERNAL_TO_INTERNAL);
if (!status) break; // Must be a requisition status, either 'suggested' or 'finalised'
const period = database.getOrCreate('Period', record.periodID);

// Look for transactions that are waiting for this requisition to be linked
const pendingTransactions = database
.objects('Transaction')
.filtered('pendingRequisitionId == $0', record.ID);
const linkedTransaction = pendingTransactions.length > 0 ? pendingTransactions[0] : null;

internalRecord = {
id: record.ID,
status: REQUISITION_STATUSES.translate(record.status, EXTERNAL_TO_INTERNAL),
status,
entryDate: parseDate(record.date_entered),
daysToSupply: parseNumber(record.daysToSupply),
serialNumber: record.serial_number,
Expand All @@ -703,13 +705,26 @@ export const createOrUpdateRecord = (database, settings, recordType, record) =>
customData: parseJsonString(record.custom_data),
isRemoteOrder: parseBoolean(record.isRemoteOrder),
createdDate: parseDate(record.date_order_received),
linkedTransaction,
};
const requisition = database.update(recordType, internalRecord);

if (linkedTransaction) {
database.update('Transaction', {
id: linkedTransaction.id,
linkedRequisition: requisition,
pendingRequisitionId: null,
});
}
if (period) period.addRequisitionIfUnique(requisition);
break;
}
case 'RequisitionItem': {
const requisition = database.getOrCreate('Requisition', record.requisition_ID);
const requisition = database.get('Requisition', record.requisition_ID);
// Technically, if the requisition is null, this item becomes an orphan record
// and shouldn't be saved.
// But for the history of the requisition and to investigate the orphaned item,
// we will save it.
internalRecord = {
id: record.ID,
requisition,
Expand All @@ -729,9 +744,12 @@ export const createOrUpdateRecord = (database, settings, recordType, record) =>
option: database.getOrCreate('Options', record.optionID),
};
const requisitionItem = database.update(recordType, internalRecord);
// requisitionItem will be an orphan record if it's not unique?
requisition.addItemIfUnique(requisitionItem);
database.save('Requisition', requisition);

if (requisition) {
// requisitionItem will be an orphan record if it's not unique?
requisition.addItemIfUnique(requisitionItem);
database.save('Requisition', requisition);
}
break;
}
case 'Stocktake': {
Expand Down Expand Up @@ -795,9 +813,7 @@ export const createOrUpdateRecord = (database, settings, recordType, record) =>
if (record.store_ID !== settings.get(THIS_STORE_ID)) break; // Not for this store
const otherParty = database.getOrCreate('Name', record.name_ID);
const enteredBy = database.getOrCreate('User', record.user_ID);
const linkedRequisition = record.requisition_ID
? database.get('Requisition', record.requisition_ID)
: null;
const linkedRequisition = database.get('Requisition', record.requisition_ID);
const linkedTransaction = record.linked_transaction_id
? database.getOrCreate('Transaction', record.linked_transaction_id)
: null;
Expand All @@ -822,6 +838,7 @@ export const createOrUpdateRecord = (database, settings, recordType, record) =>
mode: record.mode,
prescriber: database.getOrCreate('Prescriber', record.prescriber_ID),
linkedRequisition,
pendingRequisitionId: record.requisition_ID,
subtotal: parseFloat(record.subtotal),
outstanding: parseFloat(record.total),
insurancePolicy,
Expand Down
3 changes: 2 additions & 1 deletion src/sync/outgoingSyncUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,13 @@ const generateSyncData = (settings, recordType, record) => {
return nameRecord;
}
case 'Requisition': {
const status = REQUISITION_STATUSES.translate(record.status, INTERNAL_TO_EXTERNAL);
return {
ID: record.id,
date_entered: getDateString(record.entryDate),
user_ID: record.enteredById,
name_ID: record.otherStoreName?.id ?? '',
status: REQUISITION_STATUSES.translate(record.status, INTERNAL_TO_EXTERNAL),
status: status ?? 'sg', // If undefined, probably means the requisition is new. Set to 'sg'.
daysToSupply: String(record.daysToSupply),
store_ID: settings.get(THIS_STORE_ID),
serial_number: record.serialNumber,
Expand Down
3 changes: 1 addition & 2 deletions src/sync/syncTranslators.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,11 @@ export const NAME_TYPES = new SyncTranslator({
*/
class RequisitionStatusTranslator extends SyncTranslator {
translate(status, direction) {
if (['cn', 'wf'].includes(status)) return 'finalised';
if (status === 'cn') return 'finalised';
return super.translate(status, direction);
}
}
export const REQUISITION_STATUSES = new RequisitionStatusTranslator({
new: 'wp', // 'wp', 'wf', 'cn' should never be returned in api/v3.
suggested: 'sg',
finalised: 'fn',
});
Expand Down