diff --git a/build/entitlements.mac.plist b/build/entitlements.mac.plist new file mode 100644 index 00000000..d6b93bc0 --- /dev/null +++ b/build/entitlements.mac.plist @@ -0,0 +1,8 @@ + + + + + com.apple.security.cs.allow-unsigned-executable-memory + + + diff --git a/build/icon.ico b/build/icon.ico index 24ffb76b..24e3b073 100644 Binary files a/build/icon.ico and b/build/icon.ico differ diff --git a/main.js b/main.js index babcfd86..8cb3189d 100644 --- a/main.js +++ b/main.js @@ -28,6 +28,16 @@ try { // use server for route mainWindow.loadURL(`http://localhost:${env.PORT}`) + + mainWindow.webContents.session.on( + 'will-download', + (event, downloadItem, webContents) => { + downloadItem.setSaveDialogOptions({ + filters: [{ name: 'CSV', extensions: ['csv'] }], + message: 'Download CSV', + }) + } + ) } app.on('ready', createWindow) diff --git a/package.json b/package.json index ce50b21e..4665989f 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,15 @@ "private": true, "build": { "productName": "Project Reserve", - "appId": "your.id", + "appId": "project.reserve", "mac": { - "category": "your.app.category.type" + }, + "win": { + "target": [ + { + "target": "nsis" + } + ] }, "files": [ "build/*", @@ -33,7 +39,7 @@ "build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.config.js --progress", "build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.config.js --progress", "pack": "electron-builder --dir", - "dist": "yarn build && DEBUG=electron-builder electron-builder", + "dist": "yarn build && DEBUG=electron-builder electron-builder --win --mac", "postinstall": "electron-builder install-app-deps" }, "dependencies": { @@ -45,6 +51,8 @@ "body-parser": "^1.19.0", "compression": "^1.7.4", "dotenv": "^8.2.0", + "electron-builder-squirrel-windows": "^22.10.4", + "electron-notarize": "^1.0.0", "express": "^4.17.1", "http": "^0.0.1-security", "isomorphic-fetch": "^3.0.0", diff --git a/src/api/main/routes/configurations.js b/src/api/main/routes/configurations.js index 7170cb3a..092f6637 100644 --- a/src/api/main/routes/configurations.js +++ b/src/api/main/routes/configurations.js @@ -24,6 +24,7 @@ router.get("/configurations/:id", async (req, res) => { { model: db.reserveCategory, as: "reserveCategories", + order: [['reserveCatetories.`order`', 'ASC']], include: [ { model: db.priority, @@ -31,6 +32,7 @@ router.get("/configurations/:id", async (req, res) => { { model: db.categoryCriteria, as: "categoryCriteria", + order: [['order', 'ASC']], include: [ { model: db.categoryCriteriaElement, @@ -41,6 +43,7 @@ router.get("/configurations/:id", async (req, res) => { { model: db.numericCriteria, as: "numericCriteria", + order: [['order', 'ASC']], include: [ { model: db.numericCriteriaBucket, @@ -51,6 +54,7 @@ router.get("/configurations/:id", async (req, res) => { ], }, ], + }, ], }) @@ -106,6 +110,7 @@ router.get("/configurations/:id/fieldNames", async (req, res) => { where: { configurationId, isDefault: false }, }); + const categoryCriteriaFields = await db.sequelize.query( ` SELECT id, name FROM category_criteria @@ -118,6 +123,30 @@ router.get("/configurations/:id/fieldNames", async (req, res) => { ); + const categoryCriteriaFieldsDefault = await db.sequelize.query( + ` + SELECT id, name FROM category_criteria + WHERE priority_id IN ( + SELECT id FROM priority + WHERE reserve_category_id in (SELECT id FROM reserve_category WHERE configuration_id = ${configurationId} AND is_default) + ); + `, + { type: SELECT } + ); + + + const categoryCriteriaFieldsNotDefault = await db.sequelize.query( + ` + SELECT id, name FROM category_criteria + WHERE priority_id IN ( + SELECT id FROM priority + WHERE reserve_category_id in (SELECT id FROM reserve_category WHERE configuration_id = ${configurationId} AND NOT is_default) + ); + `, + { type: SELECT } + ); + + const possibleValues = await Promise.all(categoryCriteriaFields[0].map(async (criteria) => { const values = await db.sequelize.query( ` @@ -140,17 +169,29 @@ router.get("/configurations/:id/fieldNames", async (req, res) => { }, {}) - const numericCriteriaFields = await db.sequelize.query( + const numericCriteriaFieldsRequired = await db.sequelize.query( ` SELECT name, min, max FROM numeric_criteria WHERE priority_id IN ( SELECT id FROM priority - WHERE reserve_category_id in (SELECT id FROM reserve_category WHERE configuration_id = ${configurationId}) + WHERE reserve_category_id in (SELECT id FROM reserve_category WHERE configuration_id = ${configurationId} AND is_default) + ); + `, + { type: SELECT } + ); + +const numericCriteriaFieldsNotRequired = await db.sequelize.query( + ` + SELECT name, min, max FROM numeric_criteria + WHERE priority_id IN ( + SELECT id FROM priority + WHERE reserve_category_id in (SELECT id FROM reserve_category WHERE configuration_id = ${configurationId} AND NOT is_default) ); `, { type: SELECT } ); + const names = new Set() const fieldNames = [ @@ -159,14 +200,27 @@ router.get("/configurations/:id/fieldNames", async (req, res) => { reserveCategoryNames.forEach((cat) => { if (!names.has(cat.name)) { fieldNames.push({ - name: "is_" + cat.name.toLowerCase().split(" ").join("_"), + name: "is_eligible_for_reserve_cat_" + cat.name.toLowerCase().split(" ").join("_"), required: true, dataType: "BOOLEAN", }) names.add(cat.name) } }); - categoryCriteriaFields[0].forEach((criteria) => { + + categoryCriteriaFieldsDefault[0].forEach((criteria) => { + if (!names.has(criteria.name)) { + fieldNames.push({ + name: criteria.name.toLowerCase().split(" ").join("_"), + required: true, + dataType: "STRING", + possibleValues: possibleValuesMap[criteria.id] + }) + names.add(criteria.name) + } + }); + + categoryCriteriaFieldsNotDefault[0].forEach((criteria) => { if (!names.has(criteria.name)) { fieldNames.push({ name: criteria.name.toLowerCase().split(" ").join("_"), @@ -177,7 +231,20 @@ router.get("/configurations/:id/fieldNames", async (req, res) => { names.add(criteria.name) } }); - numericCriteriaFields[0].forEach((criteria) => { + + numericCriteriaFieldsRequired[0].forEach((criteria) => { + if (!names.has(criteria.name)) { + fieldNames.push({ + name: criteria.name.toLowerCase().split(" ").join("_"), + required: true, + dataType: "NUMBER", + possibleValues: { min: criteria.min, max: criteria.max } + }) + names.add(criteria.name) + } + }); + + numericCriteriaFieldsNotRequired[0].forEach((criteria) => { if (!names.has(criteria.name)) { fieldNames.push({ name: criteria.name.toLowerCase().split(" ").join("_"), @@ -189,6 +256,9 @@ router.get("/configurations/:id/fieldNames", async (req, res) => { } }); + + + res.json(fieldNames); }); diff --git a/src/api/main/routes/patients.js b/src/api/main/routes/patients.js index 9d1c8a4c..6656eea6 100755 --- a/src/api/main/routes/patients.js +++ b/src/api/main/routes/patients.js @@ -47,8 +47,8 @@ router.post("/patients", async (req, res) => { // add reserve categories const reserveCategoryFieldNames = fields - .filter((f) => f.startsWith("is_") && rawPat[f]) - .map((f) => f.substr(3)); + .filter((f) => f.startsWith("is_eligible_for_reserve_cat_") && rawPat[f]) + .map((f) => f.substr(28)); const reserveCategories = db.reserveCategory.findAll({ where: { configurationId, @@ -76,7 +76,7 @@ router.post("/patients", async (req, res) => { // add numeric criteria values const possibleNumericCriteriaFields = fields.filter( - (f) => !f.startsWith("is_") + (f) => !f.startsWith("is_eligible_for_reserve_cat_") ); const numericCriteria = db.numericCriteria.findAll({ where: { @@ -129,7 +129,7 @@ router.post("/patients", async (req, res) => { // add category criteria values const possibleCategoryCriteriaFields = fields.filter( - (f) => !f.startsWith("is_") + (f) => !f.startsWith("is_eligible_for_reserve_cat_") ); const categoryCriteria = db.categoryCriteria.findAll({ where: { name: { [Op.in]: possibleCategoryCriteriaFields } }, diff --git a/src/api/main/routes/source_files.js b/src/api/main/routes/source_files.js index 71eae7db..79f1aee5 100644 --- a/src/api/main/routes/source_files.js +++ b/src/api/main/routes/source_files.js @@ -36,7 +36,7 @@ router.get('/sourceFiles/:id', async (req, res) => { return res.json(await db.sourceFile.findOne({ where: { id } })) }) -// GET one source file id +// GET nth reserve patients router.get('/sourceFiles/:id/nthReservePatients', async (req, res) => { const { db } = req const id = req.params.id @@ -47,6 +47,13 @@ router.get('/sourceFiles/:id/nthReservePatients', async (req, res) => { ) }) +// GET the number of left over units for reserve instance +router.get('/sourceFiles/:id/leftOver', async (req, res) => { + const { db } = req + const id = req.params.id + return res.json((await db.sourceFile.findOne({ where: { id } })).left_over) +}) + // GET all patients for a source file router.get('/sourceFiles/:id/patients', async (req, res) => { const { db } = req @@ -55,7 +62,7 @@ router.get('/sourceFiles/:id/patients', async (req, res) => { if (req.query.givenUnit) filterLosers += `and ${ req.query.givenUnit === 'false' ? 'not' : '' - } given_unit` + } given_unit` return res.json(await getPatientsWithAttributes(db, id, filterLosers)) }) @@ -85,7 +92,6 @@ router.post('/sourceFiles/:id/process', async (req, res) => { }) ) - const patients = await Promise.all( ( await db.patient.findAll({ @@ -105,17 +111,13 @@ router.post('/sourceFiles/:id/process', async (req, res) => { let given = 0 let i = 0 while (i < f.patients.length) { - - if (given < f.size && !selectedPatients.has(f.patients[i])) { - selectedPatients.add(f.patients[i]) allocatedPatientGroups[f.patients[i]] = f.name given += 1 notSelectedPatients.delete(f.patients[i]) if (given == f.size) { - nthReservePatients.push({ name: f.name, nthRecipientPrimaryId: f.patients[i], @@ -128,20 +130,8 @@ router.post('/sourceFiles/:id/process', async (req, res) => { leftOver += f.size - given }) - const notSelectedPatientsArray = Array.from(notSelectedPatients) - let i = 0 - // give left over to unallocated patients if there are any - while (leftOver > 0 && i < notSelectedPatientsArray.length) { - const pat = notSelectedPatientsArray[i] - selectedPatients.add(pat) - allocatedPatientGroups[pat] = 'None' - leftOver -= 1 - i += 1 - } - // update patients selectedPatients.forEach(async (pId) => { - const patient = await db.patient.findOne({ where: { id: pId } }) patient.given_unit = true patient.group_allocated_under = allocatedPatientGroups[pId] @@ -150,9 +140,9 @@ router.post('/sourceFiles/:id/process', async (req, res) => { const nthReservePatientsWithNames = await Promise.all( nthReservePatients.map(async (f) => { - const name = ( - await db.patient.findOne({ where: { id: f.nthRecipientPrimaryId } }) - ) + const name = await db.patient.findOne({ + where: { id: f.nthRecipientPrimaryId }, + }) return { name: f.name, nthRecipientId: name.dataValues.recipient_id } }) diff --git a/src/notarize.js b/src/notarize.js new file mode 100644 index 00000000..09a133e6 --- /dev/null +++ b/src/notarize.js @@ -0,0 +1,18 @@ +require('dotenv').config(); +const { notarize } = require('electron-notarize'); + +exports.default = async function notarizing(context) { + const { electronPlatformName, appOutDir } = context; + if (electronPlatformName !== 'darwin') { + return; + } + + const appName = context.packager.appInfo.productFilename; + + return await notarize({ + appBundleId: 'project.reserve', + appPath: `${appOutDir}/${appName}.app`, + appleId: process.env.APPLEID, + appleIdPassword: process.env.APPLEIDPASS, + }); +}; diff --git a/src/public/components/ConfigSummary.vue b/src/public/components/ConfigSummary.vue index 2064e820..25c1c4f1 100644 --- a/src/public/components/ConfigSummary.vue +++ b/src/public/components/ConfigSummary.vue @@ -115,7 +115,7 @@ export default { }, } -