Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 21 additions & 168 deletions src/repositories/baseOrgRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ const CveIdRepository = require('./cveIdRepository')
const uuid = require('uuid')
const _ = require('lodash')
const BaseOrg = require('../model/baseorg')
const ConversationRepository = require('./conversationRepository')
const getConstants = require('../constants').getConstants

const skipNulls = (objValue, srcValue) => {
if (_.isArray(objValue)) {
return srcValue
}
return undefined
}
const {
handleShortNameUpdate,
automateProgramDataDates,
processJointApprovalAndMerge,
createAuditLogEntry,
handleAuthorityModelChange
} = require('./baseOrgRepositoryHelpers')

/**
* @function setAggregateOrgObj
Expand Down Expand Up @@ -822,7 +821,6 @@ class BaseOrgRepository extends BaseRepository {
return changedFields
}

/**
/**
* @async
* @function updateOrgFull
Expand All @@ -839,22 +837,25 @@ class BaseOrgRepository extends BaseRepository {
* @returns {Promise<object>} A promise that resolves to a plain JavaScript object representing the updated organization, stripped of internal properties and empty values.
*/
async updateOrgFull (shortName, incomingOrg, options = {}, isLegacyObject = false, requestingUserUUID = null, isAdmin = false, isSecretariat = false) {
// TODO: Fix these imports, remove the circular imports
const { deepRemoveEmpty } = require('../utils/utils')
const OrgRepository = require('./orgRepository')
const ReviewObjectRepository = require('./reviewObjectRepository')
const BaseUserRepository = require('./baseUserRepository')
const ConversationRepository = require('./conversationRepository')

const legacyOrgRepo = new OrgRepository()
const reviewObjectRepo = new ReviewObjectRepository()
const userRepo = new BaseUserRepository()
const conversationRepo = new ConversationRepository()

const legacyOrg = await legacyOrgRepo.findOneByShortName(shortName, options)
const registryOrg = await this.findOneByShortName(shortName, options)
const originalRegistryOrgObject = registryOrg.toObject()
// check to see if there is a PENDING review object:
const originalRoles = registryOrg.authority

const reviewObject = await reviewObjectRepo.getOrgReviewObjectByOrgShortname(shortName, isSecretariat, options)
const { conversation, ...incomingOrgBody } = incomingOrg

let legacyObjectRaw
let registryObjectRaw

Expand All @@ -866,175 +867,27 @@ class BaseOrgRepository extends BaseRepository {
legacyObjectRaw = this.convertRegistryToLegacy(incomingOrgBody)
}

if (incomingOrg?.new_short_name) {
const newName = incomingOrg.new_short_name

// 1. Update the Mongoose instances
registryOrg.short_name = newName
legacyOrg.short_name = newName

// 2. Update the raw tracking objects so lodash.merge doesn't restore the old short_name
registryObjectRaw.short_name = newName
legacyObjectRaw.short_name = newName

// 3. Remove new_short_name from the raw objects so it doesn't merge into the DB
delete registryObjectRaw.new_short_name
delete legacyObjectRaw.new_short_name
delete incomingOrg.new_short_name // Keeping for existing logic
}
handleShortNameUpdate(incomingOrg, registryOrg, legacyOrg, registryObjectRaw, legacyObjectRaw)
automateProgramDataDates(registryObjectRaw, registryOrg)

// Automate program_data dates based on status
if (registryObjectRaw.program_data && registryObjectRaw.program_data.status) {
const incomingStatus = registryObjectRaw.program_data.status
const currentStatus = registryOrg.program_data?.status || 'inactive'
if (incomingStatus !== currentStatus) {
if (incomingStatus === 'active') {
registryObjectRaw.program_data.partner_active_date = new Date().toISOString().split('T')[0]
if (registryObjectRaw.program_data.partner_inactive_date !== undefined) {
delete registryObjectRaw.program_data.partner_inactive_date
}
} else if (incomingStatus === 'inactive') {
registryObjectRaw.program_data.partner_inactive_date = new Date().toISOString().split('T')[0]
if (registryObjectRaw.program_data.partner_active_date !== undefined) {
delete registryObjectRaw.program_data.partner_active_date
}
}
} else {
// Keep existing dates if status didn't change
if (registryOrg.program_data?.partner_active_date) {
registryObjectRaw.program_data.partner_active_date = registryOrg.program_data.partner_active_date
}
if (registryOrg.program_data?.partner_inactive_date) {
registryObjectRaw.program_data.partner_inactive_date = registryOrg.program_data.partner_inactive_date
}
}
}
const requestingUser = requestingUserUUID ? await userRepo.findUserByUUID(requestingUserUUID, options) : null
const requestingUsername = requestingUser ? requestingUser.username : null

// Checking for joint approval fields
const jointApprovalFieldsRegistry = this.getJointApprovalFields(registryOrg, registryObjectRaw)
const jointApprovalFieldsLegacy = this.getJointApprovalFields(legacyOrg, legacyObjectRaw, true)
let updatedRegistryOrg = null
let updatedLegacyOrg = null
let jointApprovalRegistry = null

// If there are no joint approval fields, merge the original and updated objects. Otherwise, update the registry object and legacy object separately considering joint approval.
// Dealing with roles requires a bit of extra control.
const originalRoles = registryOrg.authority

const requestingUser = requestingUserUUID ? await userRepo.findUserByUUID(requestingUserUUID, options) : null
const requestingUsername = requestingUser ? requestingUser.username : null

const protectedFields = ['_id', 'UUID', '__v', '__t', 'created', 'last_updated', 'createdAt', 'updatedAt', 'users', 'admins']
let registryProtectedFields = [...protectedFields]
if (!isSecretariat) {
registryProtectedFields = [...registryProtectedFields, ...getConstants().ORG_RESTRICTED_FIELDS]
}
let { updatedRegistryOrg, updatedLegacyOrg } = await processJointApprovalAndMerge(
registryOrg, legacyOrg, registryObjectRaw, legacyObjectRaw, reviewObject, isSecretariat, options, requestingUsername, jointApprovalFieldsRegistry, jointApprovalFieldsLegacy
)

if (isSecretariat || _.isEmpty(jointApprovalFieldsRegistry)) {
updatedLegacyOrg = legacyOrg.overwrite(_.mergeWith(_.pick(legacyOrg.toObject(), protectedFields), _.omit(legacyObjectRaw, protectedFields), skipNulls))
updatedRegistryOrg = registryOrg.overwrite(_.mergeWith(_.pick(registryOrg.toObject(), registryProtectedFields), _.omit(registryObjectRaw, registryProtectedFields), skipNulls))
} else {
// Check if there are actual changes to joint approval fields compared to current org object (not current review)
// Only compare fields that are actually in the incoming data
const incomingJointApprovalKeys = Object.keys(_.pick(registryObjectRaw, jointApprovalFieldsRegistry))
const currentJointApprovalData = JSON.parse(JSON.stringify(_.pick(registryOrg.toObject(), incomingJointApprovalKeys)))
const incomingJointApprovalData = JSON.parse(JSON.stringify(_.pick(registryObjectRaw, incomingJointApprovalKeys)))
const hasJointApprovalChanges = !_.isEqual(currentJointApprovalData, incomingJointApprovalData)

if (hasJointApprovalChanges) {
// write the joint approval to the database
jointApprovalRegistry = _.mergeWith({}, registryOrg.toObject(), registryObjectRaw, skipNulls)
if (reviewObject) {
await reviewObjectRepo.updateReviewOrgObject(jointApprovalRegistry, reviewObject.uuid, options)
} else {
await reviewObjectRepo.createReviewOrgObject(jointApprovalRegistry, requestingUsername, options)
}
} else {
// If no changes between org and new object but a review object exists, remove it since joint approval is no longer needed
if (reviewObject) {
await reviewObjectRepo.rejectReviewOrgObject(reviewObject.uuid, requestingUsername, options)
}
}
updatedRegistryOrg = registryOrg.overwrite(_.mergeWith(_.pick(registryOrg.toObject(), [...registryProtectedFields, ...jointApprovalFieldsRegistry]), _.omit(registryObjectRaw, [...registryProtectedFields, ...jointApprovalFieldsRegistry]), skipNulls))
updatedLegacyOrg = legacyOrg.overwrite(_.mergeWith(_.pick(legacyOrg.toObject(), [...protectedFields, ...jointApprovalFieldsLegacy]), _.omit(legacyObjectRaw, [...protectedFields, ...jointApprovalFieldsLegacy]), skipNulls))
}
// handle conversation
const conversationArray = []
if (conversation) {
conversationArray.push(await conversationRepo.createConversation(registryOrg.UUID, conversation, requestingUser, isSecretariat, options))
}

// ADD AUDIT ENTRY AUTOMATICALLY for the registry object before it gets saved.
if (requestingUserUUID) {
try {
const AuditRepository = require('./auditRepository')
const auditRepo = new AuditRepository()
// Seed the audit history with the existing org data if an audit document doesn't already exist.
// This is necessary because older entities might not have an audit log yet, and we want
// the first entry to be their baseline state before this update.
await auditRepo.seedAuditHistoryForOrg(
registryOrg.UUID,
originalRegistryOrgObject,
requestingUserUUID,
{ ...options, upsert: true }
)
// Get the org state before save for comparison
const beforeUpdateObject = originalRegistryOrgObject
const afterUpdateObject = registryOrg.toObject()

// Clean objects for comparison (remove Mongoose metadata)
const cleanBefore = _.omit(beforeUpdateObject, ['_id', '__v', '__t', 'createdAt', 'updatedAt'])
const cleanAfter = _.omit(afterUpdateObject, ['_id', '__v', '__t', 'createdAt', 'updatedAt'])

// Only add audit entry if there are changes
if (!_.isEqual(cleanBefore, cleanAfter)) {
await auditRepo.appendToAuditHistoryForOrg(
registryOrg.UUID,
registryOrg.toObject(),
requestingUserUUID,
{ ...options, upsert: true }
)
}
console.log('Audit entry created for registry object')
} catch (auditError) {
console.error('Audit entry creation failed:', auditError)
}
}

// Handle possible authority (discriminator) changes that require a different Mongoose model
let roleChange = false
if (!_.isEqual([...originalRoles].sort(), [...updatedRegistryOrg?.authority].sort())) {
roleChange = true
}

// Determine the correct model based on the updated authority
let TargetModel = null
if (updatedRegistryOrg.authority?.includes('SECRETARIAT')) {
TargetModel = SecretariatOrgModel
} else if (updatedRegistryOrg.authority?.includes('CNA')) {
TargetModel = CNAOrgModel
} else if (updatedRegistryOrg.authority?.includes('ADP')) {
TargetModel = ADPOrgModel
} else if (updatedRegistryOrg.authority?.includes('BULK_DOWNLOAD')) {
TargetModel = BulkDownloadModel
} else if (updatedRegistryOrg.authority?.includes('ROOT')) {
TargetModel = RootOrgModel
}
await createAuditLogEntry(registryOrg, originalRegistryOrgObject, requestingUserUUID, options)

// If the model type has changed, replace the document with a new one of the correct type
if (TargetModel && roleChange) {
const oldId = updatedRegistryOrg._id
// Remove the old document
await BaseOrgModel.deleteOne({ _id: oldId }, options)
// Prepare data for the new document, preserving the UUID and _id
const newDocData = updatedRegistryOrg.toObject()
delete newDocData.__t
newDocData._id = oldId
const newDoc = new TargetModel(newDocData)
await newDoc.save(options)
// Update reference so subsequent code works with the newly saved document
updatedRegistryOrg = newDoc
}
updatedRegistryOrg = await handleAuthorityModelChange(updatedRegistryOrg, originalRoles, options)

try {
await updatedLegacyOrg.save(options)
Expand Down
Loading
Loading