From 35b1b6c36220b8af1a83194b7923cf7f301f2a5d Mon Sep 17 00:00:00 2001 From: Tim Kent Date: Wed, 6 Dec 2023 08:44:49 -0800 Subject: [PATCH 01/11] add new model template --- src/models/bmdashboard/buildingReusable.js | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/models/bmdashboard/buildingReusable.js diff --git a/src/models/bmdashboard/buildingReusable.js b/src/models/bmdashboard/buildingReusable.js new file mode 100644 index 000000000..1d0503010 --- /dev/null +++ b/src/models/bmdashboard/buildingReusable.js @@ -0,0 +1,29 @@ +const mongoose = require('mongoose'); + +const { Schema } = mongoose; + +const buildingReusable = new Schema({ + itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, + project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, + stockBought: { type: Number, default: 0 }, + stockDestroyed: { type: Number, default: 0 }, + stockAvailable: { type: Number, default: 0 }, + purchaseRecord: [{ + _id: false, // do not add _id field to subdocument + date: { type: Date, default: Date.now() }, + requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + quantity: { type: Number, required: true }, + priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, + brand: String, + status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, + }], + updateRecord: [{ + _id: false, + date: { type: Date, required: true }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + quantityUsed: { type: Number, required: true }, + quantityDestroyed: { type: Number, required: true }, + }], +}); + +module.exports = mongoose.model('buildingReusable', buildingReusable, 'buildingReusables'); From 9e5ae9875eb5422b5d6ec260d5617ab314f905a4 Mon Sep 17 00:00:00 2001 From: Tim Kent Date: Wed, 6 Dec 2023 20:01:06 -0800 Subject: [PATCH 02/11] experimenting with discriminators --- .../bmdashboard/bmMaterialsController.js | 12 ++++- src/models/bmdashboard/buildingMaterial.js | 49 +++++++++++++++++-- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/controllers/bmdashboard/bmMaterialsController.js b/src/controllers/bmdashboard/bmMaterialsController.js index a8090ebd9..c3dcd8c69 100644 --- a/src/controllers/bmdashboard/bmMaterialsController.js +++ b/src/controllers/bmdashboard/bmMaterialsController.js @@ -47,6 +47,8 @@ const bmMaterialsController = function (ItemMaterial, BuildingMaterial) { }; const bmPurchaseMaterials = async function (req, res) { + console.log(BuildingMaterial); + console.log(req.body); const { projectId, matTypeId, @@ -83,7 +85,10 @@ const bmMaterialsController = function (ItemMaterial, BuildingMaterial) { }; BuildingMaterial .create(newDoc) - .then(() => res.status(201).send()) + .then((result) => { + console.log('result new: ', result); + res.status(201).send(); + }) .catch(error => res.status(500).send(error)); return; } @@ -93,7 +98,10 @@ const bmMaterialsController = function (ItemMaterial, BuildingMaterial) { { $push: { purchaseRecord: newPurchaseRecord } }, ) .exec() - .then(() => res.status(201).send()) + .then((result) => { + console.log('result old: ', result); + res.status(201).send(); + }) .catch(error => res.status(500).send(error)); } catch (error) { res.status(500).send(error); diff --git a/src/models/bmdashboard/buildingMaterial.js b/src/models/bmdashboard/buildingMaterial.js index 4170443e0..d9a6be194 100644 --- a/src/models/bmdashboard/buildingMaterial.js +++ b/src/models/bmdashboard/buildingMaterial.js @@ -2,7 +2,14 @@ const mongoose = require('mongoose'); const { Schema } = mongoose; -const buildingMaterial = new Schema({ +const inventoryBaseSchema = new Schema({ + testField1: { type: String, default: 'hello world' }, + testField2: { type: Number, default: 101 }, +}); + +const InvBase = mongoose.model('InvBase', inventoryBaseSchema); + +const buildingMaterial = InvBase.discriminator('buildingMaterial', new Schema({ itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project @@ -25,6 +32,42 @@ const buildingMaterial = new Schema({ quantityUsed: { type: Number, required: true }, quantityWasted: { type: Number, required: true }, }], -}); +})); + +// common fields +const eventDefinition = { time: Date } +// specific fields +const ClickedLinkEventDefinition = {...eventDefinition, url: String} + +// completely separate models and collections on db level +const eventSchema = new mongoose.Schema(eventDefinition, options); +const Event = mongoose.model('Event', eventSchema); + +const ClickedLinkEvent = new mongoose.Schema(ClickedLinkEventDefinition , options); + +// const buildingMaterial = new Schema({ +// itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, +// project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, +// stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project +// stockUsed: { type: Number, default: 0 }, // total amount of item used successfully in the project +// stockWasted: { type: Number, default: 0 }, // total amount of item wasted/ruined/lost in the project +// stockAvailable: { type: Number, default: 0 }, // bought - (used + wasted) +// purchaseRecord: [{ +// _id: false, // do not add _id field to subdocument +// date: { type: Date, default: Date.now() }, +// requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, +// quantity: { type: Number, required: true }, +// priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, +// brand: String, +// status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, +// }], +// updateRecord: [{ +// _id: false, +// date: { type: Date, required: true }, +// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, +// quantityUsed: { type: Number, required: true }, +// quantityWasted: { type: Number, required: true }, +// }], +// }); -module.exports = mongoose.model('buildingMaterial', buildingMaterial, 'buildingMaterials'); +module.exports = buildingMaterial; From d54cc867e1efe980f5bfcc955ddfff34d5414802 Mon Sep 17 00:00:00 2001 From: Tim Kent Date: Thu, 7 Dec 2023 16:57:12 -0800 Subject: [PATCH 03/11] add baseInvSchema file, update material and reusable schemas with discriminators --- .../bmdashboard/bmMaterialsController.js | 14 +--- src/models/bmdashboard/baseInvSchema.js | 23 +++++++ src/models/bmdashboard/buildingMaterial.js | 66 +++---------------- src/models/bmdashboard/buildingReusable.js | 22 ++----- 4 files changed, 41 insertions(+), 84 deletions(-) create mode 100644 src/models/bmdashboard/baseInvSchema.js diff --git a/src/controllers/bmdashboard/bmMaterialsController.js b/src/controllers/bmdashboard/bmMaterialsController.js index c3dcd8c69..c79cbb56c 100644 --- a/src/controllers/bmdashboard/bmMaterialsController.js +++ b/src/controllers/bmdashboard/bmMaterialsController.js @@ -47,8 +47,6 @@ const bmMaterialsController = function (ItemMaterial, BuildingMaterial) { }; const bmPurchaseMaterials = async function (req, res) { - console.log(BuildingMaterial); - console.log(req.body); const { projectId, matTypeId, @@ -60,7 +58,7 @@ const bmMaterialsController = function (ItemMaterial, BuildingMaterial) { const newPurchaseRecord = { quantity, priority, - brand, + brandPref: brand, requestedBy: requestorId, }; try { @@ -85,10 +83,7 @@ const bmMaterialsController = function (ItemMaterial, BuildingMaterial) { }; BuildingMaterial .create(newDoc) - .then((result) => { - console.log('result new: ', result); - res.status(201).send(); - }) + .then(() => res.status(201).send()) .catch(error => res.status(500).send(error)); return; } @@ -98,10 +93,7 @@ const bmMaterialsController = function (ItemMaterial, BuildingMaterial) { { $push: { purchaseRecord: newPurchaseRecord } }, ) .exec() - .then((result) => { - console.log('result old: ', result); - res.status(201).send(); - }) + .then(() => res.status(201).send()) .catch(error => res.status(500).send(error)); } catch (error) { res.status(500).send(error); diff --git a/src/models/bmdashboard/baseInvSchema.js b/src/models/bmdashboard/baseInvSchema.js new file mode 100644 index 000000000..7eff943e7 --- /dev/null +++ b/src/models/bmdashboard/baseInvSchema.js @@ -0,0 +1,23 @@ +const mongoose = require('mongoose'); + +// base schema for all categories of inventory (Consumable, Material, Reusable, Tool, Equipment) +// this schema is extended by the individual schemas for each inventory type +// all documents derived from this schema are saved to the collection 'buildingInventoryItems' + +const baseInvSchema = mongoose.Schema({ + itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, + project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, + purchaseRecord: [{ + _id: false, // do not add _id field to subdocument + date: { type: Date, default: Date.now() }, + requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + quantity: { type: Number, required: true }, + priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, + brandPref: String, + status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, + }], +}); + +const baseInv = mongoose.model('buildingInventory', baseInvSchema, 'buildingInventoryItems'); + +module.exports = baseInv; diff --git a/src/models/bmdashboard/buildingMaterial.js b/src/models/bmdashboard/buildingMaterial.js index d9a6be194..caf61e4c0 100644 --- a/src/models/bmdashboard/buildingMaterial.js +++ b/src/models/bmdashboard/buildingMaterial.js @@ -1,73 +1,23 @@ const mongoose = require('mongoose'); -const { Schema } = mongoose; +const baseInv = require('./baseInvSchema'); -const inventoryBaseSchema = new Schema({ - testField1: { type: String, default: 'hello world' }, - testField2: { type: Number, default: 101 }, -}); +// inherits all properties of baseInv schema using discriminator +// each document derived from this schema includes key field { __t: "buildingMaterial" } -const InvBase = mongoose.model('InvBase', inventoryBaseSchema); - -const buildingMaterial = InvBase.discriminator('buildingMaterial', new Schema({ - itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, - project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, +const buildingMaterial = baseInv.discriminator('buildingMaterial', new mongoose.Schema({ stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project - stockUsed: { type: Number, default: 0 }, // total amount of item used successfully in the project - stockWasted: { type: Number, default: 0 }, // total amount of item wasted/ruined/lost in the project - stockAvailable: { type: Number, default: 0 }, // bought - (used + wasted) - purchaseRecord: [{ - _id: false, // do not add _id field to subdocument - date: { type: Date, default: Date.now() }, - requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - quantity: { type: Number, required: true }, - priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, - brand: String, - status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, - }], + stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused + stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock + stockAvailable: { type: Number, default: 0 }, // available = bought - (used + wasted/destroyed) updateRecord: [{ _id: false, date: { type: Date, required: true }, createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, quantityUsed: { type: Number, required: true }, quantityWasted: { type: Number, required: true }, + test: { type: String, default: 'testing this field' }, }], })); -// common fields -const eventDefinition = { time: Date } -// specific fields -const ClickedLinkEventDefinition = {...eventDefinition, url: String} - -// completely separate models and collections on db level -const eventSchema = new mongoose.Schema(eventDefinition, options); -const Event = mongoose.model('Event', eventSchema); - -const ClickedLinkEvent = new mongoose.Schema(ClickedLinkEventDefinition , options); - -// const buildingMaterial = new Schema({ -// itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, -// project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, -// stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project -// stockUsed: { type: Number, default: 0 }, // total amount of item used successfully in the project -// stockWasted: { type: Number, default: 0 }, // total amount of item wasted/ruined/lost in the project -// stockAvailable: { type: Number, default: 0 }, // bought - (used + wasted) -// purchaseRecord: [{ -// _id: false, // do not add _id field to subdocument -// date: { type: Date, default: Date.now() }, -// requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, -// quantity: { type: Number, required: true }, -// priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, -// brand: String, -// status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, -// }], -// updateRecord: [{ -// _id: false, -// date: { type: Date, required: true }, -// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, -// quantityUsed: { type: Number, required: true }, -// quantityWasted: { type: Number, required: true }, -// }], -// }); - module.exports = buildingMaterial; diff --git a/src/models/bmdashboard/buildingReusable.js b/src/models/bmdashboard/buildingReusable.js index 1d0503010..1f2ceaa48 100644 --- a/src/models/bmdashboard/buildingReusable.js +++ b/src/models/bmdashboard/buildingReusable.js @@ -1,22 +1,14 @@ const mongoose = require('mongoose'); -const { Schema } = mongoose; +const baseInv = require('./baseInvSchema'); -const buildingReusable = new Schema({ - itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, - project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, +// inherits all properties of baseInv schema using discriminator +// each document derived from this schema includes key field { __t: "buildingReusable" } + +const buildingReusable = baseInv.discriminator('buildingReusable', new mongoose.Schema({ stockBought: { type: Number, default: 0 }, stockDestroyed: { type: Number, default: 0 }, stockAvailable: { type: Number, default: 0 }, - purchaseRecord: [{ - _id: false, // do not add _id field to subdocument - date: { type: Date, default: Date.now() }, - requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - quantity: { type: Number, required: true }, - priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, - brand: String, - status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, - }], updateRecord: [{ _id: false, date: { type: Date, required: true }, @@ -24,6 +16,6 @@ const buildingReusable = new Schema({ quantityUsed: { type: Number, required: true }, quantityDestroyed: { type: Number, required: true }, }], -}); +})); -module.exports = mongoose.model('buildingReusable', buildingReusable, 'buildingReusables'); +module.exports = buildingReusable; From 778217620b5fb6c11e3522f671e35cdd45d758ef Mon Sep 17 00:00:00 2001 From: Tim Kent Date: Fri, 8 Dec 2023 14:47:04 -0800 Subject: [PATCH 04/11] add single inventory file to hold base and extended schemas --- .../bmdashboard/buildingInventoryItem.js | 94 +++++++++++++++++++ src/startup/routes.js | 2 +- 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/models/bmdashboard/buildingInventoryItem.js diff --git a/src/models/bmdashboard/buildingInventoryItem.js b/src/models/bmdashboard/buildingInventoryItem.js new file mode 100644 index 000000000..db5c3012c --- /dev/null +++ b/src/models/bmdashboard/buildingInventoryItem.js @@ -0,0 +1,94 @@ +const mongoose = require('mongoose'); + +//---------------------- +// BASE INVENTORY SCHEMA +//---------------------- + +// base schema for all categories of inventory (Consumable, Material, Reusable, Tool, Equipment) +// this schema is extended by the individual schemas for each inventory type +// all documents derived from this schema are saved to the collection 'buildingInventoryItems' + +const baseInvSchema = mongoose.Schema({ + itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, + project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, + purchaseRecord: [{ + _id: false, // do not add _id field to subdocument + date: { type: Date, default: Date.now() }, + requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + quantity: { type: Number, required: true }, + priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, + brandPref: String, + status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, + }], +}); + +const baseInv = mongoose.model('buildingInventory', baseInvSchema, 'buildingInventoryItems'); + +//----------------- +// MATERIALS SCHEMA +//----------------- + +// inherits all properties of baseInv schema using discriminator +// each document derived from this schema includes key field { __t: "material" } +// ex: sand, stone, bricks, lumber, insulation + +const buildingMaterial = baseInv.discriminator('material', new mongoose.Schema({ + stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project + stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused + stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock + stockAvailable: { type: Number, default: 0 }, // available = bought - (used + wasted/destroyed) + updateRecord: [{ + _id: false, + date: { type: Date, required: true }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + quantityUsed: { type: Number, required: true }, + quantityWasted: { type: Number, required: true }, + }], +})); + +//----------------- +// REUSABLES SCHEMA +//----------------- + +// inherits all properties of baseInv schema using discriminator +// each document derived from this schema includes key field { __t: "reusable" } +// ex: hammers, screwdrivers, mallets, brushes, gloves + +const buildingReusable = baseInv.discriminator('reusable', new mongoose.Schema({ + stockBought: { type: Number, default: 0 }, + stockDestroyed: { type: Number, default: 0 }, + stockAvailable: { type: Number, default: 0 }, + updateRecord: [{ + _id: false, + date: { type: Date, required: true }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + quantityUsed: { type: Number, required: true }, + quantityDestroyed: { type: Number, required: true }, + }], +})); + +//----------------- +// CONSUMABLES SCHEMA +//----------------- + +// inherits all properties of baseInv schema using discriminator +// each document derived from this schema includes key field { __t: "consumable" } +// ex: screws, nails, staples + +//------------- +// TOOLS SCHEMA +//------------- + +// inherits all properties of baseInv schema using discriminator +// each document derived from this schema includes key field { __t: "tool" } +// ex: power drills, wheelbarrows, shovels + +// const buildingTool = baseInv.discriminator('tool', new mongoose.Schema({ + +// })); + + +module.exports = { + buildingMaterial, + buildingReusable, +}; diff --git a/src/startup/routes.js b/src/startup/routes.js index 2d95ea639..5f77a413d 100644 --- a/src/startup/routes.js +++ b/src/startup/routes.js @@ -24,7 +24,7 @@ const inventoryItemMaterial = require('../models/inventoryItemMaterial'); const mapLocations = require('../models/mapLocation'); const buildingProject = require('../models/bmdashboard/buildingProject'); const buildingInventoryType = require('../models/bmdashboard/buildingInventoryType'); -const buildingMaterial = require('../models/bmdashboard/buildingMaterial'); +const { buildingMaterial } = require('../models/bmdashboard/buildingInventoryItem'); const userProfileRouter = require('../routes/userProfileRouter')(userProfile); const badgeRouter = require('../routes/badgeRouter')(badge); From 5dd37d817997961c79b5db65a059d39b771116a4 Mon Sep 17 00:00:00 2001 From: Tim Kent Date: Sat, 9 Dec 2023 14:55:41 -0800 Subject: [PATCH 05/11] add building tool inv to schema file --- .../bmdashboard/buildingInventoryItem.js | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/models/bmdashboard/buildingInventoryItem.js b/src/models/bmdashboard/buildingInventoryItem.js index db5c3012c..0d357ae7a 100644 --- a/src/models/bmdashboard/buildingInventoryItem.js +++ b/src/models/bmdashboard/buildingInventoryItem.js @@ -83,9 +83,35 @@ const buildingReusable = baseInv.discriminator('reusable', new mongoose.Schema({ // each document derived from this schema includes key field { __t: "tool" } // ex: power drills, wheelbarrows, shovels -// const buildingTool = baseInv.discriminator('tool', new mongoose.Schema({ - -// })); +const buildingTool = baseInv.discriminator('tool', new mongoose.Schema({ + code: { type: Number, required: true }, // add function to create code for on-site tool tracking + purchaseStatus: { type: String, enum: ['Rental', 'Purchase'], required: true }, + // add discriminator based on rental or purchase so these fields are required if tool is rented + rentedOnDate: Date, + rentalDue: Date, + userResponsible: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + purchaseRecord: [{ // track purchase/rental requests + _id: false, // do not add _id field to subdocument + date: { type: Date, default: Date.now() }, + requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, + brand: String, + status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, + }], + updateRecord: [{ // track tool condition updates + _id: false, + date: { type: Date, default: Date.now() }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + condition: { type: String, enum: ['Good', 'Needs Repair', 'Out of Order'] }, + }], + logRecord: [{ // track tool daily check in/out and use + _id: false, + date: { type: Date, default: Date.now() }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + responsibleUser: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + type: { type: String, enum: ['Check In', 'Check Out'] }, // default = opposite of current log status? + }], +})); module.exports = { From 2a1ef65af84d0d89eea2d6fb2fcc83fd0cf98810 Mon Sep 17 00:00:00 2001 From: Tim Kent Date: Tue, 12 Dec 2023 08:51:56 -0800 Subject: [PATCH 06/11] add tool and equipment discriminators --- .../bmdashboard/buildingInventoryItem.js | 88 +++++++++++++------ 1 file changed, 61 insertions(+), 27 deletions(-) diff --git a/src/models/bmdashboard/buildingInventoryItem.js b/src/models/bmdashboard/buildingInventoryItem.js index 0d357ae7a..845243331 100644 --- a/src/models/bmdashboard/buildingInventoryItem.js +++ b/src/models/bmdashboard/buildingInventoryItem.js @@ -1,8 +1,8 @@ const mongoose = require('mongoose'); -//---------------------- -// BASE INVENTORY SCHEMA -//---------------------- +//----------------------- +// BASE INVENTORY SCHEMAS +//----------------------- // base schema for all categories of inventory (Consumable, Material, Reusable, Tool, Equipment) // this schema is extended by the individual schemas for each inventory type @@ -46,6 +46,28 @@ const buildingMaterial = baseInv.discriminator('material', new mongoose.Schema({ }], })); +//----------------- +// CONSUMABLES SCHEMA +//----------------- + +// inherits all properties of baseInv schema using discriminator +// each document derived from this schema includes key field { __t: "consumable" } +// ex: screws, nails, staples + +const buildingConsumable = baseInv.discriminator('consumable', new mongoose.Schema({ + stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project + stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused + stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock + stockAvailable: { type: Number, default: 0 }, // available = bought - (used + wasted/destroyed) + updateRecord: [{ + _id: false, + date: { type: Date, required: true }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + quantityUsed: { type: Number, required: true }, + quantityWasted: { type: Number, required: true }, + }], +})); + //----------------- // REUSABLES SCHEMA //----------------- @@ -67,54 +89,66 @@ const buildingReusable = baseInv.discriminator('reusable', new mongoose.Schema({ }], })); -//----------------- -// CONSUMABLES SCHEMA -//----------------- - -// inherits all properties of baseInv schema using discriminator -// each document derived from this schema includes key field { __t: "consumable" } -// ex: screws, nails, staples - //------------- -// TOOLS SCHEMA +// TOOL SCHEMAS //------------- // inherits all properties of baseInv schema using discriminator // each document derived from this schema includes key field { __t: "tool" } -// ex: power drills, wheelbarrows, shovels +// ex: power drills, wheelbarrows, shovels, jackhammers + +// Base Tool Schema: const buildingTool = baseInv.discriminator('tool', new mongoose.Schema({ code: { type: Number, required: true }, // add function to create code for on-site tool tracking purchaseStatus: { type: String, enum: ['Rental', 'Purchase'], required: true }, - // add discriminator based on rental or purchase so these fields are required if tool is rented - rentedOnDate: Date, - rentalDue: Date, - userResponsible: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - purchaseRecord: [{ // track purchase/rental requests - _id: false, // do not add _id field to subdocument - date: { type: Date, default: Date.now() }, - requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, - brand: String, - status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, - }], + imgUrl: String, updateRecord: [{ // track tool condition updates _id: false, date: { type: Date, default: Date.now() }, createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, condition: { type: String, enum: ['Good', 'Needs Repair', 'Out of Order'] }, }], - logRecord: [{ // track tool daily check in/out and use + logRecord: [{ // track tool daily check in/out and responsible user _id: false, date: { type: Date, default: Date.now() }, createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, responsibleUser: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - type: { type: String, enum: ['Check In', 'Check Out'] }, // default = opposite of current log status? + type: { type: String, enum: ['Check In', 'Check Out'] }, }], })); +// Rented Tool Schema: +// inherits all properties of buildingTool schema using discriminator +// each document derived from this schema includes key field { __t: "tool_rental" } + +// const buildingToolRental = buildingTool.discriminator('tool_rental', new mongoose.Schema({ +// rentedOnDate: { type: Date, required: true }, +// rentalDueDate: { type: Date, required: true }, +// })); + +//------------------ +// EQUIPMENT SCHEMAS +//------------------ + +// inherits all properties of baseInv schema using discriminator +// each document derived from this schema includes key field { __t: "equipment" } +// items in this category are assumed to be rented +// ex: tractors, excavators, bulldozers + +const buildingEquipment = baseInv.discriminator('equipment', new mongoose.Schema({ + isTracked: { type: Boolean, required: true }, + assetTracker: String, + // add rental record? +})); + +// add purchase varient instead of rental varient? module.exports = { buildingMaterial, + buildingConsumable, buildingReusable, + buildingTool, + // buildingToolRental, + buildingEquipment, }; From ef31db0d43ac6f8c2c6f79b6b9d859502776547a Mon Sep 17 00:00:00 2001 From: Tim Kent Date: Tue, 12 Dec 2023 10:12:45 -0800 Subject: [PATCH 07/11] delete redundant files. add basic equipment schema --- src/models/bmdashboard/baseInvSchema.js | 23 ----------- .../bmdashboard/buildingInventoryItem.js | 41 ++++++++++++------- src/models/bmdashboard/buildingReusable.js | 21 ---------- 3 files changed, 26 insertions(+), 59 deletions(-) delete mode 100644 src/models/bmdashboard/baseInvSchema.js delete mode 100644 src/models/bmdashboard/buildingReusable.js diff --git a/src/models/bmdashboard/baseInvSchema.js b/src/models/bmdashboard/baseInvSchema.js deleted file mode 100644 index 7eff943e7..000000000 --- a/src/models/bmdashboard/baseInvSchema.js +++ /dev/null @@ -1,23 +0,0 @@ -const mongoose = require('mongoose'); - -// base schema for all categories of inventory (Consumable, Material, Reusable, Tool, Equipment) -// this schema is extended by the individual schemas for each inventory type -// all documents derived from this schema are saved to the collection 'buildingInventoryItems' - -const baseInvSchema = mongoose.Schema({ - itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, - project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, - purchaseRecord: [{ - _id: false, // do not add _id field to subdocument - date: { type: Date, default: Date.now() }, - requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - quantity: { type: Number, required: true }, - priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, - brandPref: String, - status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, - }], -}); - -const baseInv = mongoose.model('buildingInventory', baseInvSchema, 'buildingInventoryItems'); - -module.exports = baseInv; diff --git a/src/models/bmdashboard/buildingInventoryItem.js b/src/models/bmdashboard/buildingInventoryItem.js index 845243331..ed9c52ebc 100644 --- a/src/models/bmdashboard/buildingInventoryItem.js +++ b/src/models/bmdashboard/buildingInventoryItem.js @@ -15,7 +15,7 @@ const baseInvSchema = mongoose.Schema({ _id: false, // do not add _id field to subdocument date: { type: Date, default: Date.now() }, requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - quantity: { type: Number, required: true }, + quantity: { type: Number, required: true, default: 1 }, // default 1 for tool or equipment purchases priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, brandPref: String, status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, @@ -100,8 +100,11 @@ const buildingReusable = baseInv.discriminator('reusable', new mongoose.Schema({ // Base Tool Schema: const buildingTool = baseInv.discriminator('tool', new mongoose.Schema({ - code: { type: Number, required: true }, // add function to create code for on-site tool tracking + code: { type: Number, required: true }, // TODO: add function to create simple numeric code for on-site tool tracking purchaseStatus: { type: String, enum: ['Rental', 'Purchase'], required: true }, + // rental fields are required if purchaseStatus = "Rental" (hopefully correct syntax) + rentedOnDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, + rentalDueDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, imgUrl: String, updateRecord: [{ // track tool condition updates _id: false, @@ -118,15 +121,6 @@ const buildingTool = baseInv.discriminator('tool', new mongoose.Schema({ }], })); -// Rented Tool Schema: -// inherits all properties of buildingTool schema using discriminator -// each document derived from this schema includes key field { __t: "tool_rental" } - -// const buildingToolRental = buildingTool.discriminator('tool_rental', new mongoose.Schema({ -// rentedOnDate: { type: Date, required: true }, -// rentalDueDate: { type: Date, required: true }, -// })); - //------------------ // EQUIPMENT SCHEMAS //------------------ @@ -137,9 +131,27 @@ const buildingTool = baseInv.discriminator('tool', new mongoose.Schema({ // ex: tractors, excavators, bulldozers const buildingEquipment = baseInv.discriminator('equipment', new mongoose.Schema({ - isTracked: { type: Boolean, required: true }, - assetTracker: String, - // add rental record? + isTracked: { type: Boolean, required: true }, // has asset tracker + assetTracker: { type: String, required: () => this.isTracked }, // required if isTracked = true (syntax?) + code: { type: Number, required: true }, // TODO: add function to create simple numeric code for on-site tool tracking + purchaseStatus: { type: String, enum: ['Rental', 'Purchase'], required: true }, + // rental fields are required if purchaseStatus = "Rental" (hopefully correct syntax) + rentedOnDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, + rentalDueDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, + imgUrl: String, + updateRecord: [{ // track equipment condition updates + _id: false, + date: { type: Date, default: Date.now() }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + condition: { type: String, enum: ['Good', 'Needs Repair', 'Out of Order'] }, + }], + logRecord: [{ // track tool daily check in/out and responsible user + _id: false, + date: { type: Date, default: Date.now() }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + responsibleUser: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + type: { type: String, enum: ['Check In', 'Check Out'] }, + }], })); // add purchase varient instead of rental varient? @@ -149,6 +161,5 @@ module.exports = { buildingConsumable, buildingReusable, buildingTool, - // buildingToolRental, buildingEquipment, }; diff --git a/src/models/bmdashboard/buildingReusable.js b/src/models/bmdashboard/buildingReusable.js deleted file mode 100644 index 1f2ceaa48..000000000 --- a/src/models/bmdashboard/buildingReusable.js +++ /dev/null @@ -1,21 +0,0 @@ -const mongoose = require('mongoose'); - -const baseInv = require('./baseInvSchema'); - -// inherits all properties of baseInv schema using discriminator -// each document derived from this schema includes key field { __t: "buildingReusable" } - -const buildingReusable = baseInv.discriminator('buildingReusable', new mongoose.Schema({ - stockBought: { type: Number, default: 0 }, - stockDestroyed: { type: Number, default: 0 }, - stockAvailable: { type: Number, default: 0 }, - updateRecord: [{ - _id: false, - date: { type: Date, required: true }, - createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - quantityUsed: { type: Number, required: true }, - quantityDestroyed: { type: Number, required: true }, - }], -})); - -module.exports = buildingReusable; From 6cc5807680d68381b3446d49eaca951f51b33ea3 Mon Sep 17 00:00:00 2001 From: Tim Kent Date: Tue, 12 Dec 2023 12:03:57 -0800 Subject: [PATCH 08/11] experimenting with new schema varient --- .../bmdashboard/buildingInventoryItem.js | 217 ++++++++++++------ 1 file changed, 146 insertions(+), 71 deletions(-) diff --git a/src/models/bmdashboard/buildingInventoryItem.js b/src/models/bmdashboard/buildingInventoryItem.js index ed9c52ebc..a9559a45d 100644 --- a/src/models/bmdashboard/buildingInventoryItem.js +++ b/src/models/bmdashboard/buildingInventoryItem.js @@ -8,9 +8,28 @@ const mongoose = require('mongoose'); // this schema is extended by the individual schemas for each inventory type // all documents derived from this schema are saved to the collection 'buildingInventoryItems' -const baseInvSchema = mongoose.Schema({ +// const baseInvSchema = mongoose.Schema({ +// itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, +// project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, +// purchaseRecord: [{ +// _id: false, // do not add _id field to subdocument +// date: { type: Date, default: Date.now() }, +// requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, +// quantity: { type: Number, required: true, default: 1 }, // default 1 for tool or equipment purchases +// priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, +// brandPref: String, +// status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, +// }], +// }); + +// const baseInv = mongoose.model('buildingInventory', baseInvSchema, 'buildingInventoryItems'); + +const baseSchemaForMaterialReusableConsumable = mongoose.Schema({ itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, + stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project + // TODO: can stockAvailable default be a function? + stockAvailable: { type: Number, default: 0 }, // available = bought - (used + wasted/destroyed) purchaseRecord: [{ _id: false, // do not add _id field to subdocument date: { type: Date, default: Date.now() }, @@ -20,9 +39,41 @@ const baseInvSchema = mongoose.Schema({ brandPref: String, status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, }], + updateRecord: [{ + _id: false, + date: { type: Date, required: true }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + quantityUsed: { type: Number, required: true }, + quantityWasted: { type: Number, required: true }, + }], }); -const baseInv = mongoose.model('buildingInventory', baseInvSchema, 'buildingInventoryItems'); +const baseInvSmallItems = mongoose.model('buildingInvSmallItems', baseSchemaForMaterialReusableConsumable, 'buildingInventoryItems'); + +const baseSchemaForToolEquipment = mongoose.Schema({ + itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, + project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, + purchaseStatus: { type: String, enum: ['Rental', 'Purchase'], required: true }, + // rental fields are required if purchaseStatus = "Rental" (hopefully correct syntax) + rentedOnDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, + rentalDueDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, + imgUrl: String, + updateRecord: [{ // track tool condition updates + _id: false, + date: { type: Date, default: Date.now() }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + condition: { type: String, enum: ['Good', 'Needs Repair', 'Out of Order'] }, + }], + logRecord: [{ // track tool daily check in/out and responsible user + _id: false, + date: { type: Date, default: Date.now() }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + responsibleUser: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + type: { type: String, enum: ['Check In', 'Check Out'] }, + }], +}); + +const baseInvLargeItems = mongoose.model('buildingInvLargeItems', baseSchemaForToolEquipment, 'buildingInventoryItems'); //----------------- // MATERIALS SCHEMA @@ -32,18 +83,23 @@ const baseInv = mongoose.model('buildingInventory', baseInvSchema, 'buildingInve // each document derived from this schema includes key field { __t: "material" } // ex: sand, stone, bricks, lumber, insulation -const buildingMaterial = baseInv.discriminator('material', new mongoose.Schema({ - stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project +// const buildingMaterial = baseInv.discriminator('material', new mongoose.Schema({ +// stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project +// stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused +// stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock +// stockAvailable: { type: Number, default: 0 }, // available = bought - (used + wasted/destroyed) +// updateRecord: [{ +// _id: false, +// date: { type: Date, required: true }, +// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, +// quantityUsed: { type: Number, required: true }, +// quantityWasted: { type: Number, required: true }, +// }], +// })); + +const buildingMaterial = baseInvSmallItems.discriminator('material', new mongoose.Schema({ stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock - stockAvailable: { type: Number, default: 0 }, // available = bought - (used + wasted/destroyed) - updateRecord: [{ - _id: false, - date: { type: Date, required: true }, - createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - quantityUsed: { type: Number, required: true }, - quantityWasted: { type: Number, required: true }, - }], })); //----------------- @@ -54,18 +110,23 @@ const buildingMaterial = baseInv.discriminator('material', new mongoose.Schema({ // each document derived from this schema includes key field { __t: "consumable" } // ex: screws, nails, staples -const buildingConsumable = baseInv.discriminator('consumable', new mongoose.Schema({ - stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project +// const buildingConsumable = baseInv.discriminator('consumable', new mongoose.Schema({ +// stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project +// stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused +// stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock +// stockAvailable: { type: Number, default: 0 }, // available = bought - (used + wasted/destroyed) +// updateRecord: [{ +// _id: false, +// date: { type: Date, required: true }, +// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, +// quantityUsed: { type: Number, required: true }, +// quantityWasted: { type: Number, required: true }, +// }], +// })); + +const buildingConsumable = baseInvSmallItems.discriminator('consumable', new mongoose.Schema({ stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock - stockAvailable: { type: Number, default: 0 }, // available = bought - (used + wasted/destroyed) - updateRecord: [{ - _id: false, - date: { type: Date, required: true }, - createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - quantityUsed: { type: Number, required: true }, - quantityWasted: { type: Number, required: true }, - }], })); //----------------- @@ -76,17 +137,21 @@ const buildingConsumable = baseInv.discriminator('consumable', new mongoose.Sche // each document derived from this schema includes key field { __t: "reusable" } // ex: hammers, screwdrivers, mallets, brushes, gloves -const buildingReusable = baseInv.discriminator('reusable', new mongoose.Schema({ - stockBought: { type: Number, default: 0 }, +// const buildingReusable = baseInv.discriminator('reusable', new mongoose.Schema({ +// stockBought: { type: Number, default: 0 }, +// stockDestroyed: { type: Number, default: 0 }, +// stockAvailable: { type: Number, default: 0 }, +// updateRecord: [{ +// _id: false, +// date: { type: Date, required: true }, +// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, +// quantityUsed: { type: Number, required: true }, +// quantityDestroyed: { type: Number, required: true }, +// }], +// })); + +const buildingReusable = baseInvSmallItems.discriminator('reusable', new mongoose.Schema({ stockDestroyed: { type: Number, default: 0 }, - stockAvailable: { type: Number, default: 0 }, - updateRecord: [{ - _id: false, - date: { type: Date, required: true }, - createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - quantityUsed: { type: Number, required: true }, - quantityDestroyed: { type: Number, required: true }, - }], })); //------------- @@ -99,28 +164,33 @@ const buildingReusable = baseInv.discriminator('reusable', new mongoose.Schema({ // Base Tool Schema: -const buildingTool = baseInv.discriminator('tool', new mongoose.Schema({ +// const buildingTool = baseInv.discriminator('tool', new mongoose.Schema({ +// code: { type: Number, required: true }, // TODO: add function to create simple numeric code for on-site tool tracking +// purchaseStatus: { type: String, enum: ['Rental', 'Purchase'], required: true }, +// // rental fields are required if purchaseStatus = "Rental" (hopefully correct syntax) +// rentedOnDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, +// rentalDueDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, +// imgUrl: String, +// updateRecord: [{ // track tool condition updates +// _id: false, +// date: { type: Date, default: Date.now() }, +// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, +// condition: { type: String, enum: ['Good', 'Needs Repair', 'Out of Order'] }, +// }], +// logRecord: [{ // track tool daily check in/out and responsible user +// _id: false, +// date: { type: Date, default: Date.now() }, +// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, +// responsibleUser: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, +// type: { type: String, enum: ['Check In', 'Check Out'] }, +// }], +// })); + +const buildingTool = baseInvLargeItems.discriminator('tool', new mongoose.Schema({ code: { type: Number, required: true }, // TODO: add function to create simple numeric code for on-site tool tracking - purchaseStatus: { type: String, enum: ['Rental', 'Purchase'], required: true }, - // rental fields are required if purchaseStatus = "Rental" (hopefully correct syntax) - rentedOnDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, - rentalDueDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, - imgUrl: String, - updateRecord: [{ // track tool condition updates - _id: false, - date: { type: Date, default: Date.now() }, - createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - condition: { type: String, enum: ['Good', 'Needs Repair', 'Out of Order'] }, - }], - logRecord: [{ // track tool daily check in/out and responsible user - _id: false, - date: { type: Date, default: Date.now() }, - createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - responsibleUser: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - type: { type: String, enum: ['Check In', 'Check Out'] }, - }], })); + //------------------ // EQUIPMENT SCHEMAS //------------------ @@ -130,28 +200,33 @@ const buildingTool = baseInv.discriminator('tool', new mongoose.Schema({ // items in this category are assumed to be rented // ex: tractors, excavators, bulldozers -const buildingEquipment = baseInv.discriminator('equipment', new mongoose.Schema({ +// const buildingEquipment = baseInv.discriminator('equipment', new mongoose.Schema({ +// isTracked: { type: Boolean, required: true }, // has asset tracker +// assetTracker: { type: String, required: () => this.isTracked }, // required if isTracked = true (syntax?) +// code: { type: Number, required: true }, // TODO: add function to create simple numeric code for on-site tool tracking +// purchaseStatus: { type: String, enum: ['Rental', 'Purchase'], required: true }, +// // rental fields are required if purchaseStatus = "Rental" (hopefully correct syntax) +// rentedOnDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, +// rentalDueDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, +// imgUrl: String, +// updateRecord: [{ // track equipment condition updates +// _id: false, +// date: { type: Date, default: Date.now() }, +// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, +// condition: { type: String, enum: ['Good', 'Needs Repair', 'Out of Order'] }, +// }], +// logRecord: [{ // track tool daily check in/out and responsible user +// _id: false, +// date: { type: Date, default: Date.now() }, +// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, +// responsibleUser: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, +// type: { type: String, enum: ['Check In', 'Check Out'] }, +// }], +// })); + +const buildingEquipment = baseInvLargeItems.discriminator('equipment', new mongoose.Schema({ isTracked: { type: Boolean, required: true }, // has asset tracker assetTracker: { type: String, required: () => this.isTracked }, // required if isTracked = true (syntax?) - code: { type: Number, required: true }, // TODO: add function to create simple numeric code for on-site tool tracking - purchaseStatus: { type: String, enum: ['Rental', 'Purchase'], required: true }, - // rental fields are required if purchaseStatus = "Rental" (hopefully correct syntax) - rentedOnDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, - rentalDueDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, - imgUrl: String, - updateRecord: [{ // track equipment condition updates - _id: false, - date: { type: Date, default: Date.now() }, - createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - condition: { type: String, enum: ['Good', 'Needs Repair', 'Out of Order'] }, - }], - logRecord: [{ // track tool daily check in/out and responsible user - _id: false, - date: { type: Date, default: Date.now() }, - createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - responsibleUser: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, - type: { type: String, enum: ['Check In', 'Check Out'] }, - }], })); // add purchase varient instead of rental varient? From f92d83360f7cca247068b393d576c2cf3baec0dc Mon Sep 17 00:00:00 2001 From: Tim Kent Date: Tue, 12 Dec 2023 12:29:45 -0800 Subject: [PATCH 09/11] refactor file --- .../bmdashboard/buildingInventoryItem.js | 184 +++++------------- 1 file changed, 46 insertions(+), 138 deletions(-) diff --git a/src/models/bmdashboard/buildingInventoryItem.js b/src/models/bmdashboard/buildingInventoryItem.js index a9559a45d..5e8b24916 100644 --- a/src/models/bmdashboard/buildingInventoryItem.js +++ b/src/models/bmdashboard/buildingInventoryItem.js @@ -4,27 +4,13 @@ const mongoose = require('mongoose'); // BASE INVENTORY SCHEMAS //----------------------- -// base schema for all categories of inventory (Consumable, Material, Reusable, Tool, Equipment) -// this schema is extended by the individual schemas for each inventory type -// all documents derived from this schema are saved to the collection 'buildingInventoryItems' - -// const baseInvSchema = mongoose.Schema({ -// itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, -// project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, -// purchaseRecord: [{ -// _id: false, // do not add _id field to subdocument -// date: { type: Date, default: Date.now() }, -// requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, -// quantity: { type: Number, required: true, default: 1 }, // default 1 for tool or equipment purchases -// priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, -// brandPref: String, -// status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, -// }], -// }); - -// const baseInv = mongoose.model('buildingInventory', baseInvSchema, 'buildingInventoryItems'); - -const baseSchemaForMaterialReusableConsumable = mongoose.Schema({ +// TODO: purchaseRecord subdocs may be changed to purchaseRequests. A new purchaseRecord subdoc may be added to track purchases and costs for the item. + +// SMALL ITEMS BASE +// base schema for Consumable, Material, Reusable +// documents stored in 'buildingInventoryItems' collection + +const smallItemBaseSchema = mongoose.Schema({ itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project @@ -48,16 +34,29 @@ const baseSchemaForMaterialReusableConsumable = mongoose.Schema({ }], }); -const baseInvSmallItems = mongoose.model('buildingInvSmallItems', baseSchemaForMaterialReusableConsumable, 'buildingInventoryItems'); +const smallItemBase = mongoose.model('smallItemBase', smallItemBaseSchema, 'buildingInventoryItems'); + +// LARGE ITEMS BASE +// base schema for Tool, Equipment +// documents stored in 'buildingInventoryItems' collection -const baseSchemaForToolEquipment = mongoose.Schema({ +const largeItemBaseSchema = mongoose.Schema({ itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, purchaseStatus: { type: String, enum: ['Rental', 'Purchase'], required: true }, // rental fields are required if purchaseStatus = "Rental" (hopefully correct syntax) rentedOnDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, rentalDueDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, - imgUrl: String, + imageUrl: String, + purchaseRecord: [{ + _id: false, // do not add _id field to subdocument + date: { type: Date, default: Date.now() }, + requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, + makeModelPref: String, + estTimeRequired: { type: Number, required: true }, // estimated time required on site + status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, + }], updateRecord: [{ // track tool condition updates _id: false, date: { type: Date, default: Date.now() }, @@ -73,164 +72,73 @@ const baseSchemaForToolEquipment = mongoose.Schema({ }], }); -const baseInvLargeItems = mongoose.model('buildingInvLargeItems', baseSchemaForToolEquipment, 'buildingInventoryItems'); +const largeItemBase = mongoose.model('largeItemBase', largeItemBaseSchema, 'buildingInventoryItems'); //----------------- // MATERIALS SCHEMA //----------------- -// inherits all properties of baseInv schema using discriminator +// inherits all properties of smallItemBaseSchema // each document derived from this schema includes key field { __t: "material" } // ex: sand, stone, bricks, lumber, insulation -// const buildingMaterial = baseInv.discriminator('material', new mongoose.Schema({ -// stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project -// stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused -// stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock -// stockAvailable: { type: Number, default: 0 }, // available = bought - (used + wasted/destroyed) -// updateRecord: [{ -// _id: false, -// date: { type: Date, required: true }, -// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, -// quantityUsed: { type: Number, required: true }, -// quantityWasted: { type: Number, required: true }, -// }], -// })); - -const buildingMaterial = baseInvSmallItems.discriminator('material', new mongoose.Schema({ +const buildingMaterial = smallItemBase.discriminator('material', new mongoose.Schema({ stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock })); -//----------------- -// CONSUMABLES SCHEMA -//----------------- +//------------------ +// CONSUMABLE SCHEMA +//------------------ -// inherits all properties of baseInv schema using discriminator +// inherits all properties of smallItemBaseSchema // each document derived from this schema includes key field { __t: "consumable" } // ex: screws, nails, staples -// const buildingConsumable = baseInv.discriminator('consumable', new mongoose.Schema({ -// stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project -// stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused -// stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock -// stockAvailable: { type: Number, default: 0 }, // available = bought - (used + wasted/destroyed) -// updateRecord: [{ -// _id: false, -// date: { type: Date, required: true }, -// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, -// quantityUsed: { type: Number, required: true }, -// quantityWasted: { type: Number, required: true }, -// }], -// })); - -const buildingConsumable = baseInvSmallItems.discriminator('consumable', new mongoose.Schema({ +const buildingConsumable = smallItemBase.discriminator('consumable', new mongoose.Schema({ stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock })); -//----------------- -// REUSABLES SCHEMA -//----------------- +//---------------- +// REUSABLE SCHEMA +//---------------- -// inherits all properties of baseInv schema using discriminator +// inherits all properties of smallItemBaseSchema // each document derived from this schema includes key field { __t: "reusable" } // ex: hammers, screwdrivers, mallets, brushes, gloves -// const buildingReusable = baseInv.discriminator('reusable', new mongoose.Schema({ -// stockBought: { type: Number, default: 0 }, -// stockDestroyed: { type: Number, default: 0 }, -// stockAvailable: { type: Number, default: 0 }, -// updateRecord: [{ -// _id: false, -// date: { type: Date, required: true }, -// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, -// quantityUsed: { type: Number, required: true }, -// quantityDestroyed: { type: Number, required: true }, -// }], -// })); - -const buildingReusable = baseInvSmallItems.discriminator('reusable', new mongoose.Schema({ +const buildingReusable = smallItemBase.discriminator('reusable', new mongoose.Schema({ stockDestroyed: { type: Number, default: 0 }, })); -//------------- -// TOOL SCHEMAS -//------------- +//------------ +// TOOL SCHEMA +//------------ -// inherits all properties of baseInv schema using discriminator +// inherits all properties of largeItemBaseSchema // each document derived from this schema includes key field { __t: "tool" } // ex: power drills, wheelbarrows, shovels, jackhammers -// Base Tool Schema: - -// const buildingTool = baseInv.discriminator('tool', new mongoose.Schema({ -// code: { type: Number, required: true }, // TODO: add function to create simple numeric code for on-site tool tracking -// purchaseStatus: { type: String, enum: ['Rental', 'Purchase'], required: true }, -// // rental fields are required if purchaseStatus = "Rental" (hopefully correct syntax) -// rentedOnDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, -// rentalDueDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, -// imgUrl: String, -// updateRecord: [{ // track tool condition updates -// _id: false, -// date: { type: Date, default: Date.now() }, -// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, -// condition: { type: String, enum: ['Good', 'Needs Repair', 'Out of Order'] }, -// }], -// logRecord: [{ // track tool daily check in/out and responsible user -// _id: false, -// date: { type: Date, default: Date.now() }, -// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, -// responsibleUser: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, -// type: { type: String, enum: ['Check In', 'Check Out'] }, -// }], -// })); - -const buildingTool = baseInvLargeItems.discriminator('tool', new mongoose.Schema({ +const buildingTool = largeItemBase.discriminator('tool', new mongoose.Schema({ code: { type: Number, required: true }, // TODO: add function to create simple numeric code for on-site tool tracking })); -//------------------ -// EQUIPMENT SCHEMAS -//------------------ +//----------------- +// EQUIPMENT SCHEMA +//----------------- -// inherits all properties of baseInv schema using discriminator +// inherits all properties of largeItemBaseSchema // each document derived from this schema includes key field { __t: "equipment" } // items in this category are assumed to be rented // ex: tractors, excavators, bulldozers -// const buildingEquipment = baseInv.discriminator('equipment', new mongoose.Schema({ -// isTracked: { type: Boolean, required: true }, // has asset tracker -// assetTracker: { type: String, required: () => this.isTracked }, // required if isTracked = true (syntax?) -// code: { type: Number, required: true }, // TODO: add function to create simple numeric code for on-site tool tracking -// purchaseStatus: { type: String, enum: ['Rental', 'Purchase'], required: true }, -// // rental fields are required if purchaseStatus = "Rental" (hopefully correct syntax) -// rentedOnDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, -// rentalDueDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, -// imgUrl: String, -// updateRecord: [{ // track equipment condition updates -// _id: false, -// date: { type: Date, default: Date.now() }, -// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, -// condition: { type: String, enum: ['Good', 'Needs Repair', 'Out of Order'] }, -// }], -// logRecord: [{ // track tool daily check in/out and responsible user -// _id: false, -// date: { type: Date, default: Date.now() }, -// createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, -// responsibleUser: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, -// type: { type: String, enum: ['Check In', 'Check Out'] }, -// }], -// })); - -const buildingEquipment = baseInvLargeItems.discriminator('equipment', new mongoose.Schema({ +const buildingEquipment = largeItemBase.discriminator('equipment', new mongoose.Schema({ isTracked: { type: Boolean, required: true }, // has asset tracker assetTracker: { type: String, required: () => this.isTracked }, // required if isTracked = true (syntax?) })); -// add purchase varient instead of rental varient? - module.exports = { buildingMaterial, buildingConsumable, From bae46b1c69333910a54d0d914a5faaec6ed31246 Mon Sep 17 00:00:00 2001 From: Tim Kent Date: Tue, 12 Dec 2023 12:43:52 -0800 Subject: [PATCH 10/11] undo changes to current material routes to avoid breaking things --- .../bmdashboard/bmMaterialsController.js | 2 +- src/models/bmdashboard/buildingMaterial.js | 30 +++++++++++-------- src/startup/routes.js | 2 +- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/controllers/bmdashboard/bmMaterialsController.js b/src/controllers/bmdashboard/bmMaterialsController.js index 2c79bd9b9..911ca0b55 100644 --- a/src/controllers/bmdashboard/bmMaterialsController.js +++ b/src/controllers/bmdashboard/bmMaterialsController.js @@ -48,7 +48,7 @@ const bmMaterialsController = function (ItemMaterial,BuildingMaterial) { const newPurchaseRecord = { quantity, priority, - brandPref: brand, + brand, requestedBy: requestorId, }; try { diff --git a/src/models/bmdashboard/buildingMaterial.js b/src/models/bmdashboard/buildingMaterial.js index caf61e4c0..bc86884ed 100644 --- a/src/models/bmdashboard/buildingMaterial.js +++ b/src/models/bmdashboard/buildingMaterial.js @@ -1,23 +1,29 @@ const mongoose = require('mongoose'); -const baseInv = require('./baseInvSchema'); +const { Schema } = mongoose; -// inherits all properties of baseInv schema using discriminator -// each document derived from this schema includes key field { __t: "buildingMaterial" } - -const buildingMaterial = baseInv.discriminator('buildingMaterial', new mongoose.Schema({ +const buildingMaterial = new Schema({ + itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, + project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project - stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused - stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock - stockAvailable: { type: Number, default: 0 }, // available = bought - (used + wasted/destroyed) + stockUsed: { type: Number, default: 0 }, // total amount of item used successfully in the project + stockWasted: { type: Number, default: 0 }, // total amount of item wasted/ruined/lost in the project + stockAvailable: { type: Number, default: 0 }, // bought - (used + wasted) + purchaseRecord: [{ + _id: false, // do not add _id field to subdocument + date: { type: Date, default: Date.now() }, + requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + quantity: { type: Number, required: true }, + priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, + brand: String, + status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, + }], updateRecord: [{ _id: false, date: { type: Date, required: true }, createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, quantityUsed: { type: Number, required: true }, quantityWasted: { type: Number, required: true }, - test: { type: String, default: 'testing this field' }, }], -})); - -module.exports = buildingMaterial; +}); +module.exports = mongoose.model('buildingMaterial', buildingMaterial, 'buildingMaterials'); diff --git a/src/startup/routes.js b/src/startup/routes.js index 37832c199..ff5d01b2b 100644 --- a/src/startup/routes.js +++ b/src/startup/routes.js @@ -23,7 +23,7 @@ const inventoryItemMaterial = require('../models/inventoryItemMaterial'); const mapLocations = require('../models/mapLocation'); const buildingProject = require('../models/bmdashboard/buildingProject'); const buildingInventoryType = require('../models/bmdashboard/buildingInventoryType'); -const { buildingMaterial } = require('../models/bmdashboard/buildingInventoryItem'); +const buildingMaterial = require('../models/bmdashboard/buildingMaterial'); const userProfileRouter = require('../routes/userProfileRouter')(userProfile); const badgeRouter = require('../routes/badgeRouter')(badge); From 230cedc459088655359f75cc7a1752be24a4ed40 Mon Sep 17 00:00:00 2001 From: Carl Bebli Date: Fri, 15 Dec 2023 15:41:22 +0000 Subject: [PATCH 11/11] added the replyTo feature --- src/controllers/timeEntryController.js | 366 +++++++++++++++---------- src/helpers/userHelper.js | 4 +- 2 files changed, 228 insertions(+), 142 deletions(-) diff --git a/src/controllers/timeEntryController.js b/src/controllers/timeEntryController.js index 962f30170..9c4d7c5e3 100644 --- a/src/controllers/timeEntryController.js +++ b/src/controllers/timeEntryController.js @@ -1,17 +1,17 @@ -const moment = require('moment-timezone'); -const mongoose = require('mongoose'); -const { getInfringementEmailBody } = require('../helpers/userHelper')(); -const userProfile = require('../models/userProfile'); -const task = require('../models/task'); -const emailSender = require('../utilities/emailSender'); -const { hasPermission } = require('../utilities/permissions'); +const moment = require("moment-timezone"); +const mongoose = require("mongoose"); +const { getInfringementEmailBody } = require("../helpers/userHelper")(); +const userProfile = require("../models/userProfile"); +const task = require("../models/task"); +const emailSender = require("../utilities/emailSender"); +const { hasPermission } = require("../utilities/permissions"); const formatSeconds = function (seconds) { const formattedseconds = parseInt(seconds, 10); const values = `${Math.floor( - moment.duration(formattedseconds, 'seconds').asHours(), - )}:${moment.duration(formattedseconds, 'seconds').minutes()}`; - return values.split(':'); + moment.duration(formattedseconds, "seconds").asHours() + )}:${moment.duration(formattedseconds, "seconds").minutes()}`; + return values.split(":"); }; /** @@ -24,9 +24,20 @@ const formatSeconds = function (seconds) { * @param {*} requestor The userProfile object of the person that modified the time entry * @returns {String} */ -const getEditedTimeEntryEmailBody = (firstName, lastName, email, originalTime, finalTime, requestor) => { - const formattedOriginal = moment.utc(originalTime * 1000).format('HH[ hours ]mm[ minutes]'); - const formattedFinal = moment.utc(finalTime * 1000).format('HH[ hours ]mm[ minutes]'); +const getEditedTimeEntryEmailBody = ( + firstName, + lastName, + email, + originalTime, + finalTime, + requestor +) => { + const formattedOriginal = moment + .utc(originalTime * 1000) + .format("HH[ hours ]mm[ minutes]"); + const formattedFinal = moment + .utc(finalTime * 1000) + .format("HH[ hours ]mm[ minutes]"); return ` A time entry belonging to ${firstName} ${lastName} (${email}) was modified by ${requestor.firstName} ${requestor.lastName} (${requestor.email}). The entry's duration was changed from [${formattedOriginal}] to [${formattedFinal}] @@ -45,18 +56,39 @@ const notifyEditByEmail = async (personId, original, finalTime, final) => { try { const originalTime = original.totalSeconds; const record = await userProfile.findById(personId); - const requestor = (personId !== final.requestor.requestorId) ? await userProfile.findById(final.requestor.requestorId) : record; - const emailBody = getEditedTimeEntryEmailBody(record.firstName, record.lastName, record.email, originalTime, finalTime, requestor); - emailSender('onecommunityglobal@gmail.com', `A Time Entry was Edited for ${record.firstName} ${record.lastName}`, emailBody); + const requestor = + personId !== final.requestor.requestorId + ? await userProfile.findById(final.requestor.requestorId) + : record; + const emailBody = getEditedTimeEntryEmailBody( + record.firstName, + record.lastName, + record.email, + originalTime, + finalTime, + requestor + ); + emailSender( + "onecommunityglobal@gmail.com", + `A Time Entry was Edited for ${record.firstName} ${record.lastName}`, + emailBody + ); } catch (error) { - throw new Error(`Failed to send email notification about the modification of time entry belonging to user with id ${personId}`); + throw new Error( + `Failed to send email notification about the modification of time entry belonging to user with id ${personId}` + ); } }; -const notifyTaskOvertimeEmailBody = async (personId, taskName, estimatedHours, hoursLogged) => { +const notifyTaskOvertimeEmailBody = async ( + personId, + taskName, + estimatedHours, + hoursLogged +) => { try { - const record = await userProfile.findById(personId); - const text = `Dear ${record.firstName}${record.lastName}, + const record = await userProfile.findById(personId); + const text = `Dear ${record.firstName}${record.lastName},

Oops, it looks like you have logged more hours than estimated for a task

Task Name : ${taskName}

Time Estimated : ${estimatedHours}

@@ -64,24 +96,37 @@ const notifyTaskOvertimeEmailBody = async (personId, taskName, estimatedHours, h

Please connect with your manager to explain what happened and submit a new hours estimation for completion.

Thank you,

One Community

`; - emailSender( - record.email, - 'Logged more hours than estimated for a task', - text, - 'onecommunityglobal@gmail.com', - null, + emailSender( + record.email, + "Logged more hours than estimated for a task", + text, + "onecommunityglobal@gmail.com", + null, + record.email, + null ); } catch (error) { - console.log(`Failed to send email notification about the overtime for a task belonging to user with id ${personId}`); + console.log( + `Failed to send email notification about the overtime for a task belonging to user with id ${personId}` + ); } }; const checkTaskOvertime = async (timeentry, record, currentTask) => { try { // send email notification if logged in hours exceeds estiamted hours for a task - if (currentTask.hoursLogged > currentTask.estimatedHours) { notifyTaskOvertimeEmailBody(timeentry.personId.toString(), currentTask.taskName, currentTask.estimatedHours, currentTask.hoursLogged); } + if (currentTask.hoursLogged > currentTask.estimatedHours) { + notifyTaskOvertimeEmailBody( + timeentry.personId.toString(), + currentTask.taskName, + currentTask.estimatedHours, + currentTask.hoursLogged + ); + } } catch (error) { - console.log(`Failed to find task whose logged-in hours are more than estimated hours ${record.email}`); + console.log( + `Failed to find task whose logged-in hours are more than estimated hours ${record.email}` + ); } }; @@ -92,31 +137,58 @@ const timeEntrycontroller = function (TimeEntry) { try { if (!req.params.timeEntryId) { - return res.status(400).send({ error: 'ObjectId in request param is not in correct format' }); + return res + .status(400) + .send({ + error: "ObjectId in request param is not in correct format", + }); } - if (!mongoose.Types.ObjectId.isValid(req.params.timeEntryId) || !mongoose.Types.ObjectId.isValid(req.body.projectId)) { - return res.status(400).send({ error: 'ObjectIds are not correctly formed' }); + if ( + !mongoose.Types.ObjectId.isValid(req.params.timeEntryId) || + !mongoose.Types.ObjectId.isValid(req.body.projectId) + ) { + return res + .status(400) + .send({ error: "ObjectIds are not correctly formed" }); } // Get initial timeEntry by timeEntryId const timeEntry = await TimeEntry.findById(req.params.timeEntryId); if (!timeEntry) { - return res.status(400).send({ error: `No valid records found for ${req.params.timeEntryId}` }); + return res + .status(400) + .send({ + error: `No valid records found for ${req.params.timeEntryId}`, + }); } - if (!(await hasPermission(req.body.requestor, 'editTimeEntry') || timeEntry.personId.toString() === req.body.requestor.requestorId.toString())) { - return res.status(403).send({ error: 'Unauthorized request' }); + if ( + !( + (await hasPermission(req.body.requestor, "editTimeEntry")) || + timeEntry.personId.toString() === + req.body.requestor.requestorId.toString() + ) + ) { + return res.status(403).send({ error: "Unauthorized request" }); } - const hours = req.body.hours ? req.body.hours : '00'; - const minutes = req.body.minutes ? req.body.minutes : '00'; + const hours = req.body.hours ? req.body.hours : "00"; + const minutes = req.body.minutes ? req.body.minutes : "00"; const totalSeconds = moment.duration(`${hours}:${minutes}`).asSeconds(); - if (timeEntry.isTangible === true && totalSeconds !== timeEntry.totalSeconds) { - notifyEditByEmail(timeEntry.personId.toString(), timeEntry, totalSeconds, req.body); + if ( + timeEntry.isTangible === true && + totalSeconds !== timeEntry.totalSeconds + ) { + notifyEditByEmail( + timeEntry.personId.toString(), + timeEntry, + totalSeconds, + req.body + ); } const initialSeconds = timeEntry.totalSeconds; @@ -130,7 +202,7 @@ const timeEntrycontroller = function (TimeEntry) { timeEntry.isTangible = req.body.isTangible; timeEntry.lastModifiedDateTime = moment().utc().toISOString(); timeEntry.projectId = mongoose.Types.ObjectId(req.body.projectId); - timeEntry.dateOfWork = moment(req.body.dateOfWork).format('YYYY-MM-DD'); + timeEntry.dateOfWork = moment(req.body.dateOfWork).format("YYYY-MM-DD"); // Update the hoursLogged field of related tasks based on before and after timeEntries // initialIsTangible is a bealoon value, req.body.isTangible is a string @@ -138,11 +210,11 @@ const timeEntrycontroller = function (TimeEntry) { try { if (findTask) { if (initialIsTangible === true) { - findTask.hoursLogged -= (initialSeconds / 3600); + findTask.hoursLogged -= initialSeconds / 3600; } if (req.body.isTangible === true) { - findTask.hoursLogged += (totalSeconds / 3600); + findTask.hoursLogged += totalSeconds / 3600; } await findTask.save(); @@ -152,14 +224,17 @@ const timeEntrycontroller = function (TimeEntry) { } // Update edit history - if (initialSeconds !== totalSeconds - && timeEntry.isTangible - && req.body.requestor.requestorId === timeEntry.personId.toString() - && !await hasPermission(req.body.requestor, 'editTimeEntry') - ) { - const requestor = await userProfile.findById(req.body.requestor.requestorId); + if ( + initialSeconds !== totalSeconds && + timeEntry.isTangible && + req.body.requestor.requestorId === timeEntry.personId.toString() && + !(await hasPermission(req.body.requestor, "editTimeEntry")) + ) { + const requestor = await userProfile.findById( + req.body.requestor.requestorId + ); requestor.timeEntryEditHistory.push({ - date: moment().tz('America/Los_Angeles').toDate(), + date: moment().tz("America/Los_Angeles").toDate(), initialSeconds, newSeconds: totalSeconds, }); @@ -168,18 +243,23 @@ const timeEntrycontroller = function (TimeEntry) { let totalRecentEdits = 0; requestor.timeEntryEditHistory.forEach((edit) => { - if (moment().tz('America/Los_Angeles').diff(edit.date, 'days') <= 365) { + if ( + moment().tz("America/Los_Angeles").diff(edit.date, "days") <= 365 + ) { totalRecentEdits += 1; } }); if (totalRecentEdits >= 5) { requestor.infringements.push({ - date: moment().tz('America/Los_Angeles'), + date: moment().tz("America/Los_Angeles"), description: `${totalRecentEdits} time entry edits in the last calendar year`, }); - emailSender('onecommunityglobal@gmail.com', `${requestor.firstName} ${requestor.lastName} was issued a blue square for for editing a time entry ${totalRecentEdits} times`, ` + emailSender( + "onecommunityglobal@gmail.com", + `${requestor.firstName} ${requestor.lastName} was issued a blue square for for editing a time entry ${totalRecentEdits} times`, + `

${requestor.firstName} ${requestor.lastName} (${requestor.email}) was issued a blue square for editing their time entries ${totalRecentEdits} times within the last calendar year. @@ -187,28 +267,39 @@ const timeEntrycontroller = function (TimeEntry) {

This is the ${totalRecentEdits}th edit within the past 365 days.

- `); + ` + ); const emailInfringement = { - date: moment().tz('America/Los_Angeles').format('MMMM-DD-YY'), + date: moment().tz("America/Los_Angeles").format("MMMM-DD-YY"), description: `You edited your time entries ${totalRecentEdits} times within the last 365 days, exceeding the limit of 4 times per year you can edit them without penalty.`, }; - emailSender(requestor.email, 'You\'ve been issued a blue square for editing your time entry', getInfringementEmailBody(requestor.firstName, requestor.lastName, emailInfringement, requestor.infringements.length)); + emailSender( + requestor.email, + "You've been issued a blue square for editing your time entry", + getInfringementEmailBody( + requestor.firstName, + requestor.lastName, + emailInfringement, + requestor.infringements.length + ) + ); } await requestor.save(); } - await timeEntry.save(); - res.status(200).send({ message: 'Successfully updated time entry' }); + res.status(200).send({ message: "Successfully updated time entry" }); // If the time entry isn't related to a task (i.e. it's a project), then don't check for overtime (Most likely pr team) if (findTask) { // checking if logged in hours exceed estimated time after timeentry edit for a task - const record = await userProfile.findById(timeEntry.personId.toString()); + const record = await userProfile.findById( + timeEntry.personId.toString() + ); const currentTask = await task.findById(req.body.projectId); checkTaskOvertime(timeEntry, record, currentTask); } @@ -233,9 +324,9 @@ const timeEntrycontroller = function (TimeEntry) { timeentry.personId = element.personId; timeentry.projectId = element.projectId; timeentry.dateOfWork = element.dateOfWork; - timeentry.timeSpent = moment('1900-01-01 00:00:00') - .add(element.totalSeconds, 'seconds') - .format('HH:mm:ss'); + timeentry.timeSpent = moment("1900-01-01 00:00:00") + .add(element.totalSeconds, "seconds") + .format("HH:mm:ss"); timeentry.notes = element.notes; timeentry.isTangible = element.isTangible; items.push(timeentry); @@ -246,21 +337,21 @@ const timeEntrycontroller = function (TimeEntry) { const postTimeEntry = async function (req, res) { if ( - !mongoose.Types.ObjectId.isValid(req.body.personId) - || !mongoose.Types.ObjectId.isValid(req.body.projectId) - || !req.body.dateOfWork - || !moment(req.body.dateOfWork).isValid() - || !req.body.timeSpent - || !req.body.isTangible + !mongoose.Types.ObjectId.isValid(req.body.personId) || + !mongoose.Types.ObjectId.isValid(req.body.projectId) || + !req.body.dateOfWork || + !moment(req.body.dateOfWork).isValid() || + !req.body.timeSpent || + !req.body.isTangible ) { - res.status(400).send({ error: 'Bad request' }); + res.status(400).send({ error: "Bad request" }); return; } const timeentry = new TimeEntry(); const { dateOfWork, timeSpent } = req.body; timeentry.personId = req.body.personId; timeentry.projectId = req.body.projectId; - timeentry.dateOfWork = moment(dateOfWork).format('YYYY-MM-DD'); + timeentry.dateOfWork = moment(dateOfWork).format("YYYY-MM-DD"); timeentry.totalSeconds = moment.duration(timeSpent).asSeconds(); timeentry.notes = req.body.notes; timeentry.isTangible = req.body.isTangible; @@ -274,12 +365,14 @@ const timeEntrycontroller = function (TimeEntry) { .status(200) .send({ message: `Time Entry saved with id as ${results._id}` }); }) - .catch(error => res.status(400).send(error)); + .catch((error) => res.status(400).send(error)); - // Get the task related to this time entry, if not found, then it's a project sets to null - const currentTask = await task.findById(req.body.projectId).catch(() => null); + // Get the task related to this time entry, if not found, then it's a project sets to null + const currentTask = await task + .findById(req.body.projectId) + .catch(() => null); - // Add this tangbile time entry to related task's hoursLogged and checks if timeEntry is related to a task + // Add this tangbile time entry to related task's hoursLogged and checks if timeEntry is related to a task if (timeentry.isTangible === true && currentTask) { try { currentTask.hoursLogged += timeentry.totalSeconds / 3600; @@ -292,7 +385,9 @@ const timeEntrycontroller = function (TimeEntry) { // checking if logged in hours exceed estimated time after timeentry for a task, only if the time entry is related to a task (It might not be, if it's a project) if (currentTask) { try { - const record = await userProfile.findById(timeentry.personId.toString()); + const record = await userProfile.findById( + timeentry.personId.toString() + ); checkTaskOvertime(timeentry, record, currentTask); } catch (error) { throw new Error(error); @@ -302,19 +397,23 @@ const timeEntrycontroller = function (TimeEntry) { const getTimeEntriesForSpecifiedPeriod = function (req, res) { if ( - !req.params - || !req.params.fromdate - || !req.params.todate - || !req.params.userId - || !moment(req.params.fromdate).isValid() - || !moment(req.params.toDate).isValid() + !req.params || + !req.params.fromdate || + !req.params.todate || + !req.params.userId || + !moment(req.params.fromdate).isValid() || + !moment(req.params.toDate).isValid() ) { - res.status(400).send({ error: 'Invalid request' }); + res.status(400).send({ error: "Invalid request" }); return; } - const fromdate = moment(req.params.fromdate).tz('America/Los_Angeles').format('YYYY-MM-DD'); - const todate = moment(req.params.todate).tz('America/Los_Angeles').format('YYYY-MM-DD'); + const fromdate = moment(req.params.fromdate) + .tz("America/Los_Angeles") + .format("YYYY-MM-DD"); + const todate = moment(req.params.todate) + .tz("America/Los_Angeles") + .format("YYYY-MM-DD"); const { userId } = req.params; TimeEntry.aggregate([ @@ -326,18 +425,18 @@ const timeEntrycontroller = function (TimeEntry) { }, { $lookup: { - from: 'projects', - localField: 'projectId', - foreignField: '_id', - as: 'project', + from: "projects", + localField: "projectId", + foreignField: "_id", + as: "project", }, }, { $lookup: { - from: 'tasks', - localField: 'projectId', - foreignField: '_id', - as: 'task', + from: "tasks", + localField: "projectId", + foreignField: "_id", + as: "task", }, }, { @@ -349,41 +448,26 @@ const timeEntrycontroller = function (TimeEntry) { projectId: 1, lastModifiedDateTime: 1, projectName: { - $arrayElemAt: [ - '$project.projectName', - 0, - ], + $arrayElemAt: ["$project.projectName", 0], }, taskName: { - $arrayElemAt: [ - '$task.taskName', - 0, - ], + $arrayElemAt: ["$task.taskName", 0], }, category: { - $arrayElemAt: [ - '$project.category', - 0, - ], + $arrayElemAt: ["$project.category", 0], }, classification: { - $arrayElemAt: [ - '$task.classification', - 0, - ], + $arrayElemAt: ["$task.classification", 0], }, dateOfWork: 1, hours: { $floor: { - $divide: ['$totalSeconds', 3600], + $divide: ["$totalSeconds", 3600], }, }, minutes: { $floor: { - $divide: [ - { $mod: ['$totalSeconds', 3600] }, - 60, - ], + $divide: [{ $mod: ["$totalSeconds", 3600] }, 60], }, }, }, @@ -393,9 +477,11 @@ const timeEntrycontroller = function (TimeEntry) { lastModifiedDateTime: -1, }, }, - ]).then((results) => { - res.status(200).send(results); - }).catch(error => res.status(400).send(error)); + ]) + .then((results) => { + res.status(200).send(results); + }) + .catch((error) => res.status(400).send(error)); }; const getTimeEntriesForUsersList = function (req, res) { @@ -406,9 +492,9 @@ const timeEntrycontroller = function (TimeEntry) { personId: { $in: users }, dateOfWork: { $gte: fromDate, $lte: toDate }, }, - ' -createdDateTime', + " -createdDateTime" ) - .populate('projectId') + .populate("projectId") .sort({ lastModifiedDateTime: -1 }) .then((results) => { const data = []; @@ -419,72 +505,73 @@ const timeEntrycontroller = function (TimeEntry) { record.notes = element.notes; record.isTangible = element.isTangible; record.personId = element.personId; - record.projectId = element.projectId ? element.projectId._id : ''; + record.projectId = element.projectId ? element.projectId._id : ""; record.projectName = element.projectId ? element.projectId.projectName - : ''; + : ""; record.dateOfWork = element.dateOfWork; [record.hours, record.minutes] = formatSeconds(element.totalSeconds); data.push(record); }); res.status(200).send(data); }) - .catch(error => res.status(400).send(error)); + .catch((error) => res.status(400).send(error)); }; const getTimeEntriesForSpecifiedProject = function (req, res) { if ( - !req.params - || !req.params.fromDate - || !req.params.toDate - || !req.params.projectId + !req.params || + !req.params.fromDate || + !req.params.toDate || + !req.params.projectId ) { - res.status(400).send({ error: 'Invalid request' }); + res.status(400).send({ error: "Invalid request" }); return; } - const todate = moment(req.params.toDate).format('YYYY-MM-DD'); - const fromDate = moment(req.params.fromDate).format('YYYY-MM-DD'); + const todate = moment(req.params.toDate).format("YYYY-MM-DD"); + const fromDate = moment(req.params.fromDate).format("YYYY-MM-DD"); const { projectId } = req.params; TimeEntry.find( { projectId, dateOfWork: { $gte: fromDate, $lte: todate }, }, - '-createdDateTime -lastModifiedDateTime', + "-createdDateTime -lastModifiedDateTime" ) - .populate('userId') + .populate("userId") .sort({ dateOfWork: -1 }) .then((results) => { res.status(200).send(results); }) - .catch(error => res.status(400).send(error)); + .catch((error) => res.status(400).send(error)); }; const deleteTimeEntry = async function (req, res) { if (!req.params.timeEntryId) { - res.status(400).send({ error: 'Bad request' }); + res.status(400).send({ error: "Bad request" }); return; } TimeEntry.findById(req.params.timeEntryId) .then(async (record) => { if (!record) { - res.status(400).send({ message: 'No valid record found' }); + res.status(400).send({ message: "No valid record found" }); return; } if ( - record.personId.toString() - === req.body.requestor.requestorId.toString() - || await hasPermission(req.body.requestor, 'deleteTimeEntry') + record.personId.toString() === + req.body.requestor.requestorId.toString() || + (await hasPermission(req.body.requestor, "deleteTimeEntry")) ) { // Revert this tangible timeEntry of related task's hoursLogged if (record.isTangible === true) { - task.findById(record.projectId) + task + .findById(record.projectId) .then((currentTask) => { // If the time entry isn't related to a task (i.e. it's a project), then don't revert hours (Most likely pr team) if (currentTask) { - currentTask.hoursLogged -= (record.totalSeconds / 3600); + currentTask.hoursLogged -= record.totalSeconds / 3600; currentTask.save(); } }) @@ -496,13 +583,13 @@ const timeEntrycontroller = function (TimeEntry) { record .remove() .then(() => { - res.status(200).send({ message: 'Successfully deleted' }); + res.status(200).send({ message: "Successfully deleted" }); }) .catch((error) => { res.status(500).send(error); }); } else { - res.status(403).send({ error: 'Unauthorized request' }); + res.status(403).send({ error: "Unauthorized request" }); } }) .catch((error) => { @@ -510,7 +597,6 @@ const timeEntrycontroller = function (TimeEntry) { }); }; - return { getAllTimeEnteries, postTimeEntry, diff --git a/src/helpers/userHelper.js b/src/helpers/userHelper.js index 8bf04eb5f..42422a02d 100644 --- a/src/helpers/userHelper.js +++ b/src/helpers/userHelper.js @@ -492,8 +492,8 @@ const userHelper = function () { emailBody, null, "onecommunityglobal@gmail.com", - null, - status.email + status.email, + null ); const categories = await dashboardHelper.laborThisWeekByCategory(